l2tp: don't use inet_shutdown on ppp session destroy
[pandora-kernel.git] / net / l2tp / l2tp_eth.c
index d2726a7..35c8973 100644 (file)
@@ -39,25 +39,13 @@ struct l2tp_eth {
        struct net_device       *dev;
        struct sock             *tunnel_sock;
        struct l2tp_session     *session;
-       struct list_head        list;
 };
 
 /* via l2tp_session_priv() */
 struct l2tp_eth_sess {
-       struct net_device       *dev;
-};
-
-/* per-net private data for this module */
-static unsigned int l2tp_eth_net_id;
-struct l2tp_eth_net {
-       struct list_head l2tp_eth_dev_list;
-       spinlock_t l2tp_eth_lock;
+       struct net_device __rcu *dev;
 };
 
-static inline struct l2tp_eth_net *l2tp_eth_pernet(struct net *net)
-{
-       return net_generic(net, l2tp_eth_net_id);
-}
 
 static int l2tp_eth_dev_init(struct net_device *dev)
 {
@@ -73,12 +61,13 @@ static int l2tp_eth_dev_init(struct net_device *dev)
 static void l2tp_eth_dev_uninit(struct net_device *dev)
 {
        struct l2tp_eth *priv = netdev_priv(dev);
-       struct l2tp_eth_net *pn = l2tp_eth_pernet(dev_net(dev));
+       struct l2tp_eth_sess *spriv;
 
-       spin_lock(&pn->l2tp_eth_lock);
-       list_del_init(&priv->list);
-       spin_unlock(&pn->l2tp_eth_lock);
-       dev_put(dev);
+       spriv = l2tp_session_priv(priv->session);
+       RCU_INIT_POINTER(spriv->dev, NULL);
+       /* No need for synchronize_net() here. We're called by
+        * unregister_netdev*(), which does the synchronisation for us.
+        */
 }
 
 static int l2tp_eth_dev_xmit(struct sk_buff *skb, struct net_device *dev)
@@ -111,7 +100,7 @@ static void l2tp_eth_dev_setup(struct net_device *dev)
 static void l2tp_eth_dev_recv(struct l2tp_session *session, struct sk_buff *skb, int data_len)
 {
        struct l2tp_eth_sess *spriv = l2tp_session_priv(session);
-       struct net_device *dev = spriv->dev;
+       struct net_device *dev;
 
        if (session->debug & L2TP_MSG_DATA) {
                unsigned int length;
@@ -132,7 +121,7 @@ static void l2tp_eth_dev_recv(struct l2tp_session *session, struct sk_buff *skb,
                printk("\n");
        }
 
-       if (!pskb_may_pull(skb, sizeof(ETH_HLEN)))
+       if (!pskb_may_pull(skb, ETH_HLEN))
                goto error;
 
        secpath_reset(skb);
@@ -143,14 +132,22 @@ static void l2tp_eth_dev_recv(struct l2tp_session *session, struct sk_buff *skb,
        skb_dst_drop(skb);
        nf_reset(skb);
 
+       rcu_read_lock();
+       dev = rcu_dereference(spriv->dev);
+       if (!dev)
+               goto error_rcu;
+
        if (dev_forward_skb(dev, skb) == NET_RX_SUCCESS) {
                dev->stats.rx_packets++;
                dev->stats.rx_bytes += data_len;
        } else
                dev->stats.rx_errors++;
+       rcu_read_unlock();
 
        return;
 
+error_rcu:
+       rcu_read_unlock();
 error:
        dev->stats.rx_errors++;
        kfree_skb(skb);
@@ -163,10 +160,15 @@ static void l2tp_eth_delete(struct l2tp_session *session)
 
        if (session) {
                spriv = l2tp_session_priv(session);
-               dev = spriv->dev;
+
+               rtnl_lock();
+               dev = rtnl_dereference(spriv->dev);
                if (dev) {
-                       unregister_netdev(dev);
-                       spriv->dev = NULL;
+                       unregister_netdevice(dev);
+                       rtnl_unlock();
+                       module_put(THIS_MODULE);
+               } else {
+                       rtnl_unlock();
                }
        }
 }
@@ -176,41 +178,40 @@ static void l2tp_eth_show(struct seq_file *m, void *arg)
 {
        struct l2tp_session *session = arg;
        struct l2tp_eth_sess *spriv = l2tp_session_priv(session);
-       struct net_device *dev = spriv->dev;
+       struct net_device *dev;
+
+       rcu_read_lock();
+       dev = rcu_dereference(spriv->dev);
+       if (!dev) {
+               rcu_read_unlock();
+               return;
+       }
+       dev_hold(dev);
+       rcu_read_unlock();
 
        seq_printf(m, "   interface %s\n", dev->name);
+
+       dev_put(dev);
 }
 #endif
 
-static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 peer_session_id, struct l2tp_session_cfg *cfg)
+static int l2tp_eth_create(struct net *net, struct l2tp_tunnel *tunnel,
+                          u32 session_id, u32 peer_session_id,
+                          struct l2tp_session_cfg *cfg)
 {
        struct net_device *dev;
        char name[IFNAMSIZ];
-       struct l2tp_tunnel *tunnel;
        struct l2tp_session *session;
        struct l2tp_eth *priv;
        struct l2tp_eth_sess *spriv;
        int rc;
-       struct l2tp_eth_net *pn;
-
-       tunnel = l2tp_tunnel_find(net, tunnel_id);
-       if (!tunnel) {
-               rc = -ENODEV;
-               goto out;
-       }
-
-       session = l2tp_session_find(net, tunnel, session_id);
-       if (session) {
-               rc = -EEXIST;
-               goto out;
-       }
 
        if (cfg->ifname) {
                dev = dev_get_by_name(net, cfg->ifname);
                if (dev) {
                        dev_put(dev);
                        rc = -EEXIST;
-                       goto out;
+                       goto err;
                }
                strlcpy(name, cfg->ifname, IFNAMSIZ);
        } else
@@ -218,15 +219,15 @@ static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 p
 
        session = l2tp_session_create(sizeof(*spriv), tunnel, session_id,
                                      peer_session_id, cfg);
-       if (!session) {
-               rc = -ENOMEM;
-               goto out;
+       if (IS_ERR(session)) {
+               rc = PTR_ERR(session);
+               goto err;
        }
 
        dev = alloc_netdev(sizeof(*priv), name, l2tp_eth_dev_setup);
        if (!dev) {
                rc = -ENOMEM;
-               goto out_del_session;
+               goto err_sess;
        }
 
        dev_net_set(dev, net);
@@ -238,7 +239,6 @@ static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 p
        priv = netdev_priv(dev);
        priv->dev = dev;
        priv->session = session;
-       INIT_LIST_HEAD(&priv->list);
 
        priv->tunnel_sock = tunnel->sock;
        session->recv_skb = l2tp_eth_dev_recv;
@@ -248,46 +248,50 @@ static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 p
 #endif
 
        spriv = l2tp_session_priv(session);
-       spriv->dev = dev;
 
-       rc = register_netdev(dev);
-       if (rc < 0)
-               goto out_del_dev;
+       l2tp_session_inc_refcount(session);
 
-       /* Must be done after register_netdev() */
-       strlcpy(session->ifname, dev->name, IFNAMSIZ);
+       rtnl_lock();
 
-       dev_hold(dev);
-       pn = l2tp_eth_pernet(dev_net(dev));
-       spin_lock(&pn->l2tp_eth_lock);
-       list_add(&priv->list, &pn->l2tp_eth_dev_list);
-       spin_unlock(&pn->l2tp_eth_lock);
+       /* Register both device and session while holding the rtnl lock. This
+        * ensures that l2tp_eth_delete() will see that there's a device to
+        * unregister, even if it happened to run before we assign spriv->dev.
+        */
+       rc = l2tp_session_register(session, tunnel);
+       if (rc < 0) {
+               rtnl_unlock();
+               goto err_sess_dev;
+       }
 
-       return 0;
+       rc = register_netdevice(dev);
+       if (rc < 0) {
+               rtnl_unlock();
+               l2tp_session_delete(session);
+               l2tp_session_dec_refcount(session);
+               free_netdev(dev);
 
-out_del_dev:
-       free_netdev(dev);
-out_del_session:
-       l2tp_session_delete(session);
-out:
-       return rc;
-}
+               return rc;
+       }
 
-static __net_init int l2tp_eth_init_net(struct net *net)
-{
-       struct l2tp_eth_net *pn = net_generic(net, l2tp_eth_net_id);
+       strlcpy(session->ifname, dev->name, IFNAMSIZ);
+       rcu_assign_pointer(spriv->dev, dev);
 
-       INIT_LIST_HEAD(&pn->l2tp_eth_dev_list);
-       spin_lock_init(&pn->l2tp_eth_lock);
+       rtnl_unlock();
+
+       l2tp_session_dec_refcount(session);
+
+       __module_get(THIS_MODULE);
 
        return 0;
-}
 
-static struct pernet_operations l2tp_eth_net_ops = {
-       .init = l2tp_eth_init_net,
-       .id   = &l2tp_eth_net_id,
-       .size = sizeof(struct l2tp_eth_net),
-};
+err_sess_dev:
+       l2tp_session_dec_refcount(session);
+       free_netdev(dev);
+err_sess:
+       kfree(session);
+err:
+       return rc;
+}
 
 
 static const struct l2tp_nl_cmd_ops l2tp_eth_nl_cmd_ops = {
@@ -302,25 +306,18 @@ static int __init l2tp_eth_init(void)
 
        err = l2tp_nl_register_ops(L2TP_PWTYPE_ETH, &l2tp_eth_nl_cmd_ops);
        if (err)
-               goto out;
-
-       err = register_pernet_device(&l2tp_eth_net_ops);
-       if (err)
-               goto out_unreg;
+               goto err;
 
        printk(KERN_INFO "L2TP ethernet pseudowire support (L2TPv3)\n");
 
        return 0;
 
-out_unreg:
-       l2tp_nl_unregister_ops(L2TP_PWTYPE_ETH);
-out:
+err:
        return err;
 }
 
 static void __exit l2tp_eth_exit(void)
 {
-       unregister_pernet_device(&l2tp_eth_net_ops);
        l2tp_nl_unregister_ops(L2TP_PWTYPE_ETH);
 }