summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--quicktun.c54
1 files changed, 47 insertions, 7 deletions
diff --git a/quicktun.c b/quicktun.c
index 303d0bb..4881960 100644
--- a/quicktun.c
+++ b/quicktun.c
@@ -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);
}