From 2f091b1b49543aade4aad9ec3b35b3665abac3e7 Mon Sep 17 00:00:00 2001 From: Thierry Martin Date: Mon, 5 Sep 2022 15:02:06 +0200 Subject: [PATCH] nspawn: container network interface naming 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 | 29 ++++++--- man/systemd.nspawn.xml | 11 +++- src/nspawn/nspawn-gperf.gperf | 6 +- src/nspawn/nspawn-network.c | 116 +++++++++++++++++++++++++--------- src/nspawn/nspawn-network.h | 12 ++-- src/nspawn/nspawn-settings.c | 63 ++++++++++++++++++ src/nspawn/nspawn-settings.h | 3 + src/nspawn/nspawn.c | 60 +++++++++--------- 8 files changed, 221 insertions(+), 79 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 39a6febb3c6..3b158a0631c 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -852,11 +852,13 @@ - 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 - implies . This option may be - used more than once to add multiple network interfaces to the container. + 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 implies + . This option may be used more than once to add multiple network + interfaces to the container. 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 Create a macvlan interface of the specified Ethernet network - interface and add it to the container. A macvlan 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 mv-. 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 macvlan 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 mv-. Note that implies . This option may be used more than once to add multiple network interfaces to the container. @@ -895,9 +900,13 @@ After=sys-subsystem-net-devices-ens1.device Create an ipvlan interface of the specified Ethernet network - interface and add it to the container. An ipvlan 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 ipvlan interface is + a virtual interface, similar to a macvlan 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 iv-. Note that implies . This option may be used more than once to add multiple network interfaces to the container. diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index c1eef7853b6..ec94176c018 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -531,8 +531,11 @@ Interface= - Takes a space-separated list of interfaces to - add to the container. This option corresponds to the + 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 command line switch and implies Private=yes. This option is privileged (see above). @@ -544,7 +547,9 @@ 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 and command line switches and imply Private=yes. These options are diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index a93b8c38c93..9e1210f876e 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -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) diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c index 143156484bc..d898f0d4c91 100644 --- a/src/nspawn/nspawn-network.c +++ b/src/nspawn/nspawn-network.c @@ -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-"); +} diff --git a/src/nspawn/nspawn-network.h b/src/nspawn/nspawn-network.h index 5c2d9834180..355d813c963 100644 --- a/src/nspawn/nspawn-network.h +++ b/src/nspawn/nspawn-network.h @@ -5,7 +5,7 @@ #include #include -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); diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 05bde1c7567..7500eabd188 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -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, diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 004b663e9e4..0a3d9753649 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -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); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 3882676216a..ff6a4375732 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -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(); -- 2.47.3