]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nspawn: container network interface naming
authorThierry Martin <tmartin@haproxy.com>
Mon, 5 Sep 2022 13:02:06 +0000 (15:02 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 12 Apr 2023 12:28:43 +0000 (14:28 +0200)
systemd-nspawn now optionally supports colon-separated pair of
host interface name and container interface name for --network-macvlan, --network-ipvlan and --network-interface options.
Also supported in .nspawn configuration files (i.e Interface=, MACVLAN=, IPVLAN= parameters).

man page changed for ntwk interface naming

man/systemd-nspawn.xml
man/systemd.nspawn.xml
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 39a6febb3c6261655ea3bbde79fc53a1cea1a9f9..3b158a0631cb1d86d4d613fe226a67278b23237f 100644 (file)
       <varlistentry>
         <term><option>--network-interface=</option></term>
 
-        <listitem><para>Assign the specified network interface to the container. This will remove the
-        specified interface from the calling namespace and place it in the container. When the container
-        terminates, it is moved back to the calling namespace.  Note that
-        <option>--network-interface=</option> implies <option>--private-network</option>. This option may be
-        used more than once to add multiple network interfaces to the container.</para>
+        <listitem><para>Assign the specified network interface to the container. Either takes a single
+        interface name, referencing the name on the host, or a colon-separated pair of interfaces, in which
+        case the first one references the name on the host, and the second one the name in the container.
+        When the container terminates, the interface is moved back to the calling namespace and renamed to
+        its original name.  Note that <option>--network-interface=</option> implies
+        <option>--private-network</option>. This option may be used more than once to add multiple network
+        interfaces to the container.</para>
 
         <para>Note that any network interface specified this way must already exist at the time the container
         is started. If the container shall be started automatically at boot via a
@@ -880,9 +882,12 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
         <term><option>--network-macvlan=</option></term>
 
         <listitem><para>Create a <literal>macvlan</literal> interface of the specified Ethernet network
-        interface and add it to the container. A <literal>macvlan</literal> interface is a virtual interface
-        that adds a second MAC address to an existing physical Ethernet link. The interface in the container
-        will be named after the interface on the host, prefixed with <literal>mv-</literal>. Note that
+        interface and add it to the container. Either takes a single interface name, referencing the name
+        on the host, or a colon-separated pair of interfaces, in which case the first one references the name
+        on the host, and the second one the name in the container. A <literal>macvlan</literal> interface is
+        a virtual interface that adds a second MAC address to an existing physical Ethernet link. If the
+        container interface name is not defined, the interface in the container will be named after the
+        interface on the host, prefixed with <literal>mv-</literal>. Note that
         <option>--network-macvlan=</option> implies <option>--private-network</option>. This option may be
         used more than once to add multiple network interfaces to the container.</para>
 
@@ -895,9 +900,13 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
         <term><option>--network-ipvlan=</option></term>
 
         <listitem><para>Create an <literal>ipvlan</literal> interface of the specified Ethernet network
-        interface and add it to the container. An <literal>ipvlan</literal> interface is a virtual interface,
+        interface and add it to the container. Either takes a single interface name, referencing the name on
+        the host, or a colon-separated pair of interfaces, in which case the first one references the name
+        on the host, and the second one the name in the container. An <literal>ipvlan</literal> interface is
+        a virtual interface,
         similar to a <literal>macvlan</literal> interface, which uses the same MAC address as the underlying
-        interface.  The interface in the container will be named after the interface on the host, prefixed
+        interface. If the container interface name is not defined, the interface in the container will be
+        named after the interface on the host, prefixed
         with <literal>iv-</literal>.  Note that <option>--network-ipvlan=</option> implies
         <option>--private-network</option>. This option may be used more than once to add multiple network
         interfaces to the container.</para>
index c1eef7853b642dcb136e26cbb16f312ad3eba4de..ec94176c01879fb7e730ae54172d1ba7430e4d24 100644 (file)
       <varlistentry>
         <term><varname>Interface=</varname></term>
 
-        <listitem><para>Takes a space-separated list of interfaces to
-        add to the container. This option corresponds to the
+        <listitem><para>Takes a space-separated list of interfaces to add to the container.
+        The interface object is defined either by a single interface name, referencing the name on the host,
+        or a colon-separated pair of interfaces, in which case the first one references the name on the host,
+        and the second one the name in the container.
+        This option corresponds to the
         <option>--network-interface=</option> command line switch and
         implies <varname>Private=yes</varname>. This option is
         privileged (see above).</para></listitem>
 
         <listitem><para>Takes a space-separated list of interfaces to
         add MACLVAN or IPVLAN interfaces to, which are then added to
-        the container. These options correspond to the
+        the container. The interface object is defined either by a single interface name, referencing the name
+        on the host, or a colon-separated pair of interfaces, in which case the first one references the name
+        on the host, and the second one the name in the container. These options correspond to the
         <option>--network-macvlan=</option> and
         <option>--network-ipvlan=</option> command line switches and
         imply <varname>Private=yes</varname>. These options are
index a93b8c38c933e926b338cd27682c7a73a362ccb0..9e1210f876ef72f6d9e5ac5163479b2ca93de652 100644 (file)
@@ -72,9 +72,9 @@ Files.PrivateUsersChown,      config_parse_userns_chown,   0,
 Files.PrivateUsersOwnership,  config_parse_userns_ownership, 0,                      offsetof(Settings, userns_ownership)
 Files.BindUser,               config_parse_bind_user,      0,                        offsetof(Settings, bind_user)
 Network.Private,              config_parse_tristate,       0,                        offsetof(Settings, private_network)
-Network.Interface,            config_parse_strv,           0,                        offsetof(Settings, network_interfaces)
-Network.MACVLAN,              config_parse_strv,           0,                        offsetof(Settings, network_macvlan)
-Network.IPVLAN,               config_parse_strv,           0,                        offsetof(Settings, network_ipvlan)
+Network.Interface,            config_parse_network_iface_pair, 0,                    offsetof(Settings, network_interfaces)
+Network.MACVLAN,              config_parse_macvlan_iface_pair, 0,                    offsetof(Settings, network_macvlan)
+Network.IPVLAN,               config_parse_ipvlan_iface_pair, 0,                     offsetof(Settings, network_ipvlan)
 Network.VirtualEthernet,      config_parse_tristate,       0,                        offsetof(Settings, network_veth)
 Network.VirtualEthernetExtra, config_parse_veth_extra,     0,                        0
 Network.Bridge,               config_parse_ifname,         0,                        offsetof(Settings, network_bridge)
index 143156484bc4e5e9cdd9509c27e67a1cb991561b..d898f0d4c911803a122cd50c471444fd1f1e00cb 100644 (file)
@@ -463,7 +463,7 @@ int remove_bridge(const char *bridge_name) {
         return remove_one_link(rtnl, bridge_name);
 }
 
-int test_network_interface_initialized(const char *name) {
+static int test_network_interface_initialized(const char *name) {
         _cleanup_(sd_device_unrefp) sd_device *d = NULL;
         int r;
 
@@ -491,18 +491,28 @@ int test_network_interface_initialized(const char *name) {
         return 0;
 }
 
-int move_network_interfaces(int netns_fd, char **ifaces) {
+int test_network_interfaces_initialized(char **iface_pairs) {
+        int r;
+        STRV_FOREACH_PAIR(a, b, iface_pairs) {
+                r = test_network_interface_initialized(*a);
+                if (r < 0)
+                        return r;
+        }
+        return 0;
+}
+
+int move_network_interfaces(int netns_fd, char **iface_pairs) {
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         int r;
 
-        if (strv_isempty(ifaces))
+        if (strv_isempty(iface_pairs))
                 return 0;
 
         r = sd_netlink_open(&rtnl);
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to netlink: %m");
 
-        STRV_FOREACH(i, ifaces) {
+        STRV_FOREACH_PAIR(i, b, iface_pairs) {
                 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
                 int ifi;
 
@@ -518,6 +528,12 @@ int move_network_interfaces(int netns_fd, char **ifaces) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to append namespace fd to netlink message: %m");
 
+                if (!streq(*b, *i)) {
+                        r = sd_netlink_message_append_string(m, IFLA_IFNAME, *b);
+                        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 < 0)
                         return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i);
@@ -526,23 +542,23 @@ int move_network_interfaces(int netns_fd, char **ifaces) {
         return 0;
 }
 
-int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) {
+int setup_macvlan(const char *machine_name, pid_t pid, char **iface_pairs) {
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         unsigned idx = 0;
         int r;
 
-        if (strv_isempty(ifaces))
+        if (strv_isempty(iface_pairs))
                 return 0;
 
         r = sd_netlink_open(&rtnl);
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to netlink: %m");
 
-        STRV_FOREACH(i, ifaces) {
+        STRV_FOREACH_PAIR(i, b, iface_pairs) {
                 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
-                _cleanup_free_ char *n = NULL, *a = NULL;
+                _cleanup_free_ char *n = NULL;
+                int shortened, ifi;
                 struct ether_addr mac;
-                int ifi;
 
                 ifi = rtnl_resolve_interface_or_warn(&rtnl, *i);
                 if (ifi < 0)
@@ -560,16 +576,11 @@ int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to add netlink interface index: %m");
 
-                n = strjoin("mv-", *i);
+                n = strdup(*b);
                 if (!n)
                         return log_oom();
 
-                r = shorten_ifname(n);
-                if (r > 0) {
-                        a = strjoin("mv-", *i);
-                        if (!a)
-                                return log_oom();
-                }
+                shortened = shorten_ifname(n);
 
                 r = sd_netlink_message_append_string(m, IFLA_IFNAME, n);
                 if (r < 0)
@@ -607,27 +618,28 @@ int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to add new macvlan interfaces: %m");
 
-                (void) set_alternative_ifname(rtnl, n, a);
+                if (shortened > 0)
+                        (void) set_alternative_ifname(rtnl, n, *b);
         }
 
         return 0;
 }
 
-int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) {
+int setup_ipvlan(const char *machine_name, pid_t pid, char **iface_pairs) {
         _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
         int r;
 
-        if (strv_isempty(ifaces))
+        if (strv_isempty(iface_pairs))
                 return 0;
 
         r = sd_netlink_open(&rtnl);
         if (r < 0)
                 return log_error_errno(r, "Failed to connect to netlink: %m");
 
-        STRV_FOREACH(i, ifaces) {
+        STRV_FOREACH_PAIR(i, b, iface_pairs) {
                 _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
-                _cleanup_free_ char *n = NULL, *a = NULL;
-                int ifi;
+                _cleanup_free_ char *n = NULL;
+                int shortened, ifi ;
 
                 ifi = rtnl_resolve_interface_or_warn(&rtnl, *i);
                 if (ifi < 0)
@@ -641,16 +653,11 @@ int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to add netlink interface index: %m");
 
-                n = strjoin("iv-", *i);
+                n = strdup(*b);
                 if (!n)
                         return log_oom();
 
-                r = shorten_ifname(n);
-                if (r > 0) {
-                        a = strjoin("iv-", *i);
-                        if (!a)
-                                return log_oom();
-                }
+                shortened = shorten_ifname(n);
 
                 r = sd_netlink_message_append_string(m, IFLA_IFNAME, n);
                 if (r < 0)
@@ -684,7 +691,8 @@ int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to add new ipvlan interfaces: %m");
 
-                (void) set_alternative_ifname(rtnl, n, a);
+                if (shortened > 0)
+                        (void) set_alternative_ifname(rtnl, n, *b);
         }
 
         return 0;
@@ -742,3 +750,51 @@ int remove_veth_links(const char *primary, char **pairs) {
 
         return 0;
 }
+
+static int network_iface_pair_parse(const char* iftype, char ***l, const char *p, const char* ifprefix) {
+        _cleanup_free_ char *a = NULL, *b = NULL;
+        int r;
+
+        r = extract_first_word(&p, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+        if (r < 0)
+                return log_error_errno(r, "Failed to extract first word in %s parameter: %m", iftype);
+        if (r == 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Short read while reading %s parameter: %m", iftype);
+        if (!ifname_valid(a))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "%s, interface name not valid: %s", iftype, a);
+
+        if (isempty(p)) {
+                if (ifprefix)
+                        b = strjoin(ifprefix, a);
+                else
+                        b = strdup(a);
+        } else
+                b = strdup(p);
+        if (!b)
+                return log_oom();
+
+        if (!ifname_valid(b))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                        "%s, interface name not valid: %s", iftype, b);
+
+        r = strv_push_pair(l, a, b);
+        if (r < 0)
+                return log_oom();
+
+        a = b = NULL;
+        return 0;
+}
+
+int interface_pair_parse(char ***l, const char *p) {
+        return network_iface_pair_parse("Network interface", l, p, NULL);
+}
+
+int macvlan_pair_parse(char ***l, const char *p) {
+        return network_iface_pair_parse("MACVLAN network interface", l, p, "mv-");
+}
+
+int ipvlan_pair_parse(char ***l, const char *p) {
+        return network_iface_pair_parse("IPVLAN network interface", l, p, "iv-");
+}
index 5c2d9834180a504ca5cebba16844052de5a93b4f..355d813c963d28f64ae7950a3764f45ab60d095f 100644 (file)
@@ -5,7 +5,7 @@
 #include <stdbool.h>
 #include <sys/types.h>
 
-int test_network_interface_initialized(const char *name);
+int test_network_interfaces_initialized(char **iface_pairs);
 
 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);
@@ -13,11 +13,15 @@ int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs);
 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);
+int setup_macvlan(const char *machine_name, pid_t pid, char **iface_pairs);
+int setup_ipvlan(const char *machine_name, pid_t pid, char **iface_pairs);
 
-int move_network_interfaces(int netns_fd, char **ifaces);
+int move_network_interfaces(int netns_fd, char **iface_pairs);
 
 int veth_extra_parse(char ***l, const char *p);
 
 int remove_veth_links(const char *primary, char **pairs);
+
+int interface_pair_parse(char ***l, const char *p);
+int macvlan_pair_parse(char ***l, const char *p);
+int ipvlan_pair_parse(char ***l, const char *p);
index 05bde1c7567772e01591e1e98ce014e11af0b54b..7500eabd1882cd98c0e9172bef3187a0a649a692 100644 (file)
@@ -469,6 +469,69 @@ int config_parse_veth_extra(
         return 0;
 }
 
+int config_parse_network_iface_pair(
+                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) {
+
+        char*** l = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        return interface_pair_parse(l, rvalue);
+}
+
+int config_parse_macvlan_iface_pair(
+                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) {
+
+        char*** l = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        return macvlan_pair_parse(l, rvalue);
+}
+
+int config_parse_ipvlan_iface_pair(
+                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) {
+
+        char*** l = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        return ipvlan_pair_parse(l, rvalue);
+}
+
 int config_parse_network_zone(
                 const char *unit,
                 const char *filename,
index 004b663e9e4011e7719d2b9db41e9d96c874dbcd..0a3d975364954113fffc936f94346cc143f51eb1 100644 (file)
@@ -259,6 +259,9 @@ CONFIG_PARSER_PROTOTYPE(config_parse_tmpfs);
 CONFIG_PARSER_PROTOTYPE(config_parse_overlay);
 CONFIG_PARSER_PROTOTYPE(config_parse_inaccessible);
 CONFIG_PARSER_PROTOTYPE(config_parse_veth_extra);
+CONFIG_PARSER_PROTOTYPE(config_parse_network_iface_pair);
+CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_iface_pair);
+CONFIG_PARSER_PROTOTYPE(config_parse_ipvlan_iface_pair);
 CONFIG_PARSER_PROTOTYPE(config_parse_network_zone);
 CONFIG_PARSER_PROTOTYPE(config_parse_boot);
 CONFIG_PARSER_PROTOTYPE(config_parse_pid2);
index 3882676216a525c0b0c7693432b330cab11a10c1..ff6a4375732e7c7f3650221c45306d82a84e0707 100644 (file)
@@ -377,13 +377,13 @@ static int help(void) {
                "                            --private-users-ownership=auto\n\n"
                "%3$sNetworking:%4$s\n"
                "     --private-network      Disable network in container\n"
-               "     --network-interface=INTERFACE\n"
+               "     --network-interface=HOSTIF[:CONTAINERIF]\n"
                "                            Assign an existing network interface to the\n"
                "                            container\n"
-               "     --network-macvlan=INTERFACE\n"
+               "     --network-macvlan=HOSTIF[:CONTAINERIF]\n"
                "                            Create a macvlan network interface based on an\n"
                "                            existing network interface to the container\n"
-               "     --network-ipvlan=INTERFACE\n"
+               "     --network-ipvlan=HOSTIF[:CONTAINERIF]\n"
                "                            Create an 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"
@@ -924,51 +924,28 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_NETWORK_INTERFACE:
-                        if (!ifname_valid(optarg))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "Network interface name not valid: %s", optarg);
-
-                        r = test_network_interface_initialized(optarg);
+                        r = interface_pair_parse(&arg_network_interfaces, optarg);
                         if (r < 0)
                                 return r;
 
-                        if (strv_extend(&arg_network_interfaces, optarg) < 0)
-                                return log_oom();
-
                         arg_private_network = true;
                         arg_settings_mask |= SETTING_NETWORK;
                         break;
 
                 case ARG_NETWORK_MACVLAN:
-
-                        if (!ifname_valid(optarg))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "MACVLAN network interface name not valid: %s", optarg);
-
-                        r = test_network_interface_initialized(optarg);
+                        r = macvlan_pair_parse(&arg_network_macvlan, optarg);
                         if (r < 0)
                                 return r;
 
-                        if (strv_extend(&arg_network_macvlan, optarg) < 0)
-                                return log_oom();
-
                         arg_private_network = true;
                         arg_settings_mask |= SETTING_NETWORK;
                         break;
 
                 case ARG_NETWORK_IPVLAN:
-
-                        if (!ifname_valid(optarg))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "IPVLAN network interface name not valid: %s", optarg);
-
-                        r = test_network_interface_initialized(optarg);
+                        r = ipvlan_pair_parse(&arg_network_ipvlan, optarg);
                         if (r < 0)
                                 return r;
 
-                        if (strv_extend(&arg_network_ipvlan, optarg) < 0)
-                                return log_oom();
-
                         _fallthrough_;
                 case ARG_PRIVATE_NETWORK:
                         arg_private_network = true;
@@ -1894,6 +1871,23 @@ static int verify_arguments(void) {
         return 0;
 }
 
+static int verify_network_interfaces_initialized(void) {
+        int r;
+        r = test_network_interfaces_initialized(arg_network_interfaces);
+        if (r < 0)
+                return r;
+
+        r = test_network_interfaces_initialized(arg_network_macvlan);
+        if (r < 0)
+                return r;
+
+        r = test_network_interfaces_initialized(arg_network_ipvlan);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 int userns_lchown(const char *p, uid_t uid, gid_t gid) {
         assert(p);
 
@@ -5288,6 +5282,10 @@ static int run_container(
                                 _exit(EXIT_FAILURE);
                         }
 
+                        /* Reverse network interfaces pair list so that interfaces get their initial name back.
+                         * This is about ensuring interfaces get their old name back when being moved back. */
+                        arg_network_interfaces = strv_reverse(arg_network_interfaces);
+
                         r = move_network_interfaces(parent_netns_fd, arg_network_interfaces);
                         if (r < 0)
                                 log_error_errno(r, "Failed to move network interfaces back to parent network namespace: %m");
@@ -5506,6 +5504,10 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 goto finish;
 
+        r = verify_network_interfaces_initialized();
+        if (r < 0)
+                goto finish;
+
         /* Reapply environment settings. */
         (void) detect_unified_cgroup_hierarchy_from_environment();