From 9de3f331f0402b65682aadb7f65c4bd4f16ffd3f Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Mon, 25 Apr 2011 23:02:07 +0200 Subject: Fix "scheduling while atomic" in xmit path --- quicktun.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file 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 #include #include +#include #include +#include #include #include #include @@ -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); -- cgit v1.2.3