]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: make VLAN= and friends take multiple names
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 23 Jul 2026 18:27:22 +0000 (03:27 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 24 Jul 2026 04:44:04 +0000 (13:44 +0900)
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.

man/systemd.network.xml
src/network/networkd-network.c
src/network/test-networkd-conf.c

index 48d4ffce1a48415140f7d1db9ed320bde95ecc2b..1e4f2df49fa352606988cc362e744514be2d93c6 100644 (file)
@@ -1298,10 +1298,10 @@ DuplicateAddressDetection=none</programlisting></para>
         <term><varname>VXLAN=</varname></term>
         <term><varname>Xfrm=</varname></term>
         <listitem>
-          <para>The name of an IPoIB, IPVLAN, IPVTAP, MACsec, MACVLAN, MACVTAP, tunnel, VLAN,
-          VXLAN, or Xfrm to be created on the link. See
+          <para>Takes a space-separated list of stacked netdevs to be created on the interface. See
           <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
-          This option may be specified more than once.</para>
+          This option may be specified more than once. If an empty string is specified, previous assignments
+          for all netdev types are cleared.</para>
 
           <xi:include href="version-info.xml" xpointer="v211"/>
         </listitem>
index 02ded9ce8be43b8f5e29e63dae1fff8a4c6af9ca..61a247ee9fb4f32a4ef3c4738d99aeaee6a4252c 100644 (file)
@@ -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(
index 31b89fda2fa4d0342bcec20aa9a8c191c5368130..7cde3b9fa983e521f010a6f94e3a830840605889 100644 (file)
@@ -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);