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.
This commit is contained in:
Yu Watanabe
2026-07-24 03:27:22 +09:00
parent 841ebcbb7d
commit b8b7c04672
3 changed files with 57 additions and 22 deletions

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

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

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