From 5e7423ff25b4b9b0a32753b9d1ec82a36e34b290 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 May 2016 20:50:16 +0200 Subject: man: document that nspawn's host0 and ve-* interfaces have default config in networkd --- man/systemd-nspawn.xml | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index bd688a0ee1..57c0a223af 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -524,15 +524,23 @@ - Create a virtual Ethernet link - (veth) between host and container. The host - side of the Ethernet link will be available as a network - interface named after the container's name (as specified with - ), prefixed with - ve-. The container side of the Ethernet - link will be named host0. Note that - implies - . + Create a virtual Ethernet link (veth) between host and container. The host + side of the Ethernet link will be available as a network interface named after the container's name (as + specified with ), prefixed with ve-. The container side of the + Ethernet link will be named host0. The option implies + . + + Note that + systemd-networkd.service8 + includes by default a network file /usr/lib/systemd/network/80-container-ve.network + matching the host-side interfaces created this way, which contains settings to enable automatic address + provisioning on the created virtual link via DHCP, as well as automatic IP routing onto the host's external + network interfaces. It also contains /usr/lib/systemd/network/80-container-host0.network + matching the container-side interface created this way, containing settings to enable client side address + assignment via DHCP. In case systemd-networkd is running on both the host and inside the + container, automatic IP communication from the container to the host is thus available, with further + connectivity to the external network. + @@ -553,12 +561,10 @@ - Adds the host side of the Ethernet link - created with to the specified - bridge. Note that implies - . If this option is used, the - host side of the Ethernet link will use the - vb- prefix instead of + Adds the host side of the Ethernet link created with to the + specified Ethernet bridge interface. Expects a valid network interface name of a bridge device as + argument. Note that implies . If this option + is used, the host side of the Ethernet link will use the vb- prefix instead of ve-. -- cgit v1.2.3-54-g00ecf From ef76dff225a00008fe0edd1f528c9096f1a91179 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 May 2016 20:58:32 +0200 Subject: util-lib: add new ifname_valid() call that validates interface names Make use of this in nspawn at a couple of places. A later commit should port more code over to this, including networkd. --- src/basic/socket-util.c | 38 ++++++++++++++++++++++++++++++++++++++ src/basic/socket-util.h | 2 ++ src/nspawn/nspawn-network.c | 6 ++++-- src/nspawn/nspawn.c | 24 ++++++++++++++++++++++++ src/test/test-socket-util.c | 25 +++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 2 deletions(-) diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index c634f1d564..c8769a54f4 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -43,7 +43,9 @@ #include "socket-util.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "user-util.h" +#include "utf8.h" #include "util.h" int socket_address_parse(SocketAddress *a, const char *s) { @@ -795,6 +797,42 @@ static const char* const ip_tos_table[] = { DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); +bool ifname_valid(const char *p) { + bool numeric = true; + + /* Checks whether a network interface name is valid. This is inspired by dev_valid_name() in the kernel sources + * but slightly stricter, as we only allow non-control, non-space ASCII characters in the interface name. We + * also don't permit names that only container numbers, to avoid confusion with numeric interface indexes. */ + + if (isempty(p)) + return false; + + if (strlen(p) >= IFNAMSIZ) + return false; + + if (STR_IN_SET(p, ".", "..")) + return false; + + while (*p) { + if ((unsigned char) *p >= 127U) + return false; + + if ((unsigned char) *p <= 32U) + return false; + + if (*p == ':' || *p == '/') + return false; + + numeric = numeric && (*p >= '0' && *p <= '9'); + p++; + } + + if (numeric) + return false; + + return true; +} + int getpeercred(int fd, struct ucred *ucred) { socklen_t n = sizeof(struct ucred); struct ucred u; diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index 160f7c484b..e9230e4a9f 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -123,6 +123,8 @@ int fd_inc_rcvbuf(int fd, size_t n); int ip_tos_to_string_alloc(int i, char **s); int ip_tos_from_string(const char *s); +bool ifname_valid(const char *p); + int getpeercred(int fd, struct ucred *ucred); int getpeersec(int fd, char **ret); diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c index f2b7e4dd79..9938fce503 100644 --- a/src/nspawn/nspawn-network.c +++ b/src/nspawn/nspawn-network.c @@ -29,6 +29,8 @@ #include "netlink-util.h" #include "nspawn-network.h" #include "siphash24.h" +#include "socket-util.h" +#include "stat-util.h" #include "string-util.h" #include "udev-util.h" #include "util.h" @@ -515,13 +517,13 @@ int veth_extra_parse(char ***l, const char *p) { r = extract_first_word(&p, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS); if (r < 0) return r; - if (r == 0 || isempty(a)) + if (r == 0 || !ifname_valid(a)) return -EINVAL; r = extract_first_word(&p, &b, ":", EXTRACT_DONT_COALESCE_SEPARATORS); if (r < 0) return r; - if (r == 0 || isempty(b)) { + if (r == 0 || !ifname_valid(b)) { free(b); b = strdup(a); if (!b) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 3fc6cc955c..643f459851 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -467,6 +467,12 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_NETWORK_BRIDGE: + + if (!ifname_valid(optarg)) { + log_error("Bridge interface name not valid: %s", optarg); + return -EINVAL; + } + r = free_and_strdup(&arg_network_bridge, optarg); if (r < 0) return log_oom(); @@ -489,6 +495,12 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_NETWORK_INTERFACE: + + if (!ifname_valid(optarg)) { + log_error("Network interface name not valid: %s", optarg); + return -EINVAL; + } + if (strv_extend(&arg_network_interfaces, optarg) < 0) return log_oom(); @@ -497,6 +509,12 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_NETWORK_MACVLAN: + + if (!ifname_valid(optarg)) { + log_error("MACVLAN network interface name not valid: %s", optarg); + return -EINVAL; + } + if (strv_extend(&arg_network_macvlan, optarg) < 0) return log_oom(); @@ -505,6 +523,12 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_NETWORK_IPVLAN: + + if (!ifname_valid(optarg)) { + log_error("IPVLAN network interface name not valid: %s", optarg); + return -EINVAL; + } + if (strv_extend(&arg_network_ipvlan, optarg) < 0) return log_oom(); diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index 9e01f3afd4..b480fdaa9c 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -27,6 +27,29 @@ #include "string-util.h" #include "util.h" +static void test_ifname_valid(void) { + assert(ifname_valid("foo")); + assert(ifname_valid("eth0")); + + assert(!ifname_valid("0")); + assert(!ifname_valid("99")); + assert(ifname_valid("a99")); + assert(ifname_valid("99a")); + + assert(!ifname_valid(NULL)); + assert(!ifname_valid("")); + assert(!ifname_valid(" ")); + assert(!ifname_valid(" foo")); + assert(!ifname_valid("bar\n")); + assert(!ifname_valid(".")); + assert(!ifname_valid("..")); + assert(ifname_valid("foo.bar")); + assert(!ifname_valid("x:y")); + + assert(ifname_valid("xxxxxxxxxxxxxxx")); + assert(!ifname_valid("xxxxxxxxxxxxxxxx")); +} + static void test_socket_address_parse(void) { SocketAddress a; @@ -362,6 +385,8 @@ int main(int argc, char *argv[]) { log_set_max_level(LOG_DEBUG); + test_ifname_valid(); + test_socket_address_parse(); test_socket_address_parse_netlink(); test_socket_address_equal(); -- cgit v1.2.3-54-g00ecf From 22b28dfdc78680a5c03e6af3b0ce6bc96f8174a1 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 May 2016 21:00:27 +0200 Subject: nspawn: add new --network-zone= switch for automatically managed bridge devices 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 | 1 + src/nspawn/nspawn-network.c | 181 +++++++++++++++++++++++++++++++++--------- src/nspawn/nspawn-network.h | 3 +- src/nspawn/nspawn-settings.c | 40 +++++++++- src/nspawn/nspawn-settings.h | 2 + src/nspawn/nspawn.c | 51 +++++++++++- 6 files changed, 235 insertions(+), 43 deletions(-) diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index 34e1310e29..1d3f0b40bf 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -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 diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c index 9938fce503..8da47a2ca6 100644 --- a/src/nspawn/nspawn-network.c +++ b/src/nspawn/nspawn-network.c @@ -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" @@ -41,6 +42,30 @@ #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; } diff --git a/src/nspawn/nspawn-network.h b/src/nspawn/nspawn-network.h index c5036ab470..3d8861e1e5 100644 --- a/src/nspawn/nspawn-network.h +++ b/src/nspawn/nspawn-network.h @@ -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); diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index b98a79fd09..5f1522cfb6 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -24,10 +24,11 @@ #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, diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index e12e91b886..1c47e37912 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -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); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 643f459851..efda7d66d7 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -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); -- cgit v1.2.3-54-g00ecf From 3af18d8eab661143ef89274607c513a860ede808 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 May 2016 21:03:40 +0200 Subject: network: add automatic configuration for the networks created by nspawn --network-zone= This way, they "just work", similar to --network-veth behaves, as long as networkd is enabled and running on all involved nodes. --- Makefile.am | 3 ++- network/80-container-host0.network | 4 ++++ network/80-container-ve.network | 4 ++++ network/80-container-vz.network | 22 ++++++++++++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 network/80-container-vz.network diff --git a/Makefile.am b/Makefile.am index ee9e91a339..aea91e9c94 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3598,7 +3598,8 @@ INSTALL_DIRS += \ dist_network_DATA = \ network/99-default.link \ network/80-container-host0.network \ - network/80-container-ve.network + network/80-container-ve.network \ + network/80-container-vz.network dist_udevrules_DATA += \ rules/50-udev-default.rules \ diff --git a/network/80-container-host0.network b/network/80-container-host0.network index f0626f81b0..561bd1a3b8 100644 --- a/network/80-container-host0.network +++ b/network/80-container-host0.network @@ -5,6 +5,10 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. +# This network file matches the container-side of the virtual Ethernet link +# created by systemd-nspawn's --network-veth switch. See systemd-nspawn(1) for +# details. + [Match] Virtualization=container Name=host0 diff --git a/network/80-container-ve.network b/network/80-container-ve.network index abb265ddc4..c42c7cc259 100644 --- a/network/80-container-ve.network +++ b/network/80-container-ve.network @@ -5,6 +5,10 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. +# This network file matches the host-side of the virtual Ethernet link +# created by systemd-nspawn's --network-veth switch. See systemd-nspawn(1) for +# details. + [Match] Name=ve-* Driver=veth diff --git a/network/80-container-vz.network b/network/80-container-vz.network new file mode 100644 index 0000000000..69a0607820 --- /dev/null +++ b/network/80-container-vz.network @@ -0,0 +1,22 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +# This network file matches the bridge interface created by systemd-nspawn's +# --network-zone= switch. See systemd-nspawn(1) for details. + +[Match] +Name=vz-* +Driver=bridge + +[Network] +# Default to using a /24 prefix, giving up to 253 addresses per virtual network. +Address=0.0.0.0/24 +LinkLocalAddressing=yes +DHCPServer=yes +IPMasquerade=yes +LLDP=yes +EmitLLDP=yes -- cgit v1.2.3-54-g00ecf From 938d25795446058cc2926fdc44dc0abf17ed0190 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 May 2016 21:04:52 +0200 Subject: man: add documentation for the new --network-zone= concept of nspawn --- man/systemd-nspawn.xml | 31 ++++++++++++++++++++++++++++++- man/systemd.nspawn.xml | 10 ++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 57c0a223af..0c8c699201 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -568,6 +568,35 @@ ve-. + + + + Creates a virtual Ethernet link (veth) to the container and adds it to an + automatically managed Ethernet bridge interface. The bridge interface is named after the passed argument, + prefixed with vz-. The bridge interface is automatically created when the first container + configured for its name is started, and is automatically removed when the last container configured for its + name exits. Hence, each bridge interface configured this way exists only as long as there's at least one + container referencing it running. This option is very similar to , besides + this automatic creation/removal of the bridge device. + + This setting makes it easy to place multiple related containers on a common, virtual Ethernet-based + broadcast domain, here called a "zone". Each container may only be part of one zone, but each zone may contain + any number of containers. Each zone is referenced by its name. Names may be chosen freely (as long as they form + valid network interface names when prefixed with vz-), and it is sufficient to pass the same + name to the switch of the various concurrently running containers to join + them in one zone. + + Note that + systemd-networkd.service8 + includes by default a network file /usr/lib/systemd/network/80-container-vz.network + matching the bridge interfaces created this way, which contains settings to enable automatic address + provisioning on the created virtual network via DHCP, as well as automatic IP routing onto the host's external + network interfaces. Using is hence in most cases fully automatic and + sufficient to connect multiple local containers in a joined broadcast domain to the host, with further + connectivity to the external network. + + + @@ -583,7 +612,7 @@ port number and its colon may be omitted, in which case the same port as the host port is implied. This option is only supported if private networking is used, such as with - or + , . diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index 15360078ef..3683412c14 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -419,6 +419,16 @@ option is privileged (see above). + + Zone= + + Takes a network zone name. This setting implies VirtualEthernet=yes and + Private=yes and has the effect that the host side of the created virtual Ethernet link is + connected to an automatically managed bridge interface named after the passed argument, prefixed with + vz-. This option corresponds to the command line + switch. This option is privileged (see above). + + Port= -- cgit v1.2.3-54-g00ecf From d31645adefb3462d168b44bb950f92654c395021 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 May 2016 21:20:59 +0200 Subject: tree-wide: port more code to use ifname_valid() --- src/core/load-fragment.c | 26 ++++++++----- src/libsystemd-network/network-internal.c | 64 +++++++------------------------ src/libsystemd-network/network-internal.h | 4 -- src/network/networkd-address.c | 27 +++++-------- src/nspawn/nspawn-gperf.gperf | 2 +- src/shared/conf-parser.c | 38 ++++++++++++++++++ src/shared/conf-parser.h | 1 + src/shared/firewall-util.c | 6 +-- 8 files changed, 83 insertions(+), 85 deletions(-) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 1a8c03904c..a12dd38c60 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -732,16 +732,17 @@ int config_parse_exec( DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type"); DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier"); -int config_parse_socket_bindtodevice(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_socket_bindtodevice( + 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) { Socket *s = data; char *n; @@ -752,6 +753,11 @@ int config_parse_socket_bindtodevice(const char* unit, assert(data); if (rvalue[0] && !streq(rvalue, "*")) { + if (!ifname_valid(rvalue)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is invalid, ignoring: %s", rvalue); + return 0; + } + n = strdup(rvalue); if (!n) return log_oom(); diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index 182d08c50d..929f066fa0 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -32,6 +32,7 @@ #include "network-internal.h" #include "parse-util.h" #include "siphash24.h" +#include "socket-util.h" #include "string-util.h" #include "strv.h" #include "utf8.h" @@ -175,54 +176,17 @@ int config_parse_net_condition(const char *unit, return 0; } -int config_parse_ifname(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 **s = data; - _cleanup_free_ char *n = NULL; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - n = strdup(rvalue); - if (!n) - return log_oom(); - - if (!ascii_is_valid(n) || strlen(n) >= IFNAMSIZ) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not ASCII clean or is too long, ignoring assignment: %s", rvalue); - return 0; - } - - free(*s); - if (*n) { - *s = n; - n = NULL; - } else - *s = NULL; - - return 0; -} - -int config_parse_ifnames(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_ifnames( + 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 ***sv = data; int r; @@ -241,8 +205,8 @@ int config_parse_ifnames(const char *unit, if (r == 0) break; - if (!ascii_is_valid(word) || strlen(word) >= IFNAMSIZ) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not ASCII clean or is too long, ignoring assignment: %s", rvalue); + if (!ifname_valid(word)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue); return 0; } diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index 72432774d7..5bcd577167 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -50,10 +50,6 @@ int config_parse_hwaddr(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_ifname(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_ifnames(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); diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 8b52a1f742..4eb0d927a6 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -27,6 +27,7 @@ #include "networkd.h" #include "parse-util.h" #include "set.h" +#include "socket-util.h" #include "string-util.h" #include "utf8.h" #include "util.h" @@ -726,7 +727,8 @@ int config_parse_address(const char *unit, return 0; } -int config_parse_label(const char *unit, +int config_parse_label( + const char *unit, const char *filename, unsigned line, const char *section, @@ -736,9 +738,9 @@ int config_parse_label(const char *unit, const char *rvalue, void *data, void *userdata) { - Network *network = userdata; + _cleanup_address_free_ Address *n = NULL; - char *label; + Network *network = userdata; int r; assert(filename); @@ -751,23 +753,14 @@ int config_parse_label(const char *unit, if (r < 0) return r; - label = strdup(rvalue); - if (!label) - return log_oom(); - - if (!ascii_is_valid(label) || strlen(label) >= IFNAMSIZ) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Interface label is not ASCII clean or is too long, ignoring assignment: %s", rvalue); - free(label); + if (!ifname_valid(rvalue)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface label is not valid or too long, ignoring assignment: %s", rvalue); return 0; } - free(n->label); - if (*label) - n->label = label; - else { - free(label); - n->label = NULL; - } + r = free_and_strdup(&n->label, rvalue); + if (r < 0) + return log_oom(); n = NULL; diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index 1d3f0b40bf..2b5d452662 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -39,6 +39,6 @@ Network.MACVLAN, config_parse_strv, 0, offsetof(Settings, Network.IPVLAN, config_parse_strv, 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_string, 0, offsetof(Settings, network_bridge) +Network.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge) Network.Zone, config_parse_network_zone, 0, 0 Network.Port, config_parse_expose_port, 0, 0 diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 1141f9964f..83be79a4f5 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -37,6 +37,7 @@ #include "path-util.h" #include "process-util.h" #include "signal-util.h" +#include "socket-util.h" #include "string-util.h" #include "strv.h" #include "syslog-util.h" @@ -873,3 +874,40 @@ int config_parse_personality( *personality = p; return 0; } + +int config_parse_ifname( + 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 **s = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + *s = mfree(*s); + return 0; + } + + if (!ifname_valid(rvalue)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue); + return 0; + } + + r = free_and_strdup(s, rvalue); + if (r < 0) + return log_oom(); + + return 0; +} diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h index 73fb132413..f6964e3fd4 100644 --- a/src/shared/conf-parser.h +++ b/src/shared/conf-parser.h @@ -125,6 +125,7 @@ int config_parse_log_facility(const char *unit, const char *filename, unsigned l int config_parse_log_level(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_signal(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_personality(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_ifname(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); #define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \ int function(const char *unit, \ diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c index ade2de7727..97865eac4a 100644 --- a/src/shared/firewall-util.c +++ b/src/shared/firewall-util.c @@ -44,6 +44,7 @@ #include "firewall-util.h" #include "in-addr-util.h" #include "macro.h" +#include "socket-util.h" DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free); @@ -59,10 +60,9 @@ static int entry_fill_basics( assert(entry); - if (out_interface && strlen(out_interface) >= IFNAMSIZ) + if (out_interface && !ifname_valid(out_interface)) return -EINVAL; - - if (in_interface && strlen(in_interface) >= IFNAMSIZ) + if (in_interface && !ifname_valid(in_interface)) return -EINVAL; entry->ip.proto = protocol; -- cgit v1.2.3-54-g00ecf From 7272b25e163d1c7395ff0da1397511c7946c0873 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 May 2016 21:27:36 +0200 Subject: networkd: reworkd LLDP emission to allow control of propagation level This allows selecting the propagation level of emitted LLDP packets (specifically: the destination MAC address of the packets). This is useful because it allows generating LLDP packets that optionally cross certain types of bridges. See 802.11ab-2009, Table 7-1 for details. --- man/systemd.network.xml | 28 ++++++--- src/network/networkd-link.c | 16 ++--- src/network/networkd-link.h | 2 +- src/network/networkd-lldp-tx.c | 102 +++++++++++++++++++++++++------ src/network/networkd-lldp-tx.h | 14 ++++- src/network/networkd-network-gperf.gperf | 2 +- src/network/networkd-network.h | 3 +- 7 files changed, 126 insertions(+), 41 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 6d45d6c807..70e3804746 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -363,18 +363,26 @@ EmitLLDP= - Controls support for Ethernet LLDP packet emission. Accepts a boolean parameter and defaults to - false. If enabled a short LLDP packet with information about the local system is sent out in regular - intervals on the link. The LLDP packet will contain information about the local host name, the local - machine ID (as stored in - machine-id5) and the + Controls support for Ethernet LLDP packet emission. Accepts a boolean parameter or the special values + nearest-bridge, non-tpmr-bridge and + customer-bridge. Defaults to false, which turns off LLDP packet emission. If not false, + a short LLDP packet with information about the local system is sent out in regular intervals on the + link. The LLDP packet will contain information about the local host name, the local machine ID (as stored + in machine-id5) and the local interface name, as well as the pretty hostname of the system (as set in machine-info5). LLDP - emission is only available on Ethernet links. Note that this setting passed data suitable for - identification of host to the network and should thus not be used on untrusted networks, where such - identification data should not be made available. Use this option to enable other systems to identify on - which interface they are connected to this system. See LLDP= above for an option to - enable LLDP reception. + emission is only available on Ethernet links. Note that this setting passes data suitable for + identification of host to the network and should thus not be enabled on untrusted networks, where such + identification data should not be made available. Use this option to permit other systems to identify on + which interfaces they are connected to this system. The three special values control propagation of the + LLDP packets. The nearest-bridge setting permits propagation only to the nearest + connected bridge, non-tpmr-bridge permits propagation across Two-Port MAC Relays, but + not any other bridges, and customer-bridge permits propagation until a customer bridge + is reached. For details about these concepts, see IEEE 802.1AB-2009. Note that + configuring this setting to true is equivalent to nearest-bridge, the recommended and + most restricted level of propagation. See LLDP= above for an option to enable LLDP + reception. diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index c646af1f1a..4e3f62cf51 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -131,7 +131,7 @@ static bool link_lldp_rx_enabled(Link *link) { return link->network->lldp_mode != LLDP_MODE_NO; } -static bool link_lldp_tx_enabled(Link *link) { +static bool link_lldp_emit_enabled(Link *link) { assert(link); if (link->flags & IFF_LOOPBACK) @@ -143,7 +143,7 @@ static bool link_lldp_tx_enabled(Link *link) { if (!link->network) return false; - return link->network->lldp_emit; + return link->network->lldp_emit != LLDP_EMIT_NO; } static bool link_ipv4_forward_enabled(Link *link) { @@ -491,7 +491,7 @@ static void link_free(Link *link) { sd_dhcp_client_unref(link->dhcp_client); sd_dhcp_lease_unref(link->dhcp_lease); - link_lldp_tx_stop(link); + link_lldp_emit_stop(link); free(link->lease_file); @@ -618,7 +618,7 @@ static int link_stop_clients(Link *link) { r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m"); } - link_lldp_tx_stop(link); + link_lldp_emit_stop(link); return r; } @@ -1411,12 +1411,12 @@ static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n (void) link_lldp_save(link); - if (link_lldp_tx_enabled(link) && event == SD_LLDP_EVENT_ADDED) { + if (link_lldp_emit_enabled(link) && event == SD_LLDP_EVENT_ADDED) { /* If we received information about a new neighbor, restart the LLDP "fast" logic */ log_link_debug(link, "Received LLDP datagram from previously unknown neighbor, restarting 'fast' LLDP transmission."); - r = link_lldp_tx_start(link); + r = link_lldp_emit_start(link); if (r < 0) log_link_warning_errno(link, r, "Failed to restart LLDP transmission: %m"); } @@ -1501,8 +1501,8 @@ static int link_acquire_conf(Link *link) { return r; } - if (link_lldp_tx_enabled(link)) { - r = link_lldp_tx_start(link); + if (link_lldp_emit_enabled(link)) { + r = link_lldp_emit_start(link); if (r < 0) return log_link_warning_errno(link, r, "Failed to start LLDP transmission: %m"); } diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 86139be557..14c4a02c7e 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -121,7 +121,7 @@ typedef struct Link { /* This is about LLDP transmission */ unsigned lldp_tx_fast; /* The LLDP txFast counter (See 802.1ab-2009, section 9.2.5.18) */ - sd_event_source *lldp_tx_event_source; + sd_event_source *lldp_emit_event_source; Hashmap *bound_by_links; Hashmap *bound_to_links; diff --git a/src/network/networkd-lldp-tx.c b/src/network/networkd-lldp-tx.c index 03b694c3f1..3aa768388b 100644 --- a/src/network/networkd-lldp-tx.c +++ b/src/network/networkd-lldp-tx.c @@ -25,16 +25,14 @@ #include "fd-util.h" #include "fileio.h" #include "hostname-util.h" +#include "networkd-lldp-tx.h" +#include "networkd.h" +#include "parse-util.h" #include "random-util.h" #include "socket-util.h" #include "string-util.h" #include "unaligned.h" -#include "networkd.h" -#include "networkd-lldp-tx.h" - -#define LLDP_MULTICAST_ADDR { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e } - /* The LLDP spec calls this "txFastInit", see 9.2.5.19 */ #define LLDP_TX_FAST_INIT 4U @@ -50,6 +48,12 @@ /* The LLDP spec calls this msgFastTx, but we subtract half the jitter off it. */ #define LLDP_FAST_TX_USEC (1U * USEC_PER_SEC - LLDP_JITTER_USEC / 2) +static const struct ether_addr lldp_multicast_addr[_LLDP_EMIT_MAX] = { + [LLDP_EMIT_NEAREST_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }}, + [LLDP_EMIT_NON_TPMR_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }}, + [LLDP_EMIT_CUSTOMER_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }}, +}; + static int lldp_write_tlv_header(uint8_t **p, uint8_t id, size_t sz) { assert(p); @@ -66,6 +70,7 @@ static int lldp_write_tlv_header(uint8_t **p, uint8_t id, size_t sz) { } static int lldp_make_packet( + LLDPEmit mode, const struct ether_addr *hwaddr, const char *machine_id, const char *ifname, @@ -84,6 +89,8 @@ static int lldp_make_packet( size_t l; int r; + assert(mode > LLDP_EMIT_NO); + assert(mode < _LLDP_EMIT_MAX); assert(hwaddr); assert(machine_id); assert(ifname); @@ -132,7 +139,7 @@ static int lldp_make_packet( h = (struct ether_header*) packet; h->ether_type = htobe16(ETHERTYPE_LLDP); - memcpy(h->ether_dhost, &(struct ether_addr) { LLDP_MULTICAST_ADDR }, ETH_ALEN); + memcpy(h->ether_dhost, lldp_multicast_addr + mode, ETH_ALEN); memcpy(h->ether_shost, hwaddr, ETH_ALEN); p = (uint8_t*) packet + sizeof(struct ether_header); @@ -197,22 +204,28 @@ static int lldp_make_packet( return 0; } -static int lldp_send_packet(int ifindex, const void *packet, size_t packet_size) { +static int lldp_send_packet( + int ifindex, + const struct ether_addr *address, + const void *packet, + size_t packet_size) { union sockaddr_union sa = { .ll.sll_family = AF_PACKET, .ll.sll_protocol = htobe16(ETHERTYPE_LLDP), .ll.sll_ifindex = ifindex, .ll.sll_halen = ETH_ALEN, - .ll.sll_addr = LLDP_MULTICAST_ADDR, }; _cleanup_close_ int fd = -1; ssize_t l; assert(ifindex > 0); + assert(address); assert(packet || packet_size <= 0); + memcpy(sa.ll.sll_addr, address, ETH_ALEN); + fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC, IPPROTO_RAW); if (fd < 0) return -errno; @@ -237,6 +250,13 @@ static int link_send_lldp(Link *link) { usec_t ttl; int r; + assert(link); + + if (!link->network || link->network->lldp_emit == LLDP_EMIT_NO) + return 0; + + assert(link->network->lldp_emit < _LLDP_EMIT_MAX); + r = sd_id128_get_machine(&machine_id); if (r < 0) return r; @@ -251,7 +271,8 @@ static int link_send_lldp(Link *link) { SD_LLDP_SYSTEM_CAPABILITIES_ROUTER : SD_LLDP_SYSTEM_CAPABILITIES_STATION; - r = lldp_make_packet(&link->mac, + r = lldp_make_packet(link->network->lldp_emit, + &link->mac, sd_id128_to_string(machine_id, machine_id_string), link->ifname, (uint16_t) ttl, @@ -264,7 +285,7 @@ static int link_send_lldp(Link *link) { if (r < 0) return r; - return lldp_send_packet(link->ifindex, packet, packet_size); + return lldp_send_packet(link->ifindex, lldp_multicast_addr + link->network->lldp_emit, packet, packet_size); } static int on_lldp_timer(sd_event_source *s, usec_t t, void *userdata) { @@ -300,12 +321,17 @@ static int on_lldp_timer(sd_event_source *s, usec_t t, void *userdata) { return 0; } -int link_lldp_tx_start(Link *link) { +int link_lldp_emit_start(Link *link) { usec_t next; int r; assert(link); + if (!link->network || link->network->lldp_emit == LLDP_EMIT_NO) { + link_lldp_emit_stop(link); + return 0; + } + /* Starts the LLDP transmission in "fast" mode. If it is already started, turns "fast" mode back on again. */ link->lldp_tx_fast = LLDP_TX_FAST_INIT; @@ -313,22 +339,22 @@ int link_lldp_tx_start(Link *link) { next = usec_add(usec_add(now(clock_boottime_or_monotonic()), LLDP_FAST_TX_USEC), (usec_t) random_u64() % LLDP_JITTER_USEC); - if (link->lldp_tx_event_source) { + if (link->lldp_emit_event_source) { usec_t old; /* Lower the timeout, maybe */ - r = sd_event_source_get_time(link->lldp_tx_event_source, &old); + r = sd_event_source_get_time(link->lldp_emit_event_source, &old); if (r < 0) return r; if (old <= next) return 0; - return sd_event_source_set_time(link->lldp_tx_event_source, next); + return sd_event_source_set_time(link->lldp_emit_event_source, next); } else { r = sd_event_add_time( link->manager->event, - &link->lldp_tx_event_source, + &link->lldp_emit_event_source, clock_boottime_or_monotonic(), next, 0, @@ -337,14 +363,54 @@ int link_lldp_tx_start(Link *link) { if (r < 0) return r; - (void) sd_event_source_set_description(link->lldp_tx_event_source, "lldp-tx"); + (void) sd_event_source_set_description(link->lldp_emit_event_source, "lldp-tx"); } return 0; } -void link_lldp_tx_stop(Link *link) { +void link_lldp_emit_stop(Link *link) { assert(link); - link->lldp_tx_event_source = sd_event_source_unref(link->lldp_tx_event_source); + link->lldp_emit_event_source = sd_event_source_unref(link->lldp_emit_event_source); +} + +int config_parse_lldp_emit( + 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) { + + LLDPEmit *emit = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) + *emit = LLDP_EMIT_NO; + else if (streq(rvalue, "nearest-bridge")) + *emit = LLDP_EMIT_NEAREST_BRIDGE; + else if (streq(rvalue, "non-tpmr-bridge")) + *emit = LLDP_EMIT_NON_TPMR_BRIDGE; + else if (streq(rvalue, "customer-bridge")) + *emit = LLDP_EMIT_CUSTOMER_BRIDGE; + else { + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse LLDP emission setting, ignoring: %s", rvalue); + return 0; + } + + *emit = r ? LLDP_EMIT_NEAREST_BRIDGE : LLDP_EMIT_NO; + } + + return 0; } diff --git a/src/network/networkd-lldp-tx.h b/src/network/networkd-lldp-tx.h index 8c7f403005..4680c9d950 100644 --- a/src/network/networkd-lldp-tx.h +++ b/src/network/networkd-lldp-tx.h @@ -21,5 +21,15 @@ #include "networkd-link.h" -int link_lldp_tx_start(Link *link); -void link_lldp_tx_stop(Link *link); +typedef enum LLDPEmit { + LLDP_EMIT_NO, + LLDP_EMIT_NEAREST_BRIDGE, + LLDP_EMIT_NON_TPMR_BRIDGE, + LLDP_EMIT_CUSTOMER_BRIDGE, + _LLDP_EMIT_MAX, +} LLDPEmit; + +int link_lldp_emit_start(Link *link); +void link_lldp_emit_stop(Link *link); + +int config_parse_lldp_emit(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); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index a9a541559e..4425ee4e2f 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -42,7 +42,7 @@ Network.LinkLocalAddressing, config_parse_address_family_boolean, Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route) Network.IPv6Token, config_parse_ipv6token, 0, offsetof(Network, ipv6_token) Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode) -Network.EmitLLDP, config_parse_bool, 0, offsetof(Network, lldp_emit) +Network.EmitLLDP, config_parse_lldp_emit, 0, offsetof(Network, lldp_emit) Network.Address, config_parse_address, 0, 0 Network.Gateway, config_parse_gateway, 0, 0 Network.Domains, config_parse_domains, 0, 0 diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index ff2414efdd..4cd0fa4ab8 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -29,6 +29,7 @@ #include "networkd-address.h" #include "networkd-fdb.h" +#include "networkd-lldp-tx.h" #include "networkd-netdev.h" #include "networkd-route.h" #include "networkd-util.h" @@ -161,7 +162,7 @@ struct Network { DUID duid; LLDPMode lldp_mode; /* LLDP reception */ - bool lldp_emit; /* LLDP transmission */ + LLDPEmit lldp_emit; /* LLDP transmission */ LIST_HEAD(Address, static_addresses); LIST_HEAD(Route, static_routes); -- cgit v1.2.3-54-g00ecf From bbc0c09448668bc2b0687cd13f172cda1a9b8131 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 6 May 2016 22:44:13 +0200 Subject: network: allow LLDP packets to cross non-customer bridges for container network interfaces This changes the default .network files we ship for nspawn containers to set EmitLLDP=customer-bridge in order to allow propagation of the LLDP packets across bridges. This is useful so that "networkctl status" shows all peers connected to a virtual container network, collecting this data via LLDP. This is safe since the default configuration for these interfaces does not bridge these links to external interfaces, but relies on IP routing for this. --- network/80-container-host0.network | 2 +- network/80-container-ve.network | 2 +- network/80-container-vz.network | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/network/80-container-host0.network b/network/80-container-host0.network index 561bd1a3b8..b012cf98cb 100644 --- a/network/80-container-host0.network +++ b/network/80-container-host0.network @@ -17,7 +17,7 @@ Name=host0 DHCP=yes LinkLocalAddressing=yes LLDP=yes -EmitLLDP=yes +EmitLLDP=customer-bridge [DHCP] UseTimezone=yes diff --git a/network/80-container-ve.network b/network/80-container-ve.network index c42c7cc259..ac796bfb07 100644 --- a/network/80-container-ve.network +++ b/network/80-container-ve.network @@ -20,4 +20,4 @@ LinkLocalAddressing=yes DHCPServer=yes IPMasquerade=yes LLDP=yes -EmitLLDP=yes +EmitLLDP=customer-bridge diff --git a/network/80-container-vz.network b/network/80-container-vz.network index 69a0607820..3d532d6f60 100644 --- a/network/80-container-vz.network +++ b/network/80-container-vz.network @@ -19,4 +19,4 @@ LinkLocalAddressing=yes DHCPServer=yes IPMasquerade=yes LLDP=yes -EmitLLDP=yes +EmitLLDP=customer-bridge -- cgit v1.2.3-54-g00ecf From a9dd908d093e76f5c19829b6aadd0868bb663c9e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 May 2016 15:42:23 +0200 Subject: network: Make sure we log about parse errors for ifname lists Fix-up for 93e2822684b37a4eeef03775a7a1f44a3055d7b2 --- src/libsystemd-network/network-internal.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index 929f066fa0..2badcdff58 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -200,8 +200,10 @@ int config_parse_ifnames( _cleanup_free_ char *word = NULL; r = extract_first_word(&rvalue, &word, NULL, 0); - if (r < 0) - return r; + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse interface name list: %s", rvalue); + return 0; + } if (r == 0) break; -- cgit v1.2.3-54-g00ecf From 7513c5b89ffbc0f86befca1d76917cd1aa6ff197 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 May 2016 15:43:51 +0200 Subject: nspawn: only remove veth links we created ourselves Let's make sure we don't remove veth links that existed before nspawn was invoked. https://github.com/systemd/systemd/pull/3209#discussion_r62439999 --- src/nspawn/nspawn.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index efda7d66d7..0479389682 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3407,7 +3407,7 @@ int main(int argc, char *argv[]) { int ret = EXIT_SUCCESS; union in_addr_union exposed = {}; _cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; - bool interactive; + bool interactive, veth_created = false; log_parse_environment(); log_open(); @@ -3889,6 +3889,12 @@ int main(int argc, char *argv[]) { if (r < 0) goto finish; + /* We created the primary and extra veth links now; let's remember this, so that we know to + remove them later on. Note that we don't bother with removing veth links that were created + here when their setup failed half-way, because in that case the kernel should be able to + remove them on its own, since they cannot be referenced by anything yet. */ + veth_created = true; + r = setup_macvlan(arg_machine, pid, arg_network_macvlan); if (r < 0) goto finish; @@ -4051,7 +4057,9 @@ int main(int argc, char *argv[]) { } expose_port_flush(arg_expose_ports, &exposed); + (void) remove_veth_links(veth_name, arg_network_veth_extra); + veth_created = false; } finish: @@ -4084,7 +4092,9 @@ finish: } expose_port_flush(arg_expose_ports, &exposed); - (void) remove_veth_links(veth_name, arg_network_veth_extra); + + if (veth_created) + (void) remove_veth_links(veth_name, arg_network_veth_extra); (void) remove_bridge(arg_network_zone); free(arg_directory); -- cgit v1.2.3-54-g00ecf From 54ff1d6913a2b69dc6a544a44b04baf111d8196a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 9 May 2016 15:41:46 +0200 Subject: update TODO --- TODO | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index bf80f86e04..792d63b3e3 100644 --- a/TODO +++ b/TODO @@ -125,7 +125,8 @@ Features: * implement a per-service firewall based on net_cls -* Port various tools to make use of verbs.[ch], where applicable +* Port various tools to make use of verbs.[ch], where applicable: busctl, + bootctl, coredumpctl, hostnamectl, localectl, systemd-analyze, timedatectl * hostnamectl: show root image uuid @@ -287,8 +288,6 @@ Features: * be more careful what we export on the bus as (usec_t) 0 and (usec_t) -1 -* unify dispatch table in systemctl_main() and friends - * rfkill,backlight: we probably should run the load tools inside of the udev rules so that the state is properly initialized by the time other software sees it * After coming back from hibernation reset hibernation swap partition using the /dev/snapshot ioctl APIs -- cgit v1.2.3-54-g00ecf