diff options
-rw-r--r-- | quicktun.c | 54 |
1 files changed, 47 insertions, 7 deletions
@@ -4,7 +4,6 @@ #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> @@ -37,6 +36,8 @@ struct addr_struct { struct quicktun_struct { struct list_head list; + spinlock_t lock; + struct net_device *dev; unsigned long flags; @@ -67,9 +68,9 @@ static void quicktun_send_bh(struct work_struct *work) remote_addr = rcu_dereference(tun->remote_address); - if (remote_addr->addr.sin_addr.s_addr) { + if (likely(remote_addr->addr.sin_addr.s_addr)) { err = skb_linearize(skb); - if (err < 0) + if (unlikely(err < 0)) goto error; vec.iov_base = skb->data; @@ -109,7 +110,7 @@ error: 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) { + if (likely(work)) { INIT_WORK(&work->work, quicktun_send_bh); work->quicktun = netdev_priv(dev); work->skb = skb; @@ -129,7 +130,8 @@ static void free_addr_struct(struct rcu_head *rcu) 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); + + call_rcu(&rcu_dereference_protected(tun->remote_address, 1)->rcu, free_addr_struct); } @@ -138,13 +140,14 @@ static void quicktun_udp_data_ready(struct sock *sk, int bytes) struct sk_buff *skb; unsigned int len; struct quicktun_struct *tun = sk->sk_user_data; + struct addr_struct *remote_addr; int err; read_lock(&sk->sk_callback_lock); skb = skb_recv_datagram(sk, 0, 1, &err); - if (!skb) { + if (unlikely(!skb)) { read_unlock(&sk->sk_callback_lock); if (err == -EAGAIN) return; @@ -158,6 +161,37 @@ static void quicktun_udp_data_ready(struct sock *sk, int bytes) if (err < 0) goto drop; + rcu_read_lock(); + + remote_addr = rcu_dereference(tun->remote_address); + + if (unlikely((remote_addr->addr.sin_addr.s_addr != ip_hdr(skb)->saddr) || (remote_addr->addr.sin_port != udp_hdr(skb)->source))) { + rcu_read_unlock(); + + if (ACCESS_ONCE(tun->flags) & QUICKTUN_FLAG_REMOTE_FLOAT) { + struct addr_struct *addr = kmalloc(sizeof(struct addr_struct), GFP_KERNEL); + if (!addr) + goto drop; + + addr->addr.sin_addr.s_addr = ip_hdr(skb)->saddr; + addr->addr.sin_port = udp_hdr(skb)->source; + + spin_lock(&tun->lock); + + if (tun->flags & QUICKTUN_FLAG_REMOTE_FLOAT) { + remote_addr = rcu_dereference_protected(tun->remote_address, lockdep_is_held(&tun->lock)); + rcu_assign_pointer(tun->remote_address, addr); + call_rcu(&remote_addr->rcu, free_addr_struct); + } + + spin_unlock(&tun->lock); + } + else + goto drop; + } + else + rcu_read_unlock(); + __skb_pull(skb, sizeof(struct udphdr)); len = skb->len; @@ -267,6 +301,8 @@ static void quicktun_setup(struct net_device *dev) { struct quicktun_struct *tun = netdev_priv(dev); + spin_lock_init(&tun->lock); + tun->local_address.sin_family = AF_INET; tun->local_address.sin_addr.s_addr = 0; tun->local_address.sin_port = htons(QUICKTUN_DEFAULT_PORT); @@ -496,7 +532,9 @@ 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); + rcu_read_lock(); + + remote_addr = rcu_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); @@ -508,6 +546,8 @@ static void __put_tunnel_spec(struct sk_buff *skb, struct quicktun_struct *tun) if (tun->flags & QUICKTUN_FLAG_REMOTE_FLOAT) nla_put_flag(skb, QUICKTUN_A_REMOTE_FLOAT); + rcu_read_unlock(); + nla_nest_end(skb, nested); } |