diff options
Diffstat (limited to 'drivers/net/tun.c')
-rw-r--r-- | drivers/net/tun.c | 111 |
1 files changed, 67 insertions, 44 deletions
diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 40b426edc9e..fbd106edbe5 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -138,6 +138,8 @@ struct tun_file { /* only used for fasnyc */ unsigned int flags; u16 queue_index; + struct list_head next; + struct tun_struct *detached; }; struct tun_flow_entry { @@ -178,10 +180,11 @@ struct tun_struct { int debug; #endif spinlock_t lock; - struct kmem_cache *flow_cache; struct hlist_head flows[TUN_NUM_FLOW_ENTRIES]; struct timer_list flow_gc_timer; unsigned long ageing_time; + unsigned int numdisabled; + struct list_head disabled; }; static inline u32 tun_hashfn(u32 rxhash) @@ -205,8 +208,8 @@ static struct tun_flow_entry *tun_flow_create(struct tun_struct *tun, struct hlist_head *head, u32 rxhash, u16 queue_index) { - struct tun_flow_entry *e = kmem_cache_alloc(tun->flow_cache, - GFP_ATOMIC); + struct tun_flow_entry *e = kmalloc(sizeof(*e), GFP_ATOMIC); + if (e) { tun_debug(KERN_INFO, tun, "create flow: hash %u index %u\n", rxhash, queue_index); @@ -219,19 +222,12 @@ static struct tun_flow_entry *tun_flow_create(struct tun_struct *tun, return e; } -static void tun_flow_free(struct rcu_head *head) -{ - struct tun_flow_entry *e - = container_of(head, struct tun_flow_entry, rcu); - kmem_cache_free(e->tun->flow_cache, e); -} - static void tun_flow_delete(struct tun_struct *tun, struct tun_flow_entry *e) { tun_debug(KERN_INFO, tun, "delete flow: hash %u index %u\n", e->rxhash, e->queue_index); hlist_del_rcu(&e->hash_link); - call_rcu(&e->rcu, tun_flow_free); + kfree_rcu(e, rcu); } static void tun_flow_flush(struct tun_struct *tun) @@ -385,6 +381,23 @@ static void tun_set_real_num_queues(struct tun_struct *tun) netif_set_real_num_rx_queues(tun->dev, tun->numqueues); } +static void tun_disable_queue(struct tun_struct *tun, struct tun_file *tfile) +{ + tfile->detached = tun; + list_add_tail(&tfile->next, &tun->disabled); + ++tun->numdisabled; +} + +static struct tun_struct *tun_enable_queue(struct tun_file *tfile) +{ + struct tun_struct *tun = tfile->detached; + + tfile->detached = NULL; + list_del_init(&tfile->next); + --tun->numdisabled; + return tun; +} + static void __tun_detach(struct tun_file *tfile, bool clean) { struct tun_file *ntfile; @@ -406,20 +419,25 @@ static void __tun_detach(struct tun_file *tfile, bool clean) ntfile->queue_index = index; --tun->numqueues; - sock_put(&tfile->sk); + if (clean) + sock_put(&tfile->sk); + else + tun_disable_queue(tun, tfile); synchronize_net(); tun_flow_delete_by_queue(tun, tun->numqueues + 1); /* Drop read queue */ skb_queue_purge(&tfile->sk.sk_receive_queue); tun_set_real_num_queues(tun); - - if (tun->numqueues == 0 && !(tun->flags & TUN_PERSIST)) - if (dev->reg_state == NETREG_REGISTERED) - unregister_netdevice(dev); - } + } else if (tfile->detached && clean) + tun = tun_enable_queue(tfile); if (clean) { + if (tun && tun->numqueues == 0 && tun->numdisabled == 0 && + !(tun->flags & TUN_PERSIST)) + if (tun->dev->reg_state == NETREG_REGISTERED) + unregister_netdevice(tun->dev); + BUG_ON(!test_bit(SOCK_EXTERNALLY_ALLOCATED, &tfile->socket.flags)); sk_release_kernel(&tfile->sk); @@ -436,7 +454,7 @@ static void tun_detach(struct tun_file *tfile, bool clean) static void tun_detach_all(struct net_device *dev) { struct tun_struct *tun = netdev_priv(dev); - struct tun_file *tfile; + struct tun_file *tfile, *tmp; int i, n = tun->numqueues; for (i = 0; i < n; i++) { @@ -457,6 +475,12 @@ static void tun_detach_all(struct net_device *dev) skb_queue_purge(&tfile->sk.sk_receive_queue); sock_put(&tfile->sk); } + list_for_each_entry_safe(tfile, tmp, &tun->disabled, next) { + tun_enable_queue(tfile); + skb_queue_purge(&tfile->sk.sk_receive_queue); + sock_put(&tfile->sk); + } + BUG_ON(tun->numdisabled != 0); } static int tun_attach(struct tun_struct *tun, struct file *file) @@ -473,7 +497,8 @@ static int tun_attach(struct tun_struct *tun, struct file *file) goto out; err = -E2BIG; - if (tun->numqueues == MAX_TAP_QUEUES) + if (!tfile->detached && + tun->numqueues + tun->numdisabled == MAX_TAP_QUEUES) goto out; err = 0; @@ -487,9 +512,13 @@ static int tun_attach(struct tun_struct *tun, struct file *file) tfile->queue_index = tun->numqueues; rcu_assign_pointer(tfile->tun, tun); rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile); - sock_hold(&tfile->sk); tun->numqueues++; + if (tfile->detached) + tun_enable_queue(tfile); + else + sock_hold(&tfile->sk); + tun_set_real_num_queues(tun); /* device is allowed to go away first, so no need to hold extra @@ -796,12 +825,6 @@ static int tun_flow_init(struct tun_struct *tun) { int i; - tun->flow_cache = kmem_cache_create("tun_flow_cache", - sizeof(struct tun_flow_entry), 0, 0, - NULL); - if (!tun->flow_cache) - return -ENOMEM; - for (i = 0; i < TUN_NUM_FLOW_ENTRIES; i++) INIT_HLIST_HEAD(&tun->flows[i]); @@ -817,10 +840,6 @@ static void tun_flow_uninit(struct tun_struct *tun) { del_timer_sync(&tun->flow_gc_timer); tun_flow_flush(tun); - - /* Wait for completion of call_rcu()'s */ - rcu_barrier(); - kmem_cache_destroy(tun->flow_cache); } /* Initialize net device. */ @@ -1162,6 +1181,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, skb_shinfo(skb)->tx_flags |= SKBTX_DEV_ZEROCOPY; } + skb_reset_network_header(skb); rxhash = skb_get_rxhash(skb); netif_rx_ni(skb); @@ -1349,6 +1369,7 @@ static void tun_free_netdev(struct net_device *dev) { struct tun_struct *tun = netdev_priv(dev); + BUG_ON(!(list_empty(&tun->disabled))); tun_flow_uninit(tun); free_netdev(dev); } @@ -1543,6 +1564,10 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) err = tun_attach(tun, file); if (err < 0) return err; + + if (tun->flags & TUN_TAP_MQ && + (tun->numqueues + tun->numdisabled > 1)) + return err; } else { char *name; @@ -1601,6 +1626,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) TUN_USER_FEATURES; dev->features = dev->hw_features; + INIT_LIST_HEAD(&tun->disabled); err = tun_attach(tun, file); if (err < 0) goto err_free_dev; @@ -1755,32 +1781,28 @@ static int tun_set_queue(struct file *file, struct ifreq *ifr) { struct tun_file *tfile = file->private_data; struct tun_struct *tun; - struct net_device *dev; int ret = 0; rtnl_lock(); if (ifr->ifr_flags & IFF_ATTACH_QUEUE) { - dev = __dev_get_by_name(tfile->net, ifr->ifr_name); - if (!dev) { - ret = -EINVAL; - goto unlock; - } - - tun = netdev_priv(dev); - if (dev->netdev_ops != &tap_netdev_ops && - dev->netdev_ops != &tun_netdev_ops) + tun = tfile->detached; + if (!tun) ret = -EINVAL; else if (tun_not_capable(tun)) ret = -EPERM; else ret = tun_attach(tun, file); - } else if (ifr->ifr_flags & IFF_DETACH_QUEUE) - __tun_detach(tfile, false); - else + } else if (ifr->ifr_flags & IFF_DETACH_QUEUE) { + tun = rcu_dereference_protected(tfile->tun, + lockdep_rtnl_is_held()); + if (!tun || !(tun->flags & TUN_TAP_MQ)) + ret = -EINVAL; + else + __tun_detach(tfile, false); + } else ret = -EINVAL; -unlock: rtnl_unlock(); return ret; } @@ -2092,6 +2114,7 @@ static int tun_chr_open(struct inode *inode, struct file * file) file->private_data = tfile; set_bit(SOCK_EXTERNALLY_ALLOCATED, &tfile->socket.flags); + INIT_LIST_HEAD(&tfile->next); return 0; } |