nspawn: add new --network-zone= switch for automatically managed bridge devices
authorLennart Poettering <lennart@poettering.net>
Fri, 6 May 2016 19:00:27 +0000 (21:00 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 9 May 2016 13:45:31 +0000 (15:45 +0200)
This adds a new concept of network "zones", which are little more than bridge
devices that are automatically managed by nspawn: when the first container
referencing a bridge is started, the bridge device is created, when the last
container referencing it is removed the bridge device is removed again. Besides
this logic --network-zone= is pretty much identical to --network-bridge=.

The usecase for this is to make it easy to run multiple related containers
(think MySQL in one and Apache in another) in a common, named virtual Ethernet
broadcast zone, that only exists as long as one of them is running, and fully
automatically managed otherwise.

src/nspawn/nspawn-gperf.gperf
src/nspawn/nspawn-network.c
src/nspawn/nspawn-network.h
src/nspawn/nspawn-settings.c
src/nspawn/nspawn-settings.h
src/nspawn/nspawn.c

index 34e1310..1d3f0b4 100644 (file)
@@ -40,4 +40,5 @@ Network.IPVLAN,               config_parse_strv,          0, offsetof(Settings,
 Network.VirtualEthernet,      config_parse_tristate,      0, offsetof(Settings, network_veth)
 Network.VirtualEthernetExtra, config_parse_veth_extra,    0, 0
 Network.Bridge,               config_parse_string,        0, offsetof(Settings, network_bridge)
+Network.Zone,                 config_parse_network_zone,  0, 0
 Network.Port,                 config_parse_expose_port,   0, 0
index 9938fce..8da47a2 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "alloc-util.h"
 #include "ether-addr-util.h"
+#include "lockfile-util.h"
 #include "netlink-util.h"
 #include "nspawn-network.h"
 #include "siphash24.h"
 #define VETH_EXTRA_CONTAINER_HASH_KEY SD_ID128_MAKE(af,50,17,61,ce,f9,4d,35,84,0d,2b,20,54,be,ce,59)
 #define MACVLAN_HASH_KEY SD_ID128_MAKE(00,13,6d,bc,66,83,44,81,bb,0c,f9,51,1f,24,a6,6f)
 
+static int remove_one_link(sd_netlink *rtnl, const char *name) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+        int r;
+
+        if (isempty(name))
+                return 0;
+
+        r = sd_rtnl_message_new_link(rtnl, &m, RTM_DELLINK, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate netlink message: %m");
+
+        r = sd_netlink_message_append_string(m, IFLA_IFNAME, name);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add netlink interface name: %m");
+
+        r = sd_netlink_call(rtnl, m, 0, NULL);
+        if (r == -ENODEV) /* Already gone */
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to remove interface %s: %m", name);
+
+        return 1;
+}
+
 static int generate_mac(
                 const char *machine_name,
                 struct ether_addr *mac,
@@ -240,45 +265,149 @@ int setup_veth_extra(
         return 0;
 }
 
-int setup_bridge(const char *veth_name, const char *bridge_name) {
+static int join_bridge(sd_netlink *rtnl, const char *veth_name, const char *bridge_name) {
         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
-        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         int r, bridge_ifi;
 
+        assert(rtnl);
         assert(veth_name);
         assert(bridge_name);
 
         bridge_ifi = (int) if_nametoindex(bridge_name);
         if (bridge_ifi <= 0)
-                return log_error_errno(errno, "Failed to resolve interface %s: %m", bridge_name);
-
-        r = sd_netlink_open(&rtnl);
-        if (r < 0)
-                return log_error_errno(r, "Failed to connect to netlink: %m");
+                return -errno;
 
         r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, 0);
         if (r < 0)
-                return log_error_errno(r, "Failed to allocate netlink message: %m");
+                return r;
 
         r = sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP);
         if (r < 0)
-                return log_error_errno(r, "Failed to set IFF_UP flag: %m");
+                return r;
 
         r = sd_netlink_message_append_string(m, IFLA_IFNAME, veth_name);
         if (r < 0)
-                return log_error_errno(r, "Failed to add netlink interface name field: %m");
+                return r;
 
         r = sd_netlink_message_append_u32(m, IFLA_MASTER, bridge_ifi);
         if (r < 0)
-                return log_error_errno(r, "Failed to add netlink master field: %m");
+                return r;
 
         r = sd_netlink_call(rtnl, m, 0, NULL);
         if (r < 0)
-                return log_error_errno(r, "Failed to add veth interface to bridge: %m");
+                return r;
 
         return bridge_ifi;
 }
 
+static int create_bridge(sd_netlink *rtnl, const char *bridge_name) {
+        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+        int r;
+
+        r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_append_string(m, IFLA_IFNAME, bridge_name);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_open_container(m, IFLA_LINKINFO);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "bridge");
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_close_container(m);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_close_container(m);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_call(rtnl, m, 0, NULL);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int setup_bridge(const char *veth_name, const char *bridge_name, bool create) {
+        _cleanup_release_lock_file_ LockFile bridge_lock = LOCK_FILE_INIT;
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        int r, bridge_ifi;
+        unsigned n = 0;
+
+        assert(veth_name);
+        assert(bridge_name);
+
+        r = sd_netlink_open(&rtnl);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to netlink: %m");
+
+        if (create) {
+                /* We take a system-wide lock here, so that we can safely check whether there's still a member in the
+                 * bridge before removing it, without risking interferance from other nspawn instances. */
+
+                r = make_lock_file("/run/systemd/nspawn-network-zone", LOCK_EX, &bridge_lock);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to take network zone lock: %m");
+        }
+
+        for (;;) {
+                bridge_ifi = join_bridge(rtnl, veth_name, bridge_name);
+                if (bridge_ifi >= 0)
+                        return bridge_ifi;
+                if (bridge_ifi != -ENODEV || !create || n > 10)
+                        return log_error_errno(bridge_ifi, "Failed to add interface %s to bridge %s: %m", veth_name, bridge_name);
+
+                /* Count attempts, so that we don't enter an endless loop here. */
+                n++;
+
+                /* The bridge doesn't exist yet. Let's create it */
+                r = create_bridge(rtnl, bridge_name);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to create bridge interface %s: %m", bridge_name);
+
+                /* Try again, now that the bridge exists */
+        }
+}
+
+int remove_bridge(const char *bridge_name) {
+        _cleanup_release_lock_file_ LockFile bridge_lock = LOCK_FILE_INIT;
+        _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+        const char *path;
+        int r;
+
+        /* Removes the specified bridge, but only if it is currently empty */
+
+        if (isempty(bridge_name))
+                return 0;
+
+        r = make_lock_file("/run/systemd/nspawn-network-zone", LOCK_EX, &bridge_lock);
+        if (r < 0)
+                return log_error_errno(r, "Failed to take network zone lock: %m");
+
+        path = strjoina("/sys/class/net/", bridge_name, "/brif");
+
+        r = dir_is_empty(path);
+        if (r == -ENOENT) /* Already gone? */
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Can't detect if bridge %s is empty: %m", bridge_name);
+        if (r == 0) /* Still populated, leave it around */
+                return 0;
+
+        r = sd_netlink_open(&rtnl);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to netlink: %m");
+
+        return remove_one_link(rtnl, bridge_name);
+}
+
 static int parse_interface(struct udev *udev, const char *name) {
         _cleanup_udev_device_unref_ struct udev_device *d = NULL;
         char ifi_str[2 + DECIMAL_STR_MAX(int)];
@@ -541,30 +670,6 @@ int veth_extra_parse(char ***l, const char *p) {
         return 0;
 }
 
-static int remove_one_veth_link(sd_netlink *rtnl, const char *name) {
-        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
-        int r;
-
-        if (isempty(name))
-                return 0;
-
-        r = sd_rtnl_message_new_link(rtnl, &m, RTM_DELLINK, 0);
-        if (r < 0)
-                return log_error_errno(r, "Failed to allocate netlink message: %m");
-
-        r = sd_netlink_message_append_string(m, IFLA_IFNAME, name);
-        if (r < 0)
-                return log_error_errno(r, "Failed to add netlink interface name: %m");
-
-        r = sd_netlink_call(rtnl, m, 0, NULL);
-        if (r == -ENODEV) /* Already gone */
-                return 0;
-        if (r < 0)
-                return log_error_errno(r, "Failed to remove veth interface %s: %m", name);
-
-        return 1;
-}
-
 int remove_veth_links(const char *primary, char **pairs) {
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         char **a, **b;
@@ -580,10 +685,10 @@ int remove_veth_links(const char *primary, char **pairs) {
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to netlink: %m");
 
-        remove_one_veth_link(rtnl, primary);
+        remove_one_link(rtnl, primary);
 
         STRV_FOREACH_PAIR(a, b, pairs)
-                remove_one_veth_link(rtnl, *a);
+                remove_one_link(rtnl, *a);
 
         return 0;
 }
index c5036ab..3d8861e 100644 (file)
@@ -26,7 +26,8 @@
 int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], bool bridge);
 int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs);
 
-int setup_bridge(const char *veth_name, const char *bridge_name);
+int setup_bridge(const char *veth_name, const char *bridge_name, bool create);
+int remove_bridge(const char *bridge_name);
 
 int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces);
 int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces);
index b98a79f..5f1522c 100644 (file)
 #include "nspawn-settings.h"
 #include "parse-util.h"
 #include "process-util.h"
+#include "socket-util.h"
+#include "string-util.h"
 #include "strv.h"
 #include "user-util.h"
 #include "util.h"
-#include "string-util.h"
 
 int settings_load(FILE *f, const char *path, Settings **ret) {
         _cleanup_(settings_freep) Settings *s = NULL;
@@ -96,6 +97,7 @@ Settings* settings_free(Settings *s) {
         strv_free(s->network_ipvlan);
         strv_free(s->network_veth_extra);
         free(s->network_bridge);
+        free(s->network_zone);
         expose_port_free_all(s->expose_ports);
 
         custom_mount_free_all(s->custom_mounts, s->n_custom_mounts);
@@ -111,6 +113,7 @@ bool settings_private_network(Settings *s) {
                 s->private_network > 0 ||
                 s->network_veth > 0 ||
                 s->network_bridge ||
+                s->network_zone ||
                 s->network_interfaces ||
                 s->network_macvlan ||
                 s->network_ipvlan ||
@@ -122,7 +125,8 @@ bool settings_network_veth(Settings *s) {
 
         return
                 s->network_veth > 0 ||
-                s->network_bridge;
+                s->network_bridge ||
+                s->network_zone;
 }
 
 DEFINE_CONFIG_PARSE_ENUM(config_parse_volatile_mode, volatile_mode, VolatileMode, "Failed to parse volatile mode");
@@ -319,6 +323,38 @@ int config_parse_veth_extra(
         return 0;
 }
 
+int config_parse_network_zone(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Settings *settings = data;
+        _cleanup_free_ char *j = NULL;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        j = strappend("vz-", rvalue);
+        if (!ifname_valid(j)) {
+                log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid network zone name %s, ignoring: %m", rvalue);
+                return 0;
+        }
+
+        free(settings->network_zone);
+        settings->network_zone = j;
+        j = NULL;
+
+        return 0;
+}
+
 int config_parse_boot(
                 const char *unit,
                 const char *filename,
index e12e91b..1c47e37 100644 (file)
@@ -85,6 +85,7 @@ typedef struct Settings {
         int private_network;
         int network_veth;
         char *network_bridge;
+        char *network_zone;
         char **network_interfaces;
         char **network_macvlan;
         char **network_ipvlan;
@@ -109,6 +110,7 @@ int config_parse_volatile_mode(const char *unit, const char *filename, unsigned
 int config_parse_bind(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_tmpfs(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_veth_extra(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
+int config_parse_network_zone(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_boot(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_pid2(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
 int config_parse_private_users(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
index 643f459..efda7d6 100644 (file)
@@ -176,6 +176,7 @@ static char **arg_network_ipvlan = NULL;
 static bool arg_network_veth = false;
 static char **arg_network_veth_extra = NULL;
 static char *arg_network_bridge = NULL;
+static char *arg_network_zone = NULL;
 static unsigned long arg_personality = PERSONALITY_INVALID;
 static char *arg_image = NULL;
 static VolatileMode arg_volatile_mode = VOLATILE_NO;
@@ -234,6 +235,8 @@ static void help(void) {
                "                            Add a virtual Ethernet connection between host\n"
                "                            and container and add it to an existing bridge on\n"
                "                            the host\n"
+               "     --network-zone=NAME    Add a virtual Ethernet connection to the container,\n"
+               "                            and add it to an automatically managed bridge interface\n"
                "  -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n"
                "                            Expose a container IP port on the host\n"
                "  -Z --selinux-context=SECLABEL\n"
@@ -357,6 +360,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_NETWORK_MACVLAN,
                 ARG_NETWORK_IPVLAN,
                 ARG_NETWORK_BRIDGE,
+                ARG_NETWORK_ZONE,
                 ARG_NETWORK_VETH_EXTRA,
                 ARG_PERSONALITY,
                 ARG_VOLATILE,
@@ -404,6 +408,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "network-veth",          no_argument,       NULL, 'n'                   },
                 { "network-veth-extra",    required_argument, NULL, ARG_NETWORK_VETH_EXTRA},
                 { "network-bridge",        required_argument, NULL, ARG_NETWORK_BRIDGE    },
+                { "network-zone",          required_argument, NULL, ARG_NETWORK_ZONE      },
                 { "personality",           required_argument, NULL, ARG_PERSONALITY       },
                 { "image",                 required_argument, NULL, 'i'                   },
                 { "volatile",              optional_argument, NULL, ARG_VOLATILE          },
@@ -466,6 +471,28 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_settings_mask |= SETTING_USER;
                         break;
 
+                case ARG_NETWORK_ZONE: {
+                        char *j;
+
+                        j = strappend("vz-", optarg);
+                        if (!j)
+                                return log_oom();
+
+                        if (!ifname_valid(j)) {
+                                log_error("Network zone name not valid: %s", j);
+                                free(j);
+                                return -EINVAL;
+                        }
+
+                        free(arg_network_zone);
+                        arg_network_zone = j;
+
+                        arg_network_veth = true;
+                        arg_private_network = true;
+                        arg_settings_mask |= SETTING_NETWORK;
+                        break;
+                }
+
                 case ARG_NETWORK_BRIDGE:
 
                         if (!ifname_valid(optarg)) {
@@ -1027,6 +1054,11 @@ static int parse_argv(int argc, char *argv[]) {
                 return -EINVAL;
         }
 
+        if (arg_network_bridge && arg_network_zone) {
+                log_error("--network-bridge= and --network-zone= may not be combined.");
+                return -EINVAL;
+        }
+
         if (argc > optind) {
                 arg_parameters = strv_copy(argv + optind);
                 if (!arg_parameters)
@@ -3295,6 +3327,7 @@ static int load_settings(void) {
             (settings->private_network >= 0 ||
              settings->network_veth >= 0 ||
              settings->network_bridge ||
+             settings->network_zone ||
              settings->network_interfaces ||
              settings->network_macvlan ||
              settings->network_ipvlan ||
@@ -3325,6 +3358,10 @@ static int load_settings(void) {
                         free(arg_network_bridge);
                         arg_network_bridge = settings->network_bridge;
                         settings->network_bridge = NULL;
+
+                        free(arg_network_zone);
+                        arg_network_zone = settings->network_zone;
+                        settings->network_zone = NULL;
                 }
         }
 
@@ -3824,14 +3861,23 @@ int main(int argc, char *argv[]) {
                                 goto finish;
 
                         if (arg_network_veth) {
-                                r = setup_veth(arg_machine, pid, veth_name, !!arg_network_bridge);
+                                r = setup_veth(arg_machine, pid, veth_name,
+                                               arg_network_bridge || arg_network_zone);
                                 if (r < 0)
                                         goto finish;
                                 else if (r > 0)
                                         ifi = r;
 
                                 if (arg_network_bridge) {
-                                        r = setup_bridge(veth_name, arg_network_bridge);
+                                        /* Add the interface to a bridge */
+                                        r = setup_bridge(veth_name, arg_network_bridge, false);
+                                        if (r < 0)
+                                                goto finish;
+                                        if (r > 0)
+                                                ifi = r;
+                                } else if (arg_network_zone) {
+                                        /* Add the interface to a bridge, possibly creating it */
+                                        r = setup_bridge(veth_name, arg_network_zone, true);
                                         if (r < 0)
                                                 goto finish;
                                         if (r > 0)
@@ -4039,6 +4085,7 @@ finish:
 
         expose_port_flush(arg_expose_ports, &exposed);
         (void) remove_veth_links(veth_name, arg_network_veth_extra);
+        (void) remove_bridge(arg_network_zone);
 
         free(arg_directory);
         free(arg_template);