]> Pileus Git - ~andy/linux/blobdiff - net/ipv4/ip_tunnel.c
ipip: add x-netns support
[~andy/linux] / net / ipv4 / ip_tunnel.c
index 945734b2f2092669604b1e588ba045c66f009678..a4d9126c7b514a7f8e1dead0335023a4afc8e8b5 100644 (file)
@@ -350,7 +350,7 @@ static int ip_tunnel_bind_dev(struct net_device *dev)
                struct flowi4 fl4;
                struct rtable *rt;
 
-               rt = ip_route_output_tunnel(dev_net(dev), &fl4,
+               rt = ip_route_output_tunnel(tunnel->net, &fl4,
                                            tunnel->parms.iph.protocol,
                                            iph->daddr, iph->saddr,
                                            tunnel->parms.o_key,
@@ -365,7 +365,7 @@ static int ip_tunnel_bind_dev(struct net_device *dev)
        }
 
        if (!tdev && tunnel->parms.link)
-               tdev = __dev_get_by_index(dev_net(dev), tunnel->parms.link);
+               tdev = __dev_get_by_index(tunnel->net, tunnel->parms.link);
 
        if (tdev) {
                hlen = tdev->hard_header_len + tdev->needed_headroom;
@@ -454,15 +454,16 @@ int ip_tunnel_rcv(struct ip_tunnel *tunnel, struct sk_buff *skb,
        tstats->rx_bytes += skb->len;
        u64_stats_update_end(&tstats->syncp);
 
-       if (tunnel->net != dev_net(tunnel->dev))
-               skb_scrub_packet(skb);
-
        if (tunnel->dev->type == ARPHRD_ETHER) {
                skb->protocol = eth_type_trans(skb, tunnel->dev);
                skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);
        } else {
                skb->dev = tunnel->dev;
        }
+
+       if (!net_eq(tunnel->net, dev_net(tunnel->dev)))
+               skb_scrub_packet(skb);
+
        gro_cells_receive(&tunnel->gro_cells, skb);
        return 0;
 
@@ -476,7 +477,7 @@ static int tnl_update_pmtu(struct net_device *dev, struct sk_buff *skb,
                            struct rtable *rt, __be16 df)
 {
        struct ip_tunnel *tunnel = netdev_priv(dev);
-       int pkt_size = skb->len - tunnel->hlen;
+       int pkt_size = skb->len - tunnel->hlen - dev->hard_header_len;
        int mtu;
 
        if (df)
@@ -613,7 +614,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
                goto tx_error;
        }
 
-       if (tunnel->net != dev_net(dev))
+       if (!net_eq(tunnel->net, dev_net(dev)))
                skb_scrub_packet(skb);
 
        if (tunnel->err_count > 0) {
@@ -653,7 +654,7 @@ void ip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev,
                }
        }
 
-       err = iptunnel_xmit(dev_net(dev), rt, skb,
+       err = iptunnel_xmit(tunnel->net, rt, skb,
                            fl4.saddr, fl4.daddr, protocol,
                            ip_tunnel_ecn_encap(tos, inner_iph, skb), ttl, df);
        iptunnel_xmit_stats(err, &dev->stats, dev->tstats);
@@ -820,11 +821,10 @@ static void ip_tunnel_dev_free(struct net_device *dev)
 
 void ip_tunnel_dellink(struct net_device *dev, struct list_head *head)
 {
-       struct net *net = dev_net(dev);
        struct ip_tunnel *tunnel = netdev_priv(dev);
        struct ip_tunnel_net *itn;
 
-       itn = net_generic(net, tunnel->ip_tnl_net_id);
+       itn = net_generic(tunnel->net, tunnel->ip_tnl_net_id);
 
        if (itn->fb_tunnel_dev != dev) {
                ip_tunnel_del(netdev_priv(dev));
@@ -838,56 +838,70 @@ int ip_tunnel_init_net(struct net *net, int ip_tnl_net_id,
 {
        struct ip_tunnel_net *itn = net_generic(net, ip_tnl_net_id);
        struct ip_tunnel_parm parms;
+       unsigned int i;
 
-       itn->tunnels = kzalloc(IP_TNL_HASH_SIZE * sizeof(struct hlist_head), GFP_KERNEL);
-       if (!itn->tunnels)
-               return -ENOMEM;
+       for (i = 0; i < IP_TNL_HASH_SIZE; i++)
+               INIT_HLIST_HEAD(&itn->tunnels[i]);
 
        if (!ops) {
                itn->fb_tunnel_dev = NULL;
                return 0;
        }
+
        memset(&parms, 0, sizeof(parms));
        if (devname)
                strlcpy(parms.name, devname, IFNAMSIZ);
 
        rtnl_lock();
        itn->fb_tunnel_dev = __ip_tunnel_create(net, ops, &parms);
+       /* FB netdevice is special: we have one, and only one per netns.
+        * Allowing to move it to another netns is clearly unsafe.
+        */
+       itn->fb_tunnel_dev->features |= NETIF_F_NETNS_LOCAL;
        rtnl_unlock();
-       if (IS_ERR(itn->fb_tunnel_dev)) {
-               kfree(itn->tunnels);
+
+       if (IS_ERR(itn->fb_tunnel_dev))
                return PTR_ERR(itn->fb_tunnel_dev);
-       }
 
        return 0;
 }
 EXPORT_SYMBOL_GPL(ip_tunnel_init_net);
 
-static void ip_tunnel_destroy(struct ip_tunnel_net *itn, struct list_head *head)
+static void ip_tunnel_destroy(struct ip_tunnel_net *itn, struct list_head *head,
+                             struct rtnl_link_ops *ops)
 {
+       struct net *net = dev_net(itn->fb_tunnel_dev);
+       struct net_device *dev, *aux;
        int h;
 
+       for_each_netdev_safe(net, dev, aux)
+               if (dev->rtnl_link_ops == ops)
+                       unregister_netdevice_queue(dev, head);
+
        for (h = 0; h < IP_TNL_HASH_SIZE; h++) {
                struct ip_tunnel *t;
                struct hlist_node *n;
                struct hlist_head *thead = &itn->tunnels[h];
 
                hlist_for_each_entry_safe(t, n, thead, hash_node)
-                       unregister_netdevice_queue(t->dev, head);
+                       /* If dev is in the same netns, it has already
+                        * been added to the list by the previous loop.
+                        */
+                       if (!net_eq(dev_net(t->dev), net))
+                               unregister_netdevice_queue(t->dev, head);
        }
        if (itn->fb_tunnel_dev)
                unregister_netdevice_queue(itn->fb_tunnel_dev, head);
 }
 
-void ip_tunnel_delete_net(struct ip_tunnel_net *itn)
+void ip_tunnel_delete_net(struct ip_tunnel_net *itn, struct rtnl_link_ops *ops)
 {
        LIST_HEAD(list);
 
        rtnl_lock();
-       ip_tunnel_destroy(itn, &list);
+       ip_tunnel_destroy(itn, &list, ops);
        unregister_netdevice_many(&list);
        rtnl_unlock();
-       kfree(itn->tunnels);
 }
 EXPORT_SYMBOL_GPL(ip_tunnel_delete_net);
 
@@ -929,23 +943,21 @@ EXPORT_SYMBOL_GPL(ip_tunnel_newlink);
 int ip_tunnel_changelink(struct net_device *dev, struct nlattr *tb[],
                         struct ip_tunnel_parm *p)
 {
-       struct ip_tunnel *t, *nt;
-       struct net *net = dev_net(dev);
+       struct ip_tunnel *t;
        struct ip_tunnel *tunnel = netdev_priv(dev);
+       struct net *net = tunnel->net;
        struct ip_tunnel_net *itn = net_generic(net, tunnel->ip_tnl_net_id);
 
        if (dev == itn->fb_tunnel_dev)
                return -EINVAL;
 
-       nt = netdev_priv(dev);
-
        t = ip_tunnel_find(itn, p, dev->type);
 
        if (t) {
                if (t->dev != dev)
                        return -EEXIST;
        } else {
-               t = nt;
+               t = tunnel;
 
                if (dev->type != ARPHRD_ETHER) {
                        unsigned int nflags = 0;
@@ -984,6 +996,7 @@ int ip_tunnel_init(struct net_device *dev)
        }
 
        tunnel->dev = dev;
+       tunnel->net = dev_net(dev);
        strcpy(tunnel->parms.name, dev->name);
        iph->version            = 4;
        iph->ihl                = 5;
@@ -994,8 +1007,8 @@ EXPORT_SYMBOL_GPL(ip_tunnel_init);
 
 void ip_tunnel_uninit(struct net_device *dev)
 {
-       struct net *net = dev_net(dev);
        struct ip_tunnel *tunnel = netdev_priv(dev);
+       struct net *net = tunnel->net;
        struct ip_tunnel_net *itn;
 
        itn = net_generic(net, tunnel->ip_tnl_net_id);