Commit 6e914fc7 authored by Jason Wang's avatar Jason Wang Committed by David S. Miller

tuntap: RCUify dereferencing between tun_struct and tun_file

RCU were introduced in this patch to synchronize the dereferences between
tun_struct and tun_file. All tun_{get|put} were replaced with RCU, the
dereference from one to other must be done under rtnl lock or rcu read critical
region.

This is needed for the following patches since the one of the goal of multiqueue
tuntap is to allow adding or removing queues during workload. Without RCU,
control path would hold tx locks when adding or removing queues (which may cause
sme delay) and it's hard to change the number of queues without stopping the net
device. With the help of rcu, there's also no need for tun_file hold an refcnt
to tun_struct.
Signed-off-by: default avatarJason Wang <jasowang@redhat.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 54f968d6
...@@ -115,13 +115,16 @@ struct tap_filter { ...@@ -115,13 +115,16 @@ struct tap_filter {
* tap_filter were kept in tun_struct since they were used for filtering for the * tap_filter were kept in tun_struct since they were used for filtering for the
* netdevice not for a specific queue (at least I didn't see the reqirement for * netdevice not for a specific queue (at least I didn't see the reqirement for
* this). * this).
*
* RCU usage:
* The tun_file and tun_struct are loosely coupled, the pointer from on to the
* other can only be read while rcu_read_lock or rtnl_lock is held.
*/ */
struct tun_file { struct tun_file {
struct sock sk; struct sock sk;
struct socket socket; struct socket socket;
struct socket_wq wq; struct socket_wq wq;
atomic_t count; struct tun_struct __rcu *tun;
struct tun_struct *tun;
struct net *net; struct net *net;
struct fasync_struct *fasync; struct fasync_struct *fasync;
/* only used for fasnyc */ /* only used for fasnyc */
...@@ -133,7 +136,7 @@ struct tun_file { ...@@ -133,7 +136,7 @@ struct tun_file {
* file were attached to a persist device. * file were attached to a persist device.
*/ */
struct tun_struct { struct tun_struct {
struct tun_file *tfile; struct tun_file __rcu *tfile;
unsigned int flags; unsigned int flags;
kuid_t owner; kuid_t owner;
kgid_t group; kgid_t group;
...@@ -179,13 +182,11 @@ static int tun_attach(struct tun_struct *tun, struct file *file) ...@@ -179,13 +182,11 @@ static int tun_attach(struct tun_struct *tun, struct file *file)
if (!err) if (!err)
goto out; goto out;
} }
tfile->tun = tun; rcu_assign_pointer(tfile->tun, tun);
tfile->socket.sk->sk_sndbuf = tun->sndbuf; tfile->socket.sk->sk_sndbuf = tun->sndbuf;
tun->tfile = tfile; rcu_assign_pointer(tun->tfile, tfile);
netif_carrier_on(tun->dev); netif_carrier_on(tun->dev);
dev_hold(tun->dev);
sock_hold(&tfile->sk); sock_hold(&tfile->sk);
atomic_inc(&tfile->count);
out: out:
netif_tx_unlock_bh(tun->dev); netif_tx_unlock_bh(tun->dev);
...@@ -194,34 +195,29 @@ static int tun_attach(struct tun_struct *tun, struct file *file) ...@@ -194,34 +195,29 @@ static int tun_attach(struct tun_struct *tun, struct file *file)
static void __tun_detach(struct tun_struct *tun) static void __tun_detach(struct tun_struct *tun)
{ {
struct tun_file *tfile = tun->tfile; struct tun_file *tfile = rcu_dereference_protected(tun->tfile,
lockdep_rtnl_is_held());
/* Detach from net device */ /* Detach from net device */
netif_tx_lock_bh(tun->dev);
netif_carrier_off(tun->dev); netif_carrier_off(tun->dev);
tun->tfile = NULL; rcu_assign_pointer(tun->tfile, NULL);
tfile->tun = NULL; if (tfile) {
netif_tx_unlock_bh(tun->dev); rcu_assign_pointer(tfile->tun, NULL);
synchronize_net();
/* Drop read queue */ /* Drop read queue */
skb_queue_purge(&tfile->socket.sk->sk_receive_queue); skb_queue_purge(&tfile->socket.sk->sk_receive_queue);
}
/* Drop the extra count on the net device */
dev_put(tun->dev);
}
static void tun_detach(struct tun_struct *tun)
{
rtnl_lock();
__tun_detach(tun);
rtnl_unlock();
} }
static struct tun_struct *__tun_get(struct tun_file *tfile) static struct tun_struct *__tun_get(struct tun_file *tfile)
{ {
struct tun_struct *tun = NULL; struct tun_struct *tun;
if (atomic_inc_not_zero(&tfile->count)) rcu_read_lock();
tun = tfile->tun; tun = rcu_dereference(tfile->tun);
if (tun)
dev_hold(tun->dev);
rcu_read_unlock();
return tun; return tun;
} }
...@@ -233,10 +229,7 @@ static struct tun_struct *tun_get(struct file *file) ...@@ -233,10 +229,7 @@ static struct tun_struct *tun_get(struct file *file)
static void tun_put(struct tun_struct *tun) static void tun_put(struct tun_struct *tun)
{ {
struct tun_file *tfile = tun->tfile; dev_put(tun->dev);
if (atomic_dec_and_test(&tfile->count))
tun_detach(tfile->tun);
} }
/* TAP filtering */ /* TAP filtering */
...@@ -357,14 +350,15 @@ static const struct ethtool_ops tun_ethtool_ops; ...@@ -357,14 +350,15 @@ static const struct ethtool_ops tun_ethtool_ops;
static void tun_net_uninit(struct net_device *dev) static void tun_net_uninit(struct net_device *dev)
{ {
struct tun_struct *tun = netdev_priv(dev); struct tun_struct *tun = netdev_priv(dev);
struct tun_file *tfile = tun->tfile; struct tun_file *tfile = rcu_dereference_protected(tun->tfile,
lockdep_rtnl_is_held());
/* Inform the methods they need to stop using the dev. /* Inform the methods they need to stop using the dev.
*/ */
if (tfile) { if (tfile) {
wake_up_all(&tfile->wq.wait); wake_up_all(&tfile->wq.wait);
if (atomic_dec_and_test(&tfile->count))
__tun_detach(tun); __tun_detach(tun);
synchronize_net();
} }
} }
...@@ -386,14 +380,16 @@ static int tun_net_close(struct net_device *dev) ...@@ -386,14 +380,16 @@ static int tun_net_close(struct net_device *dev)
static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev) static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{ {
struct tun_struct *tun = netdev_priv(dev); struct tun_struct *tun = netdev_priv(dev);
struct tun_file *tfile = tun->tfile; struct tun_file *tfile;
tun_debug(KERN_INFO, tun, "tun_net_xmit %d\n", skb->len);
rcu_read_lock();
tfile = rcu_dereference(tun->tfile);
/* Drop packet if interface is not attached */ /* Drop packet if interface is not attached */
if (!tfile) if (!tfile)
goto drop; goto drop;
tun_debug(KERN_INFO, tun, "tun_net_xmit %d\n", skb->len);
/* Drop if the filter does not like it. /* Drop if the filter does not like it.
* This is a noop if the filter is disabled. * This is a noop if the filter is disabled.
* Filter can be enabled only for the TAP devices. */ * Filter can be enabled only for the TAP devices. */
...@@ -435,11 +431,14 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -435,11 +431,14 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
kill_fasync(&tfile->fasync, SIGIO, POLL_IN); kill_fasync(&tfile->fasync, SIGIO, POLL_IN);
wake_up_interruptible_poll(&tfile->wq.wait, POLLIN | wake_up_interruptible_poll(&tfile->wq.wait, POLLIN |
POLLRDNORM | POLLRDBAND); POLLRDNORM | POLLRDBAND);
rcu_read_unlock();
return NETDEV_TX_OK; return NETDEV_TX_OK;
drop: drop:
dev->stats.tx_dropped++; dev->stats.tx_dropped++;
kfree_skb(skb); kfree_skb(skb);
rcu_read_unlock();
return NETDEV_TX_OK; return NETDEV_TX_OK;
} }
...@@ -1089,7 +1088,6 @@ static int tun_sendmsg(struct kiocb *iocb, struct socket *sock, ...@@ -1089,7 +1088,6 @@ static int tun_sendmsg(struct kiocb *iocb, struct socket *sock,
if (!tun) if (!tun)
return -EBADFD; return -EBADFD;
ret = tun_get_user(tun, tfile, m->msg_control, m->msg_iov, total_len, ret = tun_get_user(tun, tfile, m->msg_control, m->msg_iov, total_len,
m->msg_iovlen, m->msg_flags & MSG_DONTWAIT); m->msg_iovlen, m->msg_flags & MSG_DONTWAIT);
tun_put(tun); tun_put(tun);
...@@ -1662,8 +1660,7 @@ static int tun_chr_open(struct inode *inode, struct file * file) ...@@ -1662,8 +1660,7 @@ static int tun_chr_open(struct inode *inode, struct file * file)
&tun_proto); &tun_proto);
if (!tfile) if (!tfile)
return -ENOMEM; return -ENOMEM;
atomic_set(&tfile->count, 0); rcu_assign_pointer(tfile->tun, NULL);
tfile->tun = NULL;
tfile->net = get_net(current->nsproxy->net_ns); tfile->net = get_net(current->nsproxy->net_ns);
tfile->flags = 0; tfile->flags = 0;
...@@ -1691,7 +1688,9 @@ static int tun_chr_close(struct inode *inode, struct file *file) ...@@ -1691,7 +1688,9 @@ static int tun_chr_close(struct inode *inode, struct file *file)
struct tun_struct *tun; struct tun_struct *tun;
struct net *net = tfile->net; struct net *net = tfile->net;
tun = __tun_get(tfile); rtnl_lock();
tun = rcu_dereference_protected(tfile->tun, lockdep_rtnl_is_held());
if (tun) { if (tun) {
struct net_device *dev = tun->dev; struct net_device *dev = tun->dev;
...@@ -1699,18 +1698,20 @@ static int tun_chr_close(struct inode *inode, struct file *file) ...@@ -1699,18 +1698,20 @@ static int tun_chr_close(struct inode *inode, struct file *file)
__tun_detach(tun); __tun_detach(tun);
synchronize_net();
/* If desirable, unregister the netdevice. */ /* If desirable, unregister the netdevice. */
if (!(tun->flags & TUN_PERSIST)) { if (!(tun->flags & TUN_PERSIST)) {
rtnl_lock();
if (dev->reg_state == NETREG_REGISTERED) if (dev->reg_state == NETREG_REGISTERED)
unregister_netdevice(dev); unregister_netdevice(dev);
rtnl_unlock();
} }
/* drop the reference that netdevice holds */ /* drop the reference that netdevice holds */
sock_put(&tfile->sk); sock_put(&tfile->sk);
} }
rtnl_unlock();
/* drop the reference that file holds */ /* drop the reference that file holds */
BUG_ON(!test_bit(SOCK_EXTERNALLY_ALLOCATED, BUG_ON(!test_bit(SOCK_EXTERNALLY_ALLOCATED,
&tfile->socket.flags)); &tfile->socket.flags));
...@@ -1842,14 +1843,12 @@ static void tun_cleanup(void) ...@@ -1842,14 +1843,12 @@ static void tun_cleanup(void)
* holding a reference to the file for as long as the socket is in use. */ * holding a reference to the file for as long as the socket is in use. */
struct socket *tun_get_socket(struct file *file) struct socket *tun_get_socket(struct file *file)
{ {
struct tun_struct *tun; struct tun_file *tfile;
struct tun_file *tfile = file->private_data;
if (file->f_op != &tun_fops) if (file->f_op != &tun_fops)
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
tun = tun_get(file); tfile = file->private_data;
if (!tun) if (!tfile)
return ERR_PTR(-EBADFD); return ERR_PTR(-EBADFD);
tun_put(tun);
return &tfile->socket; return &tfile->socket;
} }
EXPORT_SYMBOL_GPL(tun_get_socket); EXPORT_SYMBOL_GPL(tun_get_socket);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment