From: Yu Watanabe Date: Thu, 23 Jul 2026 18:27:22 +0000 (+0900) Subject: network: make VLAN= and friends take multiple names X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b8b7c0467247b503d7288af61d6fc7f52ffc77fb;p=thirdparty%2Fsystemd.git network: make VLAN= and friends take multiple names Previously, specifying multiple stacked netdevs of the same type required repeating the corresponding setting, e.g.: ``` [Network] VLAN=vlan_10 VLAN=vlan_20 VLAN=vlan_30 ``` With this change, the same configuration can be written as: ``` [Network] VLAN=vlan_10 vlan_20 vlan_30 ``` Specifying an empty string now also clears all previously assigned stacked netdevs. Closes #43103. --- diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 48d4ffce1a4..1e4f2df49fa 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1298,10 +1298,10 @@ DuplicateAddressDetection=none VXLAN= Xfrm= - The name of an IPoIB, IPVLAN, IPVTAP, MACsec, MACVLAN, MACVTAP, tunnel, VLAN, - VXLAN, or Xfrm to be created on the link. See + Takes a space-separated list of stacked netdevs to be created on the interface. See systemd.netdev5. - This option may be specified more than once. + This option may be specified more than once. If an empty string is specified, previous assignments + for all netdev types are cleared. diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 02ded9ce8be..61a247ee9fb 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -6,6 +6,7 @@ #include "condition.h" #include "conf-files.h" #include "conf-parser.h" +#include "extract-word.h" #include "in-addr-util.h" #include "iovec-util.h" #include "net-condition.h" @@ -951,7 +952,6 @@ int config_parse_stacked_netdev( void *data, void *userdata) { - _cleanup_free_ char *name = NULL; NetDevKind kind = ltype; Hashmap **h = ASSERT_PTR(data); int r; @@ -971,29 +971,38 @@ int config_parse_stacked_netdev( NETDEV_KIND_XFRM, _NETDEV_KIND_TUNNEL)); - if (!ifname_valid(rvalue)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid netdev name in %s=, ignoring assignment: %s", lvalue, rvalue); + if (isempty(rvalue)) { + *h = hashmap_free(*h); return 0; } - name = strdup(rvalue); - if (!name) - return log_oom(); + for (const char *p = rvalue;;) { + _cleanup_free_ char *name = NULL; - r = hashmap_ensure_put(h, &string_hash_ops_free, name, INT_TO_PTR(kind)); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) - log_syntax(unit, LOG_WARNING, filename, line, r, - "Cannot add NetDev '%s' to network, ignoring assignment: %m", name); - else if (r == 0) - log_syntax(unit, LOG_DEBUG, filename, line, r, - "NetDev '%s' specified twice, ignoring.", name); - else - TAKE_PTR(name); + r = extract_first_word(&p, &name, /* separators= */ NULL, /* flags= */ 0); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + if (r == 0) + return 0; - return 0; + if (!ifname_valid(name)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid netdev name in %s=, ignoring assignment: %s", lvalue, name); + continue; + } + + r = hashmap_ensure_put(h, &string_hash_ops_free, name, INT_TO_PTR(kind)); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Cannot add NetDev '%s' to network, ignoring assignment: %m", name); + else if (r == 0) + log_syntax(unit, LOG_DEBUG, filename, line, r, + "NetDev '%s' specified twice, ignoring.", name); + else + TAKE_PTR(name); + } } int config_parse_required_for_online( diff --git a/src/network/test-networkd-conf.c b/src/network/test-networkd-conf.c index 31b89fda2fa..7cde3b9fa98 100644 --- a/src/network/test-networkd-conf.c +++ b/src/network/test-networkd-conf.c @@ -466,4 +466,30 @@ TEST(config_parse_multipath_route) { test_config_parse_multipath_route_one("@wg0 abc", 0, 0); /* Non-numeric */ } +TEST(config_parse_stacked_netdev) { + _cleanup_hashmap_free_ Hashmap *netdevs = NULL; + ASSERT_OK(config_parse_stacked_netdev("network", "filename", 1, "section", 1, "VLAN", NETDEV_KIND_VLAN, "foo bar baz invalid:name", &netdevs, NULL)); + ASSERT_OK(config_parse_stacked_netdev("network", "filename", 1, "section", 1, "VXLAN", NETDEV_KIND_VXLAN, "aaa 321 foo bbb", &netdevs, NULL)); + + static const struct { + const char *name; + NetDevKind kind; + } expected[] = { + { "foo", NETDEV_KIND_VLAN }, + { "bar", NETDEV_KIND_VLAN }, + { "baz", NETDEV_KIND_VLAN }, + { "aaa", NETDEV_KIND_VXLAN }, + { "bbb", NETDEV_KIND_VXLAN }, + }; + + ASSERT_EQ(hashmap_size(netdevs), ELEMENTSOF(expected)); + FOREACH_ELEMENT(i, expected) { + void *p = ASSERT_NOT_NULL(hashmap_get(netdevs, i->name)); + ASSERT_EQ(PTR_TO_INT(p), i->kind); + } + + ASSERT_OK(config_parse_stacked_netdev("network", "filename", 1, "section", 1, "VXLAN", NETDEV_KIND_VXLAN, "", &netdevs, NULL)); + ASSERT_NULL(netdevs); +} + DEFINE_TEST_MAIN(LOG_INFO);