]> bbs.cooldavid.org Git - net-next-2.6.git/blobdiff - net/l2tp/l2tp_core.c
net: replace ipfragok with skb->local_df
[net-next-2.6.git] / net / l2tp / l2tp_core.c
index fbd1f2119fe9de42906779cd8370e3138cf1f15b..ecc7aea9efe4e19296476cd06e5cb66b2a593f63 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/module.h>
 #include <linux/string.h>
 #include <linux/list.h>
+#include <linux/rculist.h>
 #include <linux/uaccess.h>
 
 #include <linux/kernel.h>
@@ -105,9 +106,9 @@ static atomic_t l2tp_session_count;
 static unsigned int l2tp_net_id;
 struct l2tp_net {
        struct list_head l2tp_tunnel_list;
-       rwlock_t l2tp_tunnel_list_lock;
+       spinlock_t l2tp_tunnel_list_lock;
        struct hlist_head l2tp_session_hlist[L2TP_HASH_SIZE_2];
-       rwlock_t l2tp_session_hlist_lock;
+       spinlock_t l2tp_session_hlist_lock;
 };
 
 static inline struct l2tp_net *l2tp_pernet(struct net *net)
@@ -139,14 +140,14 @@ static struct l2tp_session *l2tp_session_find_2(struct net *net, u32 session_id)
        struct l2tp_session *session;
        struct hlist_node *walk;
 
-       read_lock_bh(&pn->l2tp_session_hlist_lock);
-       hlist_for_each_entry(session, walk, session_list, global_hlist) {
+       rcu_read_lock_bh();
+       hlist_for_each_entry_rcu(session, walk, session_list, global_hlist) {
                if (session->session_id == session_id) {
-                       read_unlock_bh(&pn->l2tp_session_hlist_lock);
+                       rcu_read_unlock_bh();
                        return session;
                }
        }
-       read_unlock_bh(&pn->l2tp_session_hlist_lock);
+       rcu_read_unlock_bh();
 
        return NULL;
 }
@@ -225,17 +226,17 @@ struct l2tp_session *l2tp_session_find_by_ifname(struct net *net, char *ifname)
        struct hlist_node *walk;
        struct l2tp_session *session;
 
-       read_lock_bh(&pn->l2tp_session_hlist_lock);
+       rcu_read_lock_bh();
        for (hash = 0; hash < L2TP_HASH_SIZE_2; hash++) {
-               hlist_for_each_entry(session, walk, &pn->l2tp_session_hlist[hash], global_hlist) {
+               hlist_for_each_entry_rcu(session, walk, &pn->l2tp_session_hlist[hash], global_hlist) {
                        if (!strcmp(session->ifname, ifname)) {
-                               read_unlock_bh(&pn->l2tp_session_hlist_lock);
+                               rcu_read_unlock_bh();
                                return session;
                        }
                }
        }
 
-       read_unlock_bh(&pn->l2tp_session_hlist_lock);
+       rcu_read_unlock_bh();
 
        return NULL;
 }
@@ -248,14 +249,14 @@ struct l2tp_tunnel *l2tp_tunnel_find(struct net *net, u32 tunnel_id)
        struct l2tp_tunnel *tunnel;
        struct l2tp_net *pn = l2tp_pernet(net);
 
-       read_lock_bh(&pn->l2tp_tunnel_list_lock);
-       list_for_each_entry(tunnel, &pn->l2tp_tunnel_list, list) {
+       rcu_read_lock_bh();
+       list_for_each_entry_rcu(tunnel, &pn->l2tp_tunnel_list, list) {
                if (tunnel->tunnel_id == tunnel_id) {
-                       read_unlock_bh(&pn->l2tp_tunnel_list_lock);
+                       rcu_read_unlock_bh();
                        return tunnel;
                }
        }
-       read_unlock_bh(&pn->l2tp_tunnel_list_lock);
+       rcu_read_unlock_bh();
 
        return NULL;
 }
@@ -267,15 +268,15 @@ struct l2tp_tunnel *l2tp_tunnel_find_nth(struct net *net, int nth)
        struct l2tp_tunnel *tunnel;
        int count = 0;
 
-       read_lock_bh(&pn->l2tp_tunnel_list_lock);
-       list_for_each_entry(tunnel, &pn->l2tp_tunnel_list, list) {
+       rcu_read_lock_bh();
+       list_for_each_entry_rcu(tunnel, &pn->l2tp_tunnel_list, list) {
                if (++count > nth) {
-                       read_unlock_bh(&pn->l2tp_tunnel_list_lock);
+                       rcu_read_unlock_bh();
                        return tunnel;
                }
        }
 
-       read_unlock_bh(&pn->l2tp_tunnel_list_lock);
+       rcu_read_unlock_bh();
 
        return NULL;
 }
@@ -953,7 +954,8 @@ int l2tp_xmit_core(struct l2tp_session *session, struct sk_buff *skb, size_t dat
        }
 
        /* Queue the packet to IP for output */
-       error = ip_queue_xmit(skb, 1);
+       skb->local_df = 1;
+       error = ip_queue_xmit(skb);
 
        /* Update stats */
        if (error >= 0) {
@@ -1167,9 +1169,10 @@ again:
                        if (tunnel->version != L2TP_HDR_VER_2) {
                                struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
 
-                               write_lock_bh(&pn->l2tp_session_hlist_lock);
-                               hlist_del_init(&session->global_hlist);
-                               write_unlock_bh(&pn->l2tp_session_hlist_lock);
+                               spin_lock_bh(&pn->l2tp_session_hlist_lock);
+                               hlist_del_init_rcu(&session->global_hlist);
+                               spin_unlock_bh(&pn->l2tp_session_hlist_lock);
+                               synchronize_rcu();
                        }
 
                        if (session->session_close != NULL)
@@ -1206,15 +1209,92 @@ void l2tp_tunnel_free(struct l2tp_tunnel *tunnel)
               "%s: free...\n", tunnel->name);
 
        /* Remove from tunnel list */
-       write_lock_bh(&pn->l2tp_tunnel_list_lock);
-       list_del_init(&tunnel->list);
-       write_unlock_bh(&pn->l2tp_tunnel_list_lock);
+       spin_lock_bh(&pn->l2tp_tunnel_list_lock);
+       list_del_rcu(&tunnel->list);
+       spin_unlock_bh(&pn->l2tp_tunnel_list_lock);
+       synchronize_rcu();
 
        atomic_dec(&l2tp_tunnel_count);
        kfree(tunnel);
 }
 EXPORT_SYMBOL_GPL(l2tp_tunnel_free);
 
+/* Create a socket for the tunnel, if one isn't set up by
+ * userspace. This is used for static tunnels where there is no
+ * managing L2TP daemon.
+ */
+static int l2tp_tunnel_sock_create(u32 tunnel_id, u32 peer_tunnel_id, struct l2tp_tunnel_cfg *cfg, struct socket **sockp)
+{
+       int err = -EINVAL;
+       struct sockaddr_in udp_addr;
+       struct sockaddr_l2tpip ip_addr;
+       struct socket *sock = NULL;
+
+       switch (cfg->encap) {
+       case L2TP_ENCAPTYPE_UDP:
+               err = sock_create(AF_INET, SOCK_DGRAM, 0, sockp);
+               if (err < 0)
+                       goto out;
+
+               sock = *sockp;
+
+               memset(&udp_addr, 0, sizeof(udp_addr));
+               udp_addr.sin_family = AF_INET;
+               udp_addr.sin_addr = cfg->local_ip;
+               udp_addr.sin_port = htons(cfg->local_udp_port);
+               err = kernel_bind(sock, (struct sockaddr *) &udp_addr, sizeof(udp_addr));
+               if (err < 0)
+                       goto out;
+
+               udp_addr.sin_family = AF_INET;
+               udp_addr.sin_addr = cfg->peer_ip;
+               udp_addr.sin_port = htons(cfg->peer_udp_port);
+               err = kernel_connect(sock, (struct sockaddr *) &udp_addr, sizeof(udp_addr), 0);
+               if (err < 0)
+                       goto out;
+
+               if (!cfg->use_udp_checksums)
+                       sock->sk->sk_no_check = UDP_CSUM_NOXMIT;
+
+               break;
+
+       case L2TP_ENCAPTYPE_IP:
+               err = sock_create(AF_INET, SOCK_DGRAM, IPPROTO_L2TP, sockp);
+               if (err < 0)
+                       goto out;
+
+               sock = *sockp;
+
+               memset(&ip_addr, 0, sizeof(ip_addr));
+               ip_addr.l2tp_family = AF_INET;
+               ip_addr.l2tp_addr = cfg->local_ip;
+               ip_addr.l2tp_conn_id = tunnel_id;
+               err = kernel_bind(sock, (struct sockaddr *) &ip_addr, sizeof(ip_addr));
+               if (err < 0)
+                       goto out;
+
+               ip_addr.l2tp_family = AF_INET;
+               ip_addr.l2tp_addr = cfg->peer_ip;
+               ip_addr.l2tp_conn_id = peer_tunnel_id;
+               err = kernel_connect(sock, (struct sockaddr *) &ip_addr, sizeof(ip_addr), 0);
+               if (err < 0)
+                       goto out;
+
+               break;
+
+       default:
+               goto out;
+       }
+
+out:
+       if ((err < 0) && sock) {
+               sock_release(sock);
+               *sockp = NULL;
+       }
+
+       return err;
+}
+
 int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32 peer_tunnel_id, struct l2tp_tunnel_cfg *cfg, struct l2tp_tunnel **tunnelp)
 {
        struct l2tp_tunnel *tunnel = NULL;
@@ -1225,14 +1305,21 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
        enum l2tp_encap_type encap = L2TP_ENCAPTYPE_UDP;
 
        /* Get the tunnel socket from the fd, which was opened by
-        * the userspace L2TP daemon.
+        * the userspace L2TP daemon. If not specified, create a
+        * kernel socket.
         */
-       err = -EBADF;
-       sock = sockfd_lookup(fd, &err);
-       if (!sock) {
-               printk(KERN_ERR "tunl %hu: sockfd_lookup(fd=%d) returned %d\n",
-                      tunnel_id, fd, err);
-               goto err;
+       if (fd < 0) {
+               err = l2tp_tunnel_sock_create(tunnel_id, peer_tunnel_id, cfg, &sock);
+               if (err < 0)
+                       goto err;
+       } else {
+               err = -EBADF;
+               sock = sockfd_lookup(fd, &err);
+               if (!sock) {
+                       printk(KERN_ERR "tunl %hu: sockfd_lookup(fd=%d) returned %d\n",
+                              tunnel_id, fd, err);
+                       goto err;
+               }
        }
 
        sk = sock->sk;
@@ -1310,9 +1397,10 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
 
        /* Add tunnel to our list */
        INIT_LIST_HEAD(&tunnel->list);
-       write_lock_bh(&pn->l2tp_tunnel_list_lock);
-       list_add(&tunnel->list, &pn->l2tp_tunnel_list);
-       write_unlock_bh(&pn->l2tp_tunnel_list_lock);
+       spin_lock_bh(&pn->l2tp_tunnel_list_lock);
+       list_add_rcu(&tunnel->list, &pn->l2tp_tunnel_list);
+       spin_unlock_bh(&pn->l2tp_tunnel_list_lock);
+       synchronize_rcu();
        atomic_inc(&l2tp_tunnel_count);
 
        /* Bump the reference count. The tunnel context is deleted
@@ -1325,7 +1413,10 @@ err:
        if (tunnelp)
                *tunnelp = tunnel;
 
-       if (sock)
+       /* If tunnel's socket was created by the kernel, it doesn't
+        *  have a file.
+        */
+       if (sock && sock->file)
                sockfd_put(sock);
 
        return err;
@@ -1337,13 +1428,22 @@ EXPORT_SYMBOL_GPL(l2tp_tunnel_create);
 int l2tp_tunnel_delete(struct l2tp_tunnel *tunnel)
 {
        int err = 0;
+       struct socket *sock = tunnel->sock ? tunnel->sock->sk_socket : NULL;
 
        /* Force the tunnel socket to close. This will eventually
         * cause the tunnel to be deleted via the normal socket close
         * mechanisms when userspace closes the tunnel socket.
         */
-       if ((tunnel->sock != NULL) && (tunnel->sock->sk_socket != NULL))
-               err = inet_shutdown(tunnel->sock->sk_socket, 2);
+       if (sock != NULL) {
+               err = inet_shutdown(sock, 2);
+
+               /* If the tunnel's socket was created by the kernel,
+                * close the socket here since the socket was not
+                * created by userspace.
+                */
+               if (sock->file == NULL)
+                       err = inet_release(sock);
+       }
 
        return err;
 }
@@ -1370,9 +1470,10 @@ void l2tp_session_free(struct l2tp_session *session)
                if (tunnel->version != L2TP_HDR_VER_2) {
                        struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
 
-                       write_lock_bh(&pn->l2tp_session_hlist_lock);
-                       hlist_del_init(&session->global_hlist);
-                       write_unlock_bh(&pn->l2tp_session_hlist_lock);
+                       spin_lock_bh(&pn->l2tp_session_hlist_lock);
+                       hlist_del_init_rcu(&session->global_hlist);
+                       spin_unlock_bh(&pn->l2tp_session_hlist_lock);
+                       synchronize_rcu();
                }
 
                if (session->session_id != 0)
@@ -1494,10 +1595,11 @@ struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunn
                if (tunnel->version != L2TP_HDR_VER_2) {
                        struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
 
-                       write_lock_bh(&pn->l2tp_session_hlist_lock);
-                       hlist_add_head(&session->global_hlist,
-                                      l2tp_session_id_hash_2(pn, session_id));
-                       write_unlock_bh(&pn->l2tp_session_hlist_lock);
+                       spin_lock_bh(&pn->l2tp_session_hlist_lock);
+                       hlist_add_head_rcu(&session->global_hlist,
+                                          l2tp_session_id_hash_2(pn, session_id));
+                       spin_unlock_bh(&pn->l2tp_session_hlist_lock);
+                       synchronize_rcu();
                }
 
                /* Ignore management session in session count value */
@@ -1524,12 +1626,12 @@ static __net_init int l2tp_init_net(struct net *net)
                return -ENOMEM;
 
        INIT_LIST_HEAD(&pn->l2tp_tunnel_list);
-       rwlock_init(&pn->l2tp_tunnel_list_lock);
+       spin_lock_init(&pn->l2tp_tunnel_list_lock);
 
        for (hash = 0; hash < L2TP_HASH_SIZE_2; hash++)
                INIT_HLIST_HEAD(&pn->l2tp_session_hlist[hash]);
 
-       rwlock_init(&pn->l2tp_session_hlist_lock);
+       spin_lock_init(&pn->l2tp_session_hlist_lock);
 
        err = net_assign_generic(net, l2tp_net_id, pn);
        if (err)