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
This commit is contained in:
Thierry Martin 2022-09-05 15:02:06 +02:00 committed by Zbigniew Jędrzejewski-Szmek
parent 3af48a86d9
commit 2f091b1b49
8 changed files with 221 additions and 79 deletions

View file

@ -852,11 +852,13 @@
<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>

View file

@ -531,8 +531,11 @@
<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>
@ -544,7 +547,9 @@
<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

View 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)

View 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-");
}

View 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);

View 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,

View 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);

View 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();