]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - net/core/dev.c
[NET]: Implement network device movement between namespaces
[mirror_ubuntu-bionic-kernel.git] / net / core / dev.c
index 520ef7b20862ca208c66b0b3ab3b4b8f62cf21fe..215b8e97690aaab77f82dafeb09bb8391032b495 100644 (file)
@@ -208,6 +208,34 @@ static inline struct hlist_head *dev_index_hash(struct net *net, int ifindex)
        return &net->dev_index_head[ifindex & ((1 << NETDEV_HASHBITS) - 1)];
 }
 
+/* Device list insertion */
+static int list_netdevice(struct net_device *dev)
+{
+       struct net *net = dev->nd_net;
+
+       ASSERT_RTNL();
+
+       write_lock_bh(&dev_base_lock);
+       list_add_tail(&dev->dev_list, &net->dev_base_head);
+       hlist_add_head(&dev->name_hlist, dev_name_hash(net, dev->name));
+       hlist_add_head(&dev->index_hlist, dev_index_hash(net, dev->ifindex));
+       write_unlock_bh(&dev_base_lock);
+       return 0;
+}
+
+/* Device list removal */
+static void unlist_netdevice(struct net_device *dev)
+{
+       ASSERT_RTNL();
+
+       /* Unlink dev from the device chain */
+       write_lock_bh(&dev_base_lock);
+       list_del(&dev->dev_list);
+       hlist_del(&dev->name_hlist);
+       hlist_del(&dev->index_hlist);
+       write_unlock_bh(&dev_base_lock);
+}
+
 /*
  *     Our notifier list
  */
@@ -3571,12 +3599,8 @@ int register_netdevice(struct net_device *dev)
        set_bit(__LINK_STATE_PRESENT, &dev->state);
 
        dev_init_scheduler(dev);
-       write_lock_bh(&dev_base_lock);
-       list_add_tail(&dev->dev_list, &net->dev_base_head);
-       hlist_add_head(&dev->name_hlist, head);
-       hlist_add_head(&dev->index_hlist, dev_index_hash(net, dev->ifindex));
        dev_hold(dev);
-       write_unlock_bh(&dev_base_lock);
+       list_netdevice(dev);
 
        /* Notify protocols, that a new device appeared. */
        ret = raw_notifier_call_chain(&netdev_chain, NETDEV_REGISTER, dev);
@@ -3883,11 +3907,7 @@ void unregister_netdevice(struct net_device *dev)
                dev_close(dev);
 
        /* And unlink it from device chain. */
-       write_lock_bh(&dev_base_lock);
-       list_del(&dev->dev_list);
-       hlist_del(&dev->name_hlist);
-       hlist_del(&dev->index_hlist);
-       write_unlock_bh(&dev_base_lock);
+       unlist_netdevice(dev);
 
        dev->reg_state = NETREG_UNREGISTERING;
 
@@ -3945,6 +3965,122 @@ void unregister_netdev(struct net_device *dev)
 
 EXPORT_SYMBOL(unregister_netdev);
 
+/**
+ *     dev_change_net_namespace - move device to different nethost namespace
+ *     @dev: device
+ *     @net: network namespace
+ *     @pat: If not NULL name pattern to try if the current device name
+ *           is already taken in the destination network namespace.
+ *
+ *     This function shuts down a device interface and moves it
+ *     to a new network namespace. On success 0 is returned, on
+ *     a failure a netagive errno code is returned.
+ *
+ *     Callers must hold the rtnl semaphore.
+ */
+
+int dev_change_net_namespace(struct net_device *dev, struct net *net, const char *pat)
+{
+       char buf[IFNAMSIZ];
+       const char *destname;
+       int err;
+
+       ASSERT_RTNL();
+
+       /* Don't allow namespace local devices to be moved. */
+       err = -EINVAL;
+       if (dev->features & NETIF_F_NETNS_LOCAL)
+               goto out;
+
+       /* Ensure the device has been registrered */
+       err = -EINVAL;
+       if (dev->reg_state != NETREG_REGISTERED)
+               goto out;
+
+       /* Get out if there is nothing todo */
+       err = 0;
+       if (dev->nd_net == net)
+               goto out;
+
+       /* Pick the destination device name, and ensure
+        * we can use it in the destination network namespace.
+        */
+       err = -EEXIST;
+       destname = dev->name;
+       if (__dev_get_by_name(net, destname)) {
+               /* We get here if we can't use the current device name */
+               if (!pat)
+                       goto out;
+               if (!dev_valid_name(pat))
+                       goto out;
+               if (strchr(pat, '%')) {
+                       if (__dev_alloc_name(net, pat, buf) < 0)
+                               goto out;
+                       destname = buf;
+               } else
+                       destname = pat;
+               if (__dev_get_by_name(net, destname))
+                       goto out;
+       }
+
+       /*
+        * And now a mini version of register_netdevice unregister_netdevice.
+        */
+
+       /* If device is running close it first. */
+       if (dev->flags & IFF_UP)
+               dev_close(dev);
+
+       /* And unlink it from device chain */
+       err = -ENODEV;
+       unlist_netdevice(dev);
+
+       synchronize_net();
+
+       /* Shutdown queueing discipline. */
+       dev_shutdown(dev);
+
+       /* Notify protocols, that we are about to destroy
+          this device. They should clean all the things.
+       */
+       call_netdevice_notifiers(NETDEV_UNREGISTER, dev);
+
+       /*
+        *      Flush the unicast and multicast chains
+        */
+       dev_addr_discard(dev);
+
+       /* Actually switch the network namespace */
+       dev->nd_net = net;
+
+       /* Assign the new device name */
+       if (destname != dev->name)
+               strcpy(dev->name, destname);
+
+       /* If there is an ifindex conflict assign a new one */
+       if (__dev_get_by_index(net, dev->ifindex)) {
+               int iflink = (dev->iflink == dev->ifindex);
+               dev->ifindex = dev_new_index(net);
+               if (iflink)
+                       dev->iflink = dev->ifindex;
+       }
+
+       /* Fixup sysfs */
+       err = device_rename(&dev->dev, dev->name);
+       BUG_ON(err);
+
+       /* Add the device back in the hashes */
+       list_netdevice(dev);
+
+       /* Notify protocols, that a new device appeared. */
+       call_netdevice_notifiers(NETDEV_REGISTER, dev);
+
+       synchronize_net();
+       err = 0;
+out:
+       return err;
+}
+
 static int dev_cpu_callback(struct notifier_block *nfb,
                            unsigned long action,
                            void *ocpu)
@@ -4177,6 +4313,36 @@ static struct pernet_operations netdev_net_ops = {
        .exit = netdev_exit,
 };
 
+static void default_device_exit(struct net *net)
+{
+       struct net_device *dev, *next;
+       /*
+        * Push all migratable of the network devices back to the
+        * initial network namespace
+        */
+       rtnl_lock();
+       for_each_netdev_safe(net, dev, next) {
+               int err;
+
+               /* Ignore unmoveable devices (i.e. loopback) */
+               if (dev->features & NETIF_F_NETNS_LOCAL)
+                       continue;
+
+               /* Push remaing network devices to init_net */
+               err = dev_change_net_namespace(dev, &init_net, "dev%d");
+               if (err) {
+                       printk(KERN_WARNING "%s: failed to move %s to init_net: %d\n",
+                               __func__, dev->name, err);
+                       unregister_netdevice(dev);
+               }
+       }
+       rtnl_unlock();
+}
+
+static struct pernet_operations default_device_ops = {
+       .exit = default_device_exit,
+};
+
 /*
  *     Initialize the DEV module. At boot time this walks the device list and
  *     unhooks any devices that fail to initialise (normally hardware not
@@ -4207,6 +4373,9 @@ static int __init net_dev_init(void)
        if (register_pernet_subsys(&netdev_net_ops))
                goto out;
 
+       if (register_pernet_device(&default_device_ops))
+               goto out;
+
        /*
         *      Initialise the packet receive queues.
         */