summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Schiffer <mschiffer@universe-factory.net>2011-04-25 23:02:07 +0200
committerMatthias Schiffer <mschiffer@universe-factory.net>2011-04-26 03:07:38 +0200
commit9de3f331f0402b65682aadb7f65c4bd4f16ffd3f (patch)
tree608bb6a028bc194e4236ab32ad2334cec793c09b
parent50f98aec98d066129b932e53bf33eef64896938f (diff)
downloadmodquicktun-9de3f331f0402b65682aadb7f65c4bd4f16ffd3f.tar
modquicktun-9de3f331f0402b65682aadb7f65c4bd4f16ffd3f.zip
Fix "scheduling while atomic" in xmit path
-rw-r--r--quicktun.c98
1 files changed, 83 insertions, 15 deletions
diff --git a/quicktun.c b/quicktun.c
index 46d973a..303d0bb 100644
--- a/quicktun.c
+++ b/quicktun.c
@@ -4,7 +4,9 @@
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/list.h>
+#include <linux/kthread.h>
#include <linux/netdevice.h>
+#include <linux/rcupdate.h>
#include <linux/udp.h>
#include <net/dst.h>
#include <net/genetlink.h>
@@ -27,6 +29,11 @@
static LIST_HEAD(quicktun_list);
+struct addr_struct {
+ struct rcu_head rcu;
+ struct sockaddr_in addr;
+};
+
struct quicktun_struct {
struct list_head list;
@@ -34,20 +41,33 @@ struct quicktun_struct {
unsigned long flags;
struct sockaddr_in local_address;
- struct sockaddr_in remote_address;
+ struct addr_struct *remote_address;
struct socket *sock;
};
+struct send_work {
+ struct work_struct work;
+ struct quicktun_struct *quicktun;
+ struct sk_buff *skb;
+};
-static netdev_tx_t quicktun_net_xmit(struct sk_buff *skb, struct net_device *dev)
+
+static void quicktun_send_bh(struct work_struct *work)
{
- struct quicktun_struct *tun = netdev_priv(dev);
+ struct send_work *send_work = container_of(work, struct send_work, work);
+ struct sk_buff *skb = send_work->skb;
+ struct quicktun_struct *tun = send_work->quicktun;
+ struct addr_struct *remote_addr;
struct msghdr msg;
struct kvec vec;
int err;
- if (tun->remote_address.sin_addr.s_addr) {
+ rcu_read_lock();
+
+ remote_addr = rcu_dereference(tun->remote_address);
+
+ if (remote_addr->addr.sin_addr.s_addr) {
err = skb_linearize(skb);
if (err < 0)
goto error;
@@ -55,8 +75,8 @@ static netdev_tx_t quicktun_net_xmit(struct sk_buff *skb, struct net_device *dev
vec.iov_base = skb->data;
vec.iov_len = skb->len;
- msg.msg_name = &tun->remote_address;
- msg.msg_namelen = sizeof(tun->remote_address);
+ msg.msg_name = &remote_addr->addr;
+ msg.msg_namelen = sizeof(remote_addr->addr);
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = MSG_DONTWAIT;
@@ -69,17 +89,50 @@ static netdev_tx_t quicktun_net_xmit(struct sk_buff *skb, struct net_device *dev
else
goto error;
+ rcu_read_unlock();
+
consume_skb(skb);
+ kfree(send_work);
- return NETDEV_TX_OK;
+ return;
error:
+ rcu_read_unlock();
+
kfree_skb(skb);
+ kfree(send_work);
tun->dev->stats.tx_errors++;
+}
+
+
+static netdev_tx_t quicktun_net_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct send_work *work = kmalloc(sizeof(struct send_work), GFP_ATOMIC);
+ if (work) {
+ INIT_WORK(&work->work, quicktun_send_bh);
+ work->quicktun = netdev_priv(dev);
+ work->skb = skb;
+ schedule_work(&work->work);
+ }
+
return NETDEV_TX_OK;
}
+
+static void free_addr_struct(struct rcu_head *rcu)
+{
+ struct addr_struct *addr = container_of(rcu, struct addr_struct, rcu);
+ kfree(addr);
+}
+
+static void quicktun_net_uninit(struct net_device *dev)
+{
+ struct quicktun_struct *tun = netdev_priv(dev);
+ call_rcu(&rtnl_dereference(tun->remote_address)->rcu, free_addr_struct);
+}
+
+
static void quicktun_udp_data_ready(struct sock *sk, int bytes)
{
struct sk_buff *skb;
@@ -170,11 +223,13 @@ static const struct net_device_ops quicktun_ethernet_netdev_ops = {
.ndo_change_mtu = quicktun_net_change_mtu,
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
+ .ndo_uninit = quicktun_net_uninit,
};
static const struct net_device_ops quicktun_ip_netdev_ops = {
.ndo_start_xmit = quicktun_net_xmit,
.ndo_change_mtu = quicktun_net_change_mtu,
+ .ndo_uninit = quicktun_net_uninit,
};
@@ -215,9 +270,6 @@ static void quicktun_setup(struct net_device *dev)
tun->local_address.sin_family = AF_INET;
tun->local_address.sin_addr.s_addr = 0;
tun->local_address.sin_port = htons(QUICKTUN_DEFAULT_PORT);
- tun->remote_address.sin_family = AF_INET;
- tun->remote_address.sin_addr.s_addr = 0;
- tun->remote_address.sin_port = htons(QUICKTUN_DEFAULT_PORT);
dev->ethtool_ops = &quicktun_ethtool_ops;
}
@@ -238,6 +290,7 @@ static void quicktun_net_init(struct net_device *dev)
case QUICKTUN_MODE_IP:
dev->netdev_ops = &quicktun_ip_netdev_ops;
+ dev->destructor = free_netdev;
dev->hard_header_len = 0;
dev->addr_len = 0;
dev->mtu = 1500;
@@ -320,6 +373,16 @@ static int quicktun_cmd_create_device(struct sk_buff *skb, struct genl_info *inf
tun->dev = dev;
tun->flags = flags;
+ tun->remote_address = kmalloc(sizeof(struct addr_struct), GFP_KERNEL);
+ if (!tun->remote_address) {
+ err = -ENOMEM;
+ goto err_free_dev;
+ }
+
+ tun->remote_address->addr.sin_family = AF_INET;
+ tun->remote_address->addr.sin_addr.s_addr = 0;
+ tun->remote_address->addr.sin_port = htons(QUICKTUN_DEFAULT_PORT);
+
quicktun_net_init(dev);
if (info->attrs[QUICKTUN_A_LOCAL_ADDRESS])
@@ -327,13 +390,13 @@ static int quicktun_cmd_create_device(struct sk_buff *skb, struct genl_info *inf
/* Set remote port to local port by default */
if (info->attrs[QUICKTUN_A_LOCAL_PORT])
- tun->remote_address.sin_port = tun->local_address.sin_port = nla_get_be16(info->attrs[QUICKTUN_A_LOCAL_PORT]);
+ tun->remote_address->addr.sin_port = tun->local_address.sin_port = nla_get_be16(info->attrs[QUICKTUN_A_LOCAL_PORT]);
if (info->attrs[QUICKTUN_A_REMOTE_ADDRESS])
- tun->remote_address.sin_addr.s_addr = nla_get_be32(info->attrs[QUICKTUN_A_REMOTE_ADDRESS]);
+ tun->remote_address->addr.sin_addr.s_addr = nla_get_be32(info->attrs[QUICKTUN_A_REMOTE_ADDRESS]);
if (info->attrs[QUICKTUN_A_REMOTE_PORT])
- tun->remote_address.sin_port = nla_get_be16(info->attrs[QUICKTUN_A_REMOTE_PORT]);
+ tun->remote_address->addr.sin_port = nla_get_be16(info->attrs[QUICKTUN_A_REMOTE_PORT]);
if (info->attrs[QUICKTUN_A_REMOTE_FLOAT]) {
if(nla_get_flag(info->attrs[QUICKTUN_A_REMOTE_FLOAT]))
@@ -383,6 +446,8 @@ static void __quicktun_delete_device(struct net_device *dev) {
list_del(&tun->list);
unregister_netdevice(dev);
+
+ flush_scheduled_work();
sock_release(sock);
}
@@ -429,13 +494,16 @@ err_unlock:
static void __put_tunnel_spec(struct sk_buff *skb, struct quicktun_struct *tun)
{
struct nlattr *nested = nla_nest_start(skb, QUICKTUN_A_TUNNEL_SPEC);
+ struct addr_struct *remote_addr;
+
+ remote_addr = rtnl_dereference(tun->remote_address);
nla_put_string(skb, QUICKTUN_A_IFNAME, tun->dev->name);
nla_put_u16(skb, QUICKTUN_A_MODE, tun->flags & QUICKTUN_MODE_MASK);
nla_put_u32(skb, QUICKTUN_A_LOCAL_ADDRESS, tun->local_address.sin_addr.s_addr);
nla_put_u16(skb, QUICKTUN_A_LOCAL_PORT, tun->local_address.sin_port);
- nla_put_u32(skb, QUICKTUN_A_REMOTE_ADDRESS, tun->remote_address.sin_addr.s_addr);
- nla_put_u16(skb, QUICKTUN_A_REMOTE_PORT, tun->remote_address.sin_port);
+ nla_put_u32(skb, QUICKTUN_A_REMOTE_ADDRESS, remote_addr->addr.sin_addr.s_addr);
+ nla_put_u16(skb, QUICKTUN_A_REMOTE_PORT, remote_addr->addr.sin_port);
if (tun->flags & QUICKTUN_FLAG_REMOTE_FLOAT)
nla_put_flag(skb, QUICKTUN_A_REMOTE_FLOAT);