diff --git a/man/systemd.network.xml b/man/systemd.network.xml index cdb6f4eb87b..b6bf993adc0 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2123,6 +2123,17 @@ Table=1234 is enabled. + + UplinkInterface= + + Specifies the name or the index of the uplink interface, or one of the special values + :self and :auto. When :self, the + interface itself is considered the uplink interface. When :auto, the first + link which acquired prefixes to be delegated from the DHCPv6 server is selected. Defaults to + :auto. + + + SubnetId= diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index 3a77a457802..0c0a76d362d 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -1281,6 +1281,7 @@ int config_parse_uplink( void *userdata) { Network *network = userdata; + bool accept_none = true; int *index, r; char **name; @@ -1288,6 +1289,7 @@ int config_parse_uplink( assert(section); assert(lvalue); assert(rvalue); + assert(userdata); if (streq(section, "DHCPServer")) { index = &network->dhcp_server_uplink_index; @@ -1295,6 +1297,10 @@ int config_parse_uplink( } else if (streq(section, "IPv6SendRA")) { index = &network->router_uplink_index; name = &network->router_uplink_name; + } else if (streq(section, "DHCPv6PrefixDelegation")) { + index = &network->dhcp6_pd_uplink_index; + name = &network->dhcp_server_uplink_name; + accept_none = false; } else assert_not_reached(); @@ -1304,12 +1310,18 @@ int config_parse_uplink( return 0; } - if (streq(rvalue, ":none")) { + if (accept_none && streq(rvalue, ":none")) { *index = UPLINK_INDEX_NONE; *name = mfree(*name); return 0; } + if (!accept_none && streq(rvalue, ":self")) { + *index = UPLINK_INDEX_SELF; + *name = mfree(*name); + return 0; + } + r = parse_ifindex(rvalue); if (r > 0) { *index = r; diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index 176f6c04fe0..d45b92e1a98 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -12,6 +12,7 @@ /* Special values for *_uplink_index. */ #define UPLINK_INDEX_AUTO 0 /* uplink will be selected automatically */ #define UPLINK_INDEX_NONE -1 /* uplink will not be selected automatically */ +#define UPLINK_INDEX_SELF -2 /* the interface itself is uplink */ #define DHCP_ROUTE_METRIC 1024 #define DHCP6PD_ROUTE_METRIC 256 diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 01850d49c52..52ff5eb3aab 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -42,6 +42,22 @@ bool link_dhcp6_pd_is_enabled(Link *link) { return link->network->dhcp6_pd; } +static int dhcp6_pd_resolve_uplink(Link *link, Link **ret) { + if (link->network->dhcp6_pd_uplink_name) + return link_get_by_name(link->manager, link->network->dhcp6_pd_uplink_name, ret); + + if (link->network->dhcp6_pd_uplink_index > 0) + return link_get_by_index(link->manager, link->network->dhcp6_pd_uplink_index, ret); + + if (link->network->dhcp6_pd_uplink_index == UPLINK_INDEX_SELF) { + *ret = link; + return 0; + } + + assert(link->network->dhcp6_pd_uplink_index == UPLINK_INDEX_AUTO); + return -ENOENT; +} + static bool dhcp6_lease_has_pd_prefix(sd_dhcp6_lease *lease) { uint32_t lifetime_preferred_sec, lifetime_valid_sec; struct in6_addr pd_prefix; @@ -575,6 +591,7 @@ static int dhcp6_pd_distribute_prefix( assert(pd_prefix_len <= 64); HASHMAP_FOREACH(link, dhcp6_link->manager->links_by_index) { + Link *uplink; if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) continue; @@ -591,6 +608,15 @@ static int dhcp6_pd_distribute_prefix( if (assign_preferred_subnet_id != link_has_preferred_subnet_id(link)) continue; + r = dhcp6_pd_resolve_uplink(link, &uplink); + if (r != -ENOENT) { + if (r < 0) /* The uplink interface does not exist yet. */ + continue; + + if (uplink != dhcp6_link) + continue; + } + r = dhcp6_pd_assign_prefix(link, pd_prefix, pd_prefix_len, lifetime_preferred_usec, lifetime_valid_usec); if (r < 0) { if (link == dhcp6_link) @@ -1402,8 +1428,17 @@ static int dhcp6_pd_find_uplink(Link *link, Link **ret) { assert(link); assert(link->manager); + assert(link->network); assert(ret); + if (dhcp6_pd_resolve_uplink(link, &l) >= 0) { + if (!dhcp6_pd_uplink_is_ready(l)) + return -EBUSY; + + *ret = l; + return 0; + } + HASHMAP_FOREACH(l, link->manager->links_by_index) { if (!dhcp6_pd_uplink_is_ready(l)) continue; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 758aa8ee19e..ccbf02b99d9 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -325,6 +325,7 @@ BridgeMDB.VLANId, config_parse_mdb_vlan_id, BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0 BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0 BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0 +DHCPv6PrefixDelegation.UplinkInterface, config_parse_uplink, 0, 0 DHCPv6PrefixDelegation.SubnetId, config_parse_dhcp6_pd_subnet_id, 0, offsetof(Network, dhcp6_pd_subnet_id) DHCPv6PrefixDelegation.Announce, config_parse_bool, 0, offsetof(Network, dhcp6_pd_announce) DHCPv6PrefixDelegation.Assign, config_parse_bool, 0, offsetof(Network, dhcp6_pd_assign) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 3972fc5c892..bd9758c899a 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -241,6 +241,8 @@ struct Network { int64_t dhcp6_pd_subnet_id; uint32_t dhcp6_pd_route_metric; Set *dhcp6_pd_tokens; + int dhcp6_pd_uplink_index; + char *dhcp6_pd_uplink_name; /* Bridge Support */ int use_bpdu; diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index dae13a243cd..985c2fc0d3f 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -151,6 +151,7 @@ DUIDType= DUIDRawData= RouteTable= [DHCPv6PrefixDelegation] +UplinkInterface= SubnetId= Announce= Assign=