chiark / gitweb /
nspawn: add ipvlan support
[elogind.git] / src / nspawn / nspawn.c
index f502500308c42c4fb0405be4c011d899f6fdb861..588a8ae8ac8c4ff230d704eb25a7ef0342a35184 100644 (file)
@@ -43,6 +43,8 @@
 #include <linux/veth.h>
 #include <sys/personality.h>
 #include <linux/loop.h>
+#include <poll.h>
+#include <sys/file.h>
 
 #ifdef HAVE_SELINUX
 #include <selinux/selinux.h>
@@ -178,6 +180,7 @@ static bool arg_register = true;
 static bool arg_keep_unit = false;
 static char **arg_network_interfaces = NULL;
 static char **arg_network_macvlan = NULL;
+static char **arg_network_ipvlan = NULL;
 static bool arg_network_veth = false;
 static const char *arg_network_bridge = NULL;
 static unsigned long arg_personality = 0xffffffffLU;
@@ -209,6 +212,9 @@ static void help(void) {
                "     --network-macvlan=INTERFACE\n"
                "                            Create a macvlan network interface based on an\n"
                "                            existing network interface to the container\n"
+               "     --network-ipvlan=INTERFACE\n"
+               "                            Create a ipvlan network interface based on an\n"
+               "                            existing network interface to the container\n"
                "  -n --network-veth         Add a virtual ethernet connection between host\n"
                "                            and container\n"
                "     --network-bridge=INTERFACE\n"
@@ -283,6 +289,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_KEEP_UNIT,
                 ARG_NETWORK_INTERFACE,
                 ARG_NETWORK_MACVLAN,
+                ARG_NETWORK_IPVLAN,
                 ARG_NETWORK_BRIDGE,
                 ARG_PERSONALITY,
                 ARG_VOLATILE,
@@ -317,6 +324,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "keep-unit",             no_argument,       NULL, ARG_KEEP_UNIT         },
                 { "network-interface",     required_argument, NULL, ARG_NETWORK_INTERFACE },
                 { "network-macvlan",       required_argument, NULL, ARG_NETWORK_MACVLAN   },
+                { "network-ipvlan",        required_argument, NULL, ARG_NETWORK_IPVLAN    },
                 { "network-veth",          no_argument,       NULL, 'n'                   },
                 { "network-bridge",        required_argument, NULL, ARG_NETWORK_BRIDGE    },
                 { "personality",           required_argument, NULL, ARG_PERSONALITY       },
@@ -399,6 +407,13 @@ static int parse_argv(int argc, char *argv[]) {
                         if (strv_extend(&arg_network_macvlan, optarg) < 0)
                                 return log_oom();
 
+                        arg_private_network = true;
+                        break;
+
+                case ARG_NETWORK_IPVLAN:
+                        if (strv_extend(&arg_network_ipvlan, optarg) < 0)
+                                return log_oom();
+
                         /* fall through */
 
                 case ARG_PRIVATE_NETWORK:
@@ -1029,7 +1044,7 @@ static int mount_cgroup(const char *dest) {
                                 return r;
 
                         if (symlink(combined, target) < 0)
-                                return log_error_errno(errno, "Failed to create symlink for combined hiearchy: %m");
+                                return log_error_errno(errno, "Failed to create symlink for combined hierarchy: %m");
                 }
         }
 
@@ -2379,6 +2394,87 @@ static int setup_macvlan(pid_t pid) {
         return 0;
 }
 
+static int setup_ipvlan(pid_t pid) {
+        _cleanup_udev_unref_ struct udev *udev = NULL;
+        _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
+        char **i;
+        int r;
+
+        if (!arg_private_network)
+                return 0;
+
+        if (strv_isempty(arg_network_ipvlan))
+                return 0;
+
+        r = sd_rtnl_open(&rtnl, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to netlink: %m");
+
+        udev = udev_new();
+        if (!udev) {
+                log_error("Failed to connect to udev.");
+                return -ENOMEM;
+        }
+
+        STRV_FOREACH(i, arg_network_ipvlan) {
+                _cleanup_rtnl_message_unref_ sd_rtnl_message *m = NULL;
+                _cleanup_free_ char *n = NULL;
+                int ifi;
+
+                ifi = parse_interface(udev, *i);
+                if (ifi < 0)
+                        return ifi;
+
+                r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate netlink message: %m");
+
+                r = sd_rtnl_message_append_u32(m, IFLA_LINK, ifi);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add netlink interface index: %m");
+
+                n = strappend("iv-", *i);
+                if (!n)
+                        return log_oom();
+
+                strshorten(n, IFNAMSIZ-1);
+
+                r = sd_rtnl_message_append_string(m, IFLA_IFNAME, n);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add netlink interface name: %m");
+
+                r = sd_rtnl_message_append_u32(m, IFLA_NET_NS_PID, pid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add netlink namespace field: %m");
+
+                r = sd_rtnl_message_open_container(m, IFLA_LINKINFO);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to open netlink container: %m");
+
+                r = sd_rtnl_message_open_container_union(m, IFLA_INFO_DATA, "ipvlan");
+                if (r < 0)
+                        return log_error_errno(r, "Failed to open netlink container: %m");
+
+                r = sd_rtnl_message_append_u16(m, IFLA_IPVLAN_MODE, IPVLAN_MODE_L2);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add ipvlan mode: %m");
+
+                r = sd_rtnl_message_close_container(m);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to close netlink container: %m");
+
+                r = sd_rtnl_message_close_container(m);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to close netlink container: %m");
+
+                r = sd_rtnl_call(rtnl, m, 0, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add new ipvlan interfaces: %m");
+        }
+
+        return 0;
+}
+
 static int setup_seccomp(void) {
 
 #ifdef HAVE_SECCOMP
@@ -2557,6 +2653,70 @@ static int setup_image(char **device_path, int *loop_nr) {
         return r;
 }
 
+static int wait_for_block_device(struct udev *udev, dev_t devnum, struct udev_device **ret) {
+        _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL;
+        int r;
+
+        assert(udev);
+        assert(ret);
+
+        for (;;) {
+                _cleanup_udev_device_unref_ struct udev_device *d = NULL;
+                struct pollfd pfd = {
+                        .events = POLLIN
+                };
+
+                d = udev_device_new_from_devnum(udev, 'b', devnum);
+                if (!d)
+                        return log_oom();
+
+                r = udev_device_get_is_initialized(d);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to check if device is initialized: %m");
+                if (r > 0) {
+                        *ret = d;
+                        d = NULL;
+                        return 0;
+                }
+                d = udev_device_unref(d);
+
+                if (!monitor) {
+                        monitor = udev_monitor_new_from_netlink(udev, "udev");
+                        if (!monitor)
+                                return log_oom();
+
+                        r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "block", NULL);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to add block match: %m");
+
+                        r = udev_monitor_enable_receiving(monitor);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to turn on monitor: %m");
+
+                        continue;
+                }
+
+                pfd.fd = udev_monitor_get_fd(monitor);
+                if (pfd.fd < 0)
+                        return log_error_errno(r, "Failed to get udev monitor fd: %m");
+
+                r = poll(&pfd, 1, -1);
+                if (r < 0)
+                        return log_error_errno(errno, "Failed to wait for device initialization: %m");
+
+                d = udev_monitor_receive_device(monitor);
+        }
+
+        return 0;
+}
+
+#define PARTITION_TABLE_BLURB \
+        "Note that the disk image needs to either contain only a single MBR partition of\n" \
+        "type 0x83 that is marked bootable, or a sinlge GPT partition of type" \
+        "0FC63DAF-8483-4772-8E79-3D69D8477DE4 or follow\n" \
+        "    http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/\n" \
+        "to be bootable with systemd-nspawn."
+
 static int dissect_image(
                 int fd,
                 char **root_device, bool *root_device_rw,
@@ -2572,18 +2732,18 @@ static int dissect_image(
 #ifdef GPT_ROOT_SECONDARY
         int secondary_root_nr = -1;
 #endif
-
-        _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL;
+        _cleanup_free_ char *home = NULL, *root = NULL, *secondary_root = NULL, *srv = NULL, *generic = NULL;
         _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL;
         _cleanup_udev_device_unref_ struct udev_device *d = NULL;
         _cleanup_blkid_free_probe_ blkid_probe b = NULL;
         _cleanup_udev_unref_ struct udev *udev = NULL;
         struct udev_list_entry *first, *item;
-        bool home_rw = true, root_rw = true, secondary_root_rw = true, srv_rw = true;
+        bool home_rw = true, root_rw = true, secondary_root_rw = true, srv_rw = true, generic_rw = true;
         const char *pttype = NULL;
         blkid_partlist pl;
         struct stat st;
         int r;
+        bool is_gpt, is_mbr, multiple_generic = false;
 
         assert(fd >= 0);
         assert(root_device);
@@ -2612,8 +2772,9 @@ static int dissect_image(
         errno = 0;
         r = blkid_do_safeprobe(b);
         if (r == -2 || r == 1) {
-                log_error("Failed to identify any partition table on %s.\n"
-                          "Note that the disk image needs to follow http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/ to be supported by systemd-nspawn.", arg_image);
+                log_error("Failed to identify any partition table on\n"
+                          "    %s\n"
+                          PARTITION_TABLE_BLURB, arg_image);
                 return -EINVAL;
         } else if (r != 0) {
                 if (errno == 0)
@@ -2623,9 +2784,14 @@ static int dissect_image(
         }
 
         blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL);
-        if (!streq_ptr(pttype, "gpt")) {
-                log_error("Image %s does not carry a GUID Partition Table.\n"
-                          "Note that the disk image needs to follow http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/ to be supported by systemd-nspawn.", arg_image);
+
+        is_gpt = streq_ptr(pttype, "gpt");
+        is_mbr = streq_ptr(pttype, "dos");
+
+        if (!is_gpt && !is_mbr) {
+                log_error("No GPT or MBR partition table discovered on\n"
+                          "    %s\n"
+                          PARTITION_TABLE_BLURB, arg_image);
                 return -EINVAL;
         }
 
@@ -2646,9 +2812,9 @@ static int dissect_image(
         if (fstat(fd, &st) < 0)
                 return log_error_errno(errno, "Failed to stat block device: %m");
 
-        d = udev_device_new_from_devnum(udev, 'b', st.st_rdev);
-        if (!d)
-                return log_oom();
+        r = wait_for_block_device(udev, st.st_rdev, &d);
+        if (r < 0)
+                return r;
 
         e = udev_enumerate_new(udev);
         if (!e)
@@ -2665,9 +2831,8 @@ static int dissect_image(
         first = udev_enumerate_get_list_entry(e);
         udev_list_entry_foreach(item, first) {
                 _cleanup_udev_device_unref_ struct udev_device *q;
-                const char *stype, *node;
+                const char *node;
                 unsigned long long flags;
-                sd_id128_t type_id;
                 blkid_partition pp;
                 dev_t qn;
                 int nr;
@@ -2698,82 +2863,110 @@ static int dissect_image(
                         continue;
 
                 flags = blkid_partition_get_flags(pp);
-                if (flags & GPT_FLAG_NO_AUTO)
-                        continue;
 
                 nr = blkid_partition_get_partno(pp);
                 if (nr < 0)
                         continue;
 
-                stype = blkid_partition_get_type_string(pp);
-                if (!stype)
-                        continue;
+                if (is_gpt) {
+                        sd_id128_t type_id;
+                        const char *stype;
 
-                if (sd_id128_from_string(stype, &type_id) < 0)
-                        continue;
+                        if (flags & GPT_FLAG_NO_AUTO)
+                                continue;
 
-                if (sd_id128_equal(type_id, GPT_HOME)) {
+                        stype = blkid_partition_get_type_string(pp);
+                        if (!stype)
+                                continue;
 
-                        if (home && nr >= home_nr)
+                        if (sd_id128_from_string(stype, &type_id) < 0)
                                 continue;
 
-                        home_nr = nr;
-                        home_rw = !(flags & GPT_FLAG_READ_ONLY);
+                        if (sd_id128_equal(type_id, GPT_HOME)) {
 
-                        free(home);
-                        home = strdup(node);
-                        if (!home)
-                                return log_oom();
-                } else if (sd_id128_equal(type_id, GPT_SRV)) {
+                                if (home && nr >= home_nr)
+                                        continue;
 
-                        if (srv && nr >= srv_nr)
-                                continue;
+                                home_nr = nr;
+                                home_rw = !(flags & GPT_FLAG_READ_ONLY);
+
+                                r = free_and_strdup(&home, node);
+                                if (r < 0)
+                                        return log_oom();
 
-                        srv_nr = nr;
-                        srv_rw = !(flags & GPT_FLAG_READ_ONLY);
+                        } else if (sd_id128_equal(type_id, GPT_SRV)) {
 
-                        free(srv);
-                        srv = strdup(node);
-                        if (!srv)
-                                return log_oom();
-                }
+                                if (srv && nr >= srv_nr)
+                                        continue;
+
+                                srv_nr = nr;
+                                srv_rw = !(flags & GPT_FLAG_READ_ONLY);
+
+                                r = free_and_strdup(&srv, node);
+                                if (r < 0)
+                                        return log_oom();
+                        }
 #ifdef GPT_ROOT_NATIVE
-                else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) {
+                        else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) {
 
-                        if (root && nr >= root_nr)
-                                continue;
+                                if (root && nr >= root_nr)
+                                        continue;
 
-                        root_nr = nr;
-                        root_rw = !(flags & GPT_FLAG_READ_ONLY);
+                                root_nr = nr;
+                                root_rw = !(flags & GPT_FLAG_READ_ONLY);
 
-                        free(root);
-                        root = strdup(node);
-                        if (!root)
-                                return log_oom();
-                }
+                                r = free_and_strdup(&root, node);
+                                if (r < 0)
+                                        return log_oom();
+                        }
 #endif
 #ifdef GPT_ROOT_SECONDARY
-                else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) {
+                        else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) {
+
+                                if (secondary_root && nr >= secondary_root_nr)
+                                        continue;
+
+                                secondary_root_nr = nr;
+                                secondary_root_rw = !(flags & GPT_FLAG_READ_ONLY);
+
+                                r = free_and_strdup(&secondary_root, node);
+                                if (r < 0)
+                                        return log_oom();
+                        }
+#endif
+                        else if (sd_id128_equal(type_id, GPT_LINUX_GENERIC)) {
+
+                                if (generic)
+                                        multiple_generic = true;
+                                else {
+                                        generic_rw = !(flags & GPT_FLAG_READ_ONLY);
+
+                                        r = free_and_strdup(&generic, node);
+                                        if (r < 0)
+                                                return log_oom();
+                                }
+                        }
 
-                        if (secondary_root && nr >= secondary_root_nr)
+                } else if (is_mbr) {
+                        int type;
+
+                        if (flags != 0x80) /* Bootable flag */
                                 continue;
 
-                        secondary_root_nr = nr;
-                        secondary_root_rw = !(flags & GPT_FLAG_READ_ONLY);
+                        type = blkid_partition_get_type(pp);
+                        if (type != 0x83) /* Linux partition */
+                                continue;
 
+                        if (generic)
+                                multiple_generic = true;
+                        else {
+                                generic_rw = true;
 
-                        free(secondary_root);
-                        secondary_root = strdup(node);
-                        if (!secondary_root)
-                                return log_oom();
+                                r = free_and_strdup(&root, node);
+                                if (r < 0)
+                                        return log_oom();
+                        }
                 }
-#endif
-        }
-
-        if (!root && !secondary_root) {
-                log_error("Failed to identify root partition in disk image %s.\n"
-                          "Note that the disk image needs to follow http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/ to be supported by systemd-nspawn.", arg_image);
-                return -EINVAL;
         }
 
         if (root) {
@@ -2788,6 +2981,31 @@ static int dissect_image(
 
                 *root_device_rw = secondary_root_rw;
                 *secondary = true;
+        } else if (generic) {
+
+                /* There were no partitions with precise meanings
+                 * around, but we found generic partitions. In this
+                 * case, if there's only one, we can go ahead and boot
+                 * it, otherwise we bail out, because we really cannot
+                 * make any sense of it. */
+
+                if (multiple_generic) {
+                        log_error("Identified multiple bootable Linux partitions on\n"
+                                  "    %s\n"
+                                  PARTITION_TABLE_BLURB, arg_image);
+                        return -EINVAL;
+                }
+
+                *root_device = generic;
+                generic = NULL;
+
+                *root_device_rw = generic_rw;
+                *secondary = false;
+        } else {
+                log_error("Failed to identify root partition in disk image\n"
+                          "    %s\n"
+                          PARTITION_TABLE_BLURB, arg_image);
+                return -EINVAL;
         }
 
         if (home) {
@@ -2915,7 +3133,7 @@ static void loop_remove(int nr, int *image_fd) {
         if (image_fd && *image_fd >= 0) {
                 r = ioctl(*image_fd, LOOP_CLR_FD);
                 if (r < 0)
-                        log_warning_errno(errno, "Failed to close loop image: %m");
+                        log_debug_errno(errno, "Failed to close loop image: %m");
                 *image_fd = safe_close(*image_fd);
         }
 
@@ -2927,7 +3145,7 @@ static void loop_remove(int nr, int *image_fd) {
 
         r = ioctl(control, LOOP_CTL_REMOVE, nr);
         if (r < 0)
-                log_warning_errno(errno, "Failed to remove loop %d: %m", nr);
+                log_debug_errno(errno, "Failed to remove loop %d: %m", nr);
 }
 
 static int spawn_getent(const char *database, const char *key, pid_t *rpid) {
@@ -3273,7 +3491,7 @@ static int determine_names(void) {
                                 return -ENOENT;
                         }
 
-                        if (i->type == IMAGE_GPT)
+                        if (i->type == IMAGE_RAW)
                                 r = set_sanitized_path(&arg_image, i->path);
                         else
                                 r = set_sanitized_path(&arg_directory, i->path);
@@ -3442,7 +3660,7 @@ int main(int argc, char *argv[]) {
                                         if (!arg_quiet)
                                                 log_info("Directory %s already exists, not populating from template %s.", arg_directory, arg_template);
                                 } else if (r < 0) {
-                                        log_error_errno(r, "Couldn't create snapshort %s from %s: %m", arg_directory, arg_template);
+                                        log_error_errno(r, "Couldn't create snapshot %s from %s: %m", arg_directory, arg_template);
                                         goto finish;
                                 } else {
                                         if (!arg_quiet)
@@ -3920,6 +4138,10 @@ int main(int argc, char *argv[]) {
                         if (r < 0)
                                 goto finish;
 
+                        r = setup_ipvlan(pid);
+                        if (r < 0)
+                                goto finish;
+
                         r = register_machine(pid, ifi);
                         if (r < 0)
                                 goto finish;
@@ -3947,9 +4169,10 @@ int main(int argc, char *argv[]) {
                                 _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
                                 char last_char = 0;
 
-                                sd_notify(false,
-                                          "READY=1\n"
-                                          "STATUS=Container running.");
+                                sd_notifyf(false,
+                                           "READY=1\n"
+                                           "STATUS=Container running.\n"
+                                           "X_NSPAWN_LEADER_PID=" PID_FMT, pid);
 
                                 r = sd_event_new(&event);
                                 if (r < 0) {
@@ -4076,6 +4299,7 @@ finish:
         strv_free(arg_setenv);
         strv_free(arg_network_interfaces);
         strv_free(arg_network_macvlan);
+        strv_free(arg_network_ipvlan);
         strv_free(arg_bind);
         strv_free(arg_bind_ro);
         strv_free(arg_tmpfs);