diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index b8bb8aedf4..299c56df0a 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -2382,12 +2382,12 @@ IPv6Token=prefixstable:2002:da8:1::
UplinkInterface=
- Specifies name or index of uplink interface, or one of the special values
- :none and :auto. When emitting DNS, NTP, or SIP servers
- are enabled but no servers are specified, the servers configured in the uplink interface will
- be emitted. When :auto, the link which has default gateway with higher
- priority will be automatically selected. When :none, no uplink interface
- will be selected. Defaults to :auto.
+ Specifies the name or the index of the uplink interface, or one of the special
+ values :none and :auto. When emitting DNS, NTP, or SIP
+ servers is enabled but no servers are specified, the servers configured in the uplink interface
+ will be emitted. When :auto, the link which has a default gateway with the
+ highest priority will be automatically selected. When :none, no uplink
+ interface will be selected. Defaults to :auto.
@@ -2586,18 +2586,28 @@ IPv6Token=prefixstable:2002:da8:1::
for details. Defaults to medium.
+
+ UplinkInterface=
+ Specifies the name or the index of the uplink interface, or one of the special
+ values :none and :auto. When emitting DNS servers or
+ search domains is enabled but no servers are specified, the servers configured in the uplink
+ interface will be emitted. When :auto, the link which has a default gateway
+ with the highest priority will be automatically selected. When :none, no
+ uplink interface will be selected. Defaults to :auto.
+
+
EmitDNS=
DNS=
- DNS= specifies a list of recursive DNS server IPv6 addresses that
- are distributed via Router Advertisement messages when EmitDNS= is
- true. DNS= also takes special value _link_local; in that case
- the IPv6 link local address is distributed. If DNS= is empty, DNS servers are read
- from the [Network] section. If the [Network] section does not contain any DNS servers either, DNS
- servers from the uplink with the highest priority default route are used. When
- EmitDNS= is false, no DNS server information is sent in Router Advertisement
- messages. EmitDNS= defaults to true.
+ DNS= specifies a list of recursive DNS server IPv6 addresses
+ that are distributed via Router Advertisement messages when EmitDNS= is true.
+ DNS= also takes special value _link_local; in that case
+ the IPv6 link local address is distributed. If DNS= is empty, DNS servers are
+ read from the [Network] section. If the [Network] section does not contain any DNS servers
+ either, DNS servers from the uplink interface specified in UplinkInterface=
+ will be used. When EmitDNS= is false, no DNS server information is sent in
+ Router Advertisement messages. EmitDNS= defaults to true.
@@ -2605,11 +2615,12 @@ IPv6Token=prefixstable:2002:da8:1::
Domains=
A list of DNS search domains distributed via Router Advertisement messages when
- EmitDomains= is true. If Domains= is empty, DNS search domains
- are read from the [Network] section. If the [Network] section does not contain any DNS search domains
- either, DNS search domains from the uplink with the highest priority default route are used. When
- EmitDomains= is false, no DNS search domain information is sent in Router
- Advertisement messages. EmitDomains= defaults to true.
+ EmitDomains= is true. If Domains= is empty, DNS search
+ domains are read from the [Network] section. If the [Network] section does not contain any DNS
+ search domains either, DNS search domains from the uplink interface specified in
+ UplinkInterface= will be used. When EmitDomains= is false,
+ no DNS search domain information is sent in Router Advertisement messages.
+ EmitDomains= defaults to true.
diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c
index e4be034a4c..f190bcf428 100644
--- a/src/network/networkd-dhcp-server.c
+++ b/src/network/networkd-dhcp-server.c
@@ -106,7 +106,7 @@ static int dhcp_server_find_uplink(Link *link, Link **ret) {
if (link->network->dhcp_server_uplink_index > 0)
return link_get_by_index(link->manager, link->network->dhcp_server_uplink_index, ret);
- if (link->network->dhcp_server_uplink_index == 0) {
+ if (link->network->dhcp_server_uplink_index == UPLINK_INDEX_AUTO) {
/* It is not necessary to propagate error in automatic selection. */
if (manager_find_uplink(link->manager, AF_INET, link, ret) < 0)
*ret = NULL;
@@ -663,55 +663,3 @@ int config_parse_dhcp_server_address(
network->dhcp_server_address_prefixlen = prefixlen;
return 0;
}
-
-int config_parse_dhcp_server_uplink(
- 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) {
-
- Network *network = userdata;
- int r;
-
- assert(filename);
- assert(lvalue);
- assert(rvalue);
-
- if (isempty(rvalue) || streq(rvalue, ":auto")) {
- network->dhcp_server_uplink_index = 0; /* uplink will be selected automatically */
- network->dhcp_server_uplink_name = mfree(network->dhcp_server_uplink_name);
- return 0;
- }
-
- if (streq(rvalue, ":none")) {
- network->dhcp_server_uplink_index = -1; /* uplink will not be selected automatically */
- network->dhcp_server_uplink_name = mfree(network->dhcp_server_uplink_name);
- return 0;
- }
-
- r = parse_ifindex(rvalue);
- if (r > 0) {
- network->dhcp_server_uplink_index = r;
- network->dhcp_server_uplink_name = mfree(network->dhcp_server_uplink_name);
- return 0;
- }
-
- if (!ifname_valid_full(rvalue, IFNAME_VALID_ALTERNATIVE)) {
- log_syntax(unit, LOG_WARNING, filename, line, 0,
- "Invalid interface name in %s=, ignoring assignment: %s", lvalue, rvalue);
- return 0;
- }
-
- r = free_and_strdup_warn(&network->dhcp_server_uplink_name, rvalue);
- if (r < 0)
- return r;
-
- network->dhcp_server_uplink_index = 0;
- return 0;
-}
diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h
index 3f23f97bb8..a02cd995ec 100644
--- a/src/network/networkd-dhcp-server.h
+++ b/src/network/networkd-dhcp-server.h
@@ -15,4 +15,3 @@ int request_process_dhcp_server(Request *req);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_agent_suboption);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_address);
-CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_uplink);
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 3a56f89f0b..17fb932f81 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -618,12 +618,8 @@ static int link_acquire_dynamic_ipv6_conf(Link *link) {
log_link_debug(link, "Starting IPv6 Router Advertisements");
- r = radv_emit_dns(link);
- if (r < 0)
- return log_link_warning_errno(link, r, "Failed to configure DNS or Domains in IPv6 Router Advertisement: %m");
-
r = sd_radv_start(link->radv);
- if (r < 0 && r != -EBUSY)
+ if (r < 0)
return log_link_warning_errno(link, r, "Could not start IPv6 Router Advertisement: %m");
}
@@ -1147,7 +1143,7 @@ static int link_configure(Link *link) {
if (r < 0)
return r;
- r = radv_configure(link);
+ r = link_request_radv(link);
if (r < 0)
return r;
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index de4120c47e..9166376aaa 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -265,7 +265,7 @@ IPv6AcceptRA.PrefixDenyList, config_parse_ndisc_address_filter,
IPv6AcceptRA.RouteAllowList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_allow_listed_route_prefix)
IPv6AcceptRA.RouteDenyList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_deny_listed_route_prefix)
DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0
-DHCPServer.UplinkInterface, config_parse_dhcp_server_uplink, 0, 0
+DHCPServer.UplinkInterface, config_parse_uplink, 0, 0
DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target)
DHCPServer.RelayAgentCircuitId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_circuit_id)
DHCPServer.RelayAgentRemoteId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_remote_id)
@@ -333,6 +333,7 @@ IPv6SendRA.DNS, config_parse_radv_dns,
IPv6SendRA.EmitDomains, config_parse_bool, 0, offsetof(Network, router_emit_domains)
IPv6SendRA.Domains, config_parse_radv_search_domains, 0, 0
IPv6SendRA.DNSLifetimeSec, config_parse_sec, 0, offsetof(Network, router_dns_lifetime_usec)
+IPv6SendRA.UplinkInterface, config_parse_uplink, 0, 0
IPv6Prefix.Prefix, config_parse_prefix, 0, 0
IPv6Prefix.OnLink, config_parse_prefix_flags, 0, 0
IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 850b4f449e..f16553a6d9 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -640,6 +640,7 @@ static Network *network_free(Network *network) {
free(network->dhcp_server_timezone);
free(network->dhcp_server_uplink_name);
+ free(network->router_uplink_name);
for (sd_dhcp_lease_server_type_t t = 0; t < _SD_DHCP_LEASE_SERVER_TYPE_MAX; t++)
free(network->dhcp_server_emit[t].addresses);
@@ -1185,6 +1186,73 @@ int config_parse_link_group(
return 0;
}
+
+int config_parse_uplink(
+ 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) {
+
+ Network *network = userdata;
+ int *index, r;
+ char **name;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (streq(section, "DHCPServer")) {
+ index = &network->dhcp_server_uplink_index;
+ name = &network->dhcp_server_uplink_name;
+ } else if (streq(section, "IPv6SendRA")) {
+ index = &network->router_uplink_index;
+ name = &network->router_uplink_name;
+ } else
+ assert_not_reached();
+
+ if (isempty(rvalue) || streq(rvalue, ":auto")) {
+ *index = UPLINK_INDEX_AUTO;
+ *name = mfree(*name);
+ return 0;
+ }
+
+ if (streq(rvalue, ":none")) {
+ *index = UPLINK_INDEX_NONE;
+ *name = mfree(*name);
+ return 0;
+ }
+
+ r = parse_ifindex(rvalue);
+ if (r > 0) {
+ *index = r;
+ *name = mfree(*name);
+ return 0;
+ }
+
+ if (!ifname_valid_full(rvalue, IFNAME_VALID_ALTERNATIVE)) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Invalid interface name in %s=, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ /* The interface name will be resolved later. */
+ r = free_and_strdup_warn(name, rvalue);
+ if (r < 0)
+ return r;
+
+ /* Note, if uplink_name is set, then uplink_index will be ignored. So, the below does not mean
+ * an uplink interface will be selected automatically. */
+ *index = UPLINK_INDEX_AUTO;
+ return 0;
+}
+
DEFINE_CONFIG_PARSE_ENUM(config_parse_required_family_for_online, link_required_address_family, AddressFamily,
"Failed to parse RequiredFamilyForOnline= setting");
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index b39063fe8a..bf0e1a733e 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -26,6 +26,10 @@
#include "resolve-util.h"
#include "socket-netlink.h"
+/* 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 */
+
typedef enum KeepConfiguration {
KEEP_CONFIGURATION_NO = 0,
KEEP_CONFIGURATION_DHCP_ON_START = 1 << 0,
@@ -224,6 +228,8 @@ struct Network {
struct in6_addr *router_dns;
unsigned n_router_dns;
OrderedSet *router_search_domains;
+ int router_uplink_index;
+ char *router_uplink_name;
/* DHCPv6 Prefix Delegation support */
int dhcp6_pd;
@@ -364,6 +370,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_keep_configuration);
CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_link_local_address_gen_mode);
CONFIG_PARSER_PROTOTYPE(config_parse_activation_policy);
CONFIG_PARSER_PROTOTYPE(config_parse_link_group);
+CONFIG_PARSER_PROTOTYPE(config_parse_uplink);
const struct ConfigPerfItem* network_network_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c
index db682d434b..27a16f2951 100644
--- a/src/network/networkd-queue.c
+++ b/src/network/networkd-queue.c
@@ -43,6 +43,8 @@ static void request_free_object(RequestType type, void *object) {
case REQUEST_TYPE_NEXTHOP:
nexthop_free(object);
break;
+ case REQUEST_TYPE_RADV:
+ break;
case REQUEST_TYPE_ROUTE:
route_free(object);
break;
@@ -110,7 +112,7 @@ static void request_hash_func(const Request *req, struct siphash *state) {
trivial_hash_func(req->object, state);
break;
case REQUEST_TYPE_DHCP_SERVER:
- /* This type does not have object. */
+ /* This type does not have an object. */
break;
case REQUEST_TYPE_IPV6_PROXY_NDP:
in6_addr_hash_func(req->ipv6_proxy_ndp, state);
@@ -121,6 +123,9 @@ static void request_hash_func(const Request *req, struct siphash *state) {
case REQUEST_TYPE_NEXTHOP:
nexthop_hash_func(req->nexthop, state);
break;
+ case REQUEST_TYPE_RADV:
+ /* This type does not have an object. */
+ break;
case REQUEST_TYPE_ROUTE:
route_hash_func(req->route, state);
break;
@@ -174,6 +179,8 @@ static int request_compare_func(const struct Request *a, const struct Request *b
return nexthop_compare_func(a->nexthop, b->nexthop);
case REQUEST_TYPE_ROUTE:
return route_compare_func(a->route, b->route);
+ case REQUEST_TYPE_RADV:
+ return 0;
case REQUEST_TYPE_ROUTING_POLICY_RULE:
return routing_policy_rule_compare_func(a->rule, b->rule);
case REQUEST_TYPE_SET_LINK:
@@ -211,10 +218,14 @@ int link_queue_request(
assert(IN_SET(type,
REQUEST_TYPE_ACTIVATE_LINK,
REQUEST_TYPE_DHCP_SERVER,
+ REQUEST_TYPE_RADV,
REQUEST_TYPE_SET_LINK,
REQUEST_TYPE_UP_DOWN) ||
object);
- assert(type == REQUEST_TYPE_DHCP_SERVER || netlink_handler);
+ assert(IN_SET(type,
+ REQUEST_TYPE_DHCP_SERVER,
+ REQUEST_TYPE_RADV) ||
+ netlink_handler);
req = new(Request, 1);
if (!req) {
@@ -298,6 +309,9 @@ int manager_process_requests(sd_event_source *s, void *userdata) {
case REQUEST_TYPE_NEXTHOP:
r = request_process_nexthop(req);
break;
+ case REQUEST_TYPE_RADV:
+ r = request_process_radv(req);
+ break;
case REQUEST_TYPE_ROUTE:
r = request_process_route(req);
break;
diff --git a/src/network/networkd-queue.h b/src/network/networkd-queue.h
index 1f4b15f087..85d2be7367 100644
--- a/src/network/networkd-queue.h
+++ b/src/network/networkd-queue.h
@@ -31,6 +31,7 @@ typedef enum RequestType {
REQUEST_TYPE_IPV6_PROXY_NDP,
REQUEST_TYPE_NEIGHBOR,
REQUEST_TYPE_NEXTHOP,
+ REQUEST_TYPE_RADV,
REQUEST_TYPE_ROUTE,
REQUEST_TYPE_ROUTING_POLICY_RULE,
REQUEST_TYPE_SET_LINK,
diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c
index 805d3fff6c..953a12b572 100644
--- a/src/network/networkd-radv.c
+++ b/src/network/networkd-radv.c
@@ -10,6 +10,7 @@
#include "networkd-link.h"
#include "networkd-manager.h"
#include "networkd-network.h"
+#include "networkd-queue.h"
#include "networkd-radv.h"
#include "parse-util.h"
#include "string-util.h"
@@ -583,10 +584,7 @@ static int radv_set_dns(Link *link, Link *uplink) {
goto set_dns;
if (uplink) {
- if (!uplink->network) {
- log_link_debug(uplink, "Cannot fetch DNS servers as uplink interface is not managed by us");
- return 0;
- }
+ assert(uplink->network);
r = network_get_ipv6_dns(uplink->network, &dns, &n_dns);
if (r > 0)
@@ -595,7 +593,7 @@ static int radv_set_dns(Link *link, Link *uplink) {
return 0;
- set_dns:
+set_dns:
return sd_radv_set_rdnss(link->radv,
DIV_ROUND_UP(lifetime_usec, USEC_PER_SEC),
dns, n_dns);
@@ -622,10 +620,7 @@ static int radv_set_domains(Link *link, Link *uplink) {
goto set_domains;
if (uplink) {
- if (!uplink->network) {
- log_link_debug(uplink, "Cannot fetch DNS search domains as uplink interface is not managed by us");
- return 0;
- }
+ assert(uplink->network);
search_domains = uplink->network->search_domains;
if (search_domains)
@@ -634,7 +629,7 @@ static int radv_set_domains(Link *link, Link *uplink) {
return 0;
- set_domains:
+set_domains:
s = ordered_set_get_strv(search_domains);
if (!s)
return log_oom();
@@ -645,20 +640,23 @@ static int radv_set_domains(Link *link, Link *uplink) {
}
-int radv_emit_dns(Link *link) {
- Link *uplink = NULL;
- int r;
+static int radv_find_uplink(Link *link, Link **ret) {
+ assert(link);
- (void) manager_find_uplink(link->manager, AF_INET6, link, &uplink);
+ if (link->network->router_uplink_name)
+ return link_get_by_name(link->manager, link->network->router_uplink_name, ret);
- r = radv_set_dns(link, uplink);
- if (r < 0)
- log_link_warning_errno(link, r, "Could not set RA DNS: %m");
+ if (link->network->router_uplink_index > 0)
+ return link_get_by_index(link->manager, link->network->router_uplink_index, ret);
- r = radv_set_domains(link, uplink);
- if (r < 0)
- log_link_warning_errno(link, r, "Could not set RA Domains: %m");
+ if (link->network->router_uplink_index == UPLINK_INDEX_AUTO) {
+ /* It is not necessary to propagate error in automatic selection. */
+ if (manager_find_uplink(link->manager, AF_INET6, link, ret) < 0)
+ *ret = NULL;
+ return 0;
+ }
+ *ret = NULL;
return 0;
}
@@ -671,8 +669,9 @@ static bool link_radv_enabled(Link *link) {
return link->network->router_prefix_delegation;
}
-int radv_configure(Link *link) {
+static int radv_configure(Link *link) {
uint16_t router_lifetime;
+ Link *uplink = NULL;
RoutePrefix *q;
Prefix *p;
int r;
@@ -680,9 +679,6 @@ int radv_configure(Link *link) {
assert(link);
assert(link->network);
- if (!link_radv_enabled(link))
- return 0;
-
if (link->radv)
return -EBUSY;
@@ -748,6 +744,16 @@ int radv_configure(Link *link) {
return r;
}
+ (void) radv_find_uplink(link, &uplink);
+
+ r = radv_set_dns(link, uplink);
+ if (r < 0)
+ return log_link_debug_errno(link, r, "Could not set RA DNS: %m");
+
+ r = radv_set_domains(link, uplink);
+ if (r < 0)
+ return log_link_debug_errno(link, r, "Could not set RA Domains: %m");
+
return 0;
}
@@ -779,6 +785,96 @@ int radv_update_mac(Link *link) {
return 0;
}
+static int radv_is_ready_to_configure(Link *link) {
+ bool needs_uplink = false;
+ int r;
+
+ assert(link);
+ assert(link->network);
+
+ if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED))
+ return false;
+
+ if (in6_addr_is_null(&link->ipv6ll_address))
+ return false;
+
+ if (link->network->router_emit_dns && !link->network->router_dns) {
+ _cleanup_free_ struct in6_addr *dns = NULL;
+ size_t n_dns;
+
+ r = network_get_ipv6_dns(link->network, &dns, &n_dns);
+ if (r < 0)
+ return r;
+
+ needs_uplink = r == 0;
+ }
+
+ if (link->network->router_emit_domains &&
+ !link->network->router_search_domains &&
+ !link->network->search_domains)
+ needs_uplink = true;
+
+ if (needs_uplink) {
+ Link *uplink = NULL;
+
+ if (radv_find_uplink(link, &uplink) < 0)
+ return false;
+
+ if (uplink && !uplink->network)
+ return false;
+ }
+
+ return true;
+}
+
+int request_process_radv(Request *req) {
+ Link *link;
+ int r;
+
+ assert(req);
+ assert(req->link);
+ assert(req->type == REQUEST_TYPE_RADV);
+
+ link = req->link;
+
+ r = radv_is_ready_to_configure(link);
+ if (r <= 0)
+ return r;
+
+ r = radv_configure(link);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to configure IPv6 Router Advertisement engine: %m");
+
+ if (link_has_carrier(link)) {
+ r = sd_radv_start(link->radv);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to start IPv6 Router Advertisement engine: %m");
+ }
+
+ log_link_debug(link, "IPv6 Router Advertisement engine is configured%s.",
+ link_has_carrier(link) ? " and started." : "");
+ return 1;
+}
+
+int link_request_radv(Link *link) {
+ int r;
+
+ assert(link);
+
+ if (!link_radv_enabled(link))
+ return 0;
+
+ if (link->radv)
+ return 0;
+
+ r = link_queue_request(link, REQUEST_TYPE_RADV, NULL, false, NULL, NULL, NULL);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to request IPv6 Router Advertisement engine: %m");
+
+ log_link_debug(link, "IPv6 Router Advertisement engine is requested.");
+ return 0;
+}
+
int radv_add_prefix(
Link *link,
const struct in6_addr *prefix,
diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h
index f6efd32697..a8adb9bce1 100644
--- a/src/network/networkd-radv.h
+++ b/src/network/networkd-radv.h
@@ -14,8 +14,9 @@
#include "conf-parser.h"
#include "networkd-util.h"
-typedef struct Network Network;
typedef struct Link Link;
+typedef struct Network Network;
+typedef struct Request Request;
typedef enum RADVPrefixDelegation {
RADV_PREFIX_DELEGATION_NONE = 0,
@@ -50,12 +51,13 @@ void network_drop_invalid_prefixes(Network *network);
void network_drop_invalid_route_prefixes(Network *network);
void network_adjust_radv(Network *network);
-int radv_emit_dns(Link *link);
-int radv_configure(Link *link);
int radv_update_mac(Link *link);
int radv_add_prefix(Link *link, const struct in6_addr *prefix, uint8_t prefix_len,
uint32_t lifetime_preferred, uint32_t lifetime_valid);
+int request_process_radv(Request *req);
+int link_request_radv(Link *link);
+
const char* radv_prefix_delegation_to_string(RADVPrefixDelegation i) _const_;
RADVPrefixDelegation radv_prefix_delegation_from_string(const char *s) _pure_;
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index f1305a04b2..2dca1cda3a 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -307,6 +307,7 @@ EmitDNS=
EmitDomains=
Managed=
OtherInformation=
+UplinkInterface=
[IPv6PrefixDelegation]
RouterPreference=
DNSLifetimeSec=
diff --git a/test/test-network/conf/dhcp-server-with-ipv6-prefix.network b/test/test-network/conf/dhcp-server-with-ipv6-prefix.network
index 7a1cfc65e1..4c89035139 100644
--- a/test/test-network/conf/dhcp-server-with-ipv6-prefix.network
+++ b/test/test-network/conf/dhcp-server-with-ipv6-prefix.network
@@ -13,3 +13,7 @@ Prefix=2002:da8:1:0::/64
[DHCPServer]
DNS=192.168.5.1
NTP=192.168.5.1
+
+[IPv6SendRA]
+EmitDNS=no
+EmitDomains=no
diff --git a/test/test-network/conf/ipv6ra-prefix-client-deny-list.network b/test/test-network/conf/ipv6ra-prefix-client-deny-list.network
index ce7a76f70c..be0d2e40c5 100644
--- a/test/test-network/conf/ipv6ra-prefix-client-deny-list.network
+++ b/test/test-network/conf/ipv6ra-prefix-client-deny-list.network
@@ -9,3 +9,4 @@ IPv6AcceptRA=yes
RouterDenyList=2001::1
PrefixDenyList=2001:db8:0:2::
RouteDenyList=2001:db1:fff::
+UseDomains=yes
diff --git a/test/test-network/conf/ipv6ra-prefix-client.network b/test/test-network/conf/ipv6ra-prefix-client.network
index 58883658f3..e18ecaf290 100644
--- a/test/test-network/conf/ipv6ra-prefix-client.network
+++ b/test/test-network/conf/ipv6ra-prefix-client.network
@@ -11,3 +11,4 @@ PrefixAllowList=2001:db8:0:1:: 2001:db8:0:1::
PrefixDenyList=2001:db8:0:1:: 2001:db8:0:1::
RouteAllowList=2001:db0:fff:: 2001:db0:fff::
RouteDenyList=2001:db0:fff:: 2001:db0:fff::
+UseDomains=yes
diff --git a/test/test-network/conf/ipv6ra-prefix.network b/test/test-network/conf/ipv6ra-prefix.network
index cfb03f50c4..72ad808b71 100644
--- a/test/test-network/conf/ipv6ra-prefix.network
+++ b/test/test-network/conf/ipv6ra-prefix.network
@@ -5,6 +5,9 @@ Name=veth99
DHCP=no
IPv6SendRA=yes
+[IPv6SendRA]
+UplinkInterface=dummy98
+
[IPv6Prefix]
Prefix=2001:db8:0:1::/64
diff --git a/test/test-network/conf/ipv6ra-uplink.network b/test/test-network/conf/ipv6ra-uplink.network
new file mode 100644
index 0000000000..9146def210
--- /dev/null
+++ b/test/test-network/conf/ipv6ra-uplink.network
@@ -0,0 +1,8 @@
+[Match]
+Name=dummy98
+
+[Network]
+IPv6AcceptRA=no
+Address=2001:db8:1:1::1/64
+DNS=2001:db8:1:1::2
+Domains=example.com
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index dc31d133c4..11e97e0ade 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -4685,14 +4685,19 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99')
class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities):
- links = ['veth99']
+ links = [
+ 'dummy98',
+ 'veth99',
+ ]
units = [
+ '12-dummy.netdev',
'25-veth.netdev',
'ipv6ra-prefix-client-deny-list.network',
'ipv6ra-prefix-client.network',
- 'ipv6ra-prefix.network'
- ]
+ 'ipv6ra-prefix.network',
+ 'ipv6ra-uplink.network',
+ ]
def setUp(self):
remove_links(self.links)
@@ -4705,10 +4710,11 @@ class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities):
stop_networkd(show_logs=True)
def test_ipv6_route_prefix(self):
- copy_unit_to_networkd_unit_path('25-veth.netdev', 'ipv6ra-prefix-client.network', 'ipv6ra-prefix.network')
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'ipv6ra-prefix-client.network', 'ipv6ra-prefix.network',
+ '12-dummy.netdev', 'ipv6ra-uplink.network')
start_networkd()
- self.wait_online(['veth99:routable', 'veth-peer:routable'])
+ self.wait_online(['veth99:routable', 'veth-peer:routable', 'dummy98:routable'])
output = check_output('ip address show dev veth-peer')
print(output)
@@ -4727,11 +4733,20 @@ class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities):
self.assertNotIn('inet6 2001:db8:0:1:', output)
self.assertIn('inet6 2001:db8:0:2:', output)
+ output = check_output(*resolvectl_cmd, 'dns', 'veth-peer', env=env)
+ print(output)
+ self.assertRegex(output, '2001:db8:1:1::2')
+
+ output = check_output(*resolvectl_cmd, 'domain', 'veth-peer', env=env)
+ print(output)
+ self.assertIn('example.com', output)
+
def test_ipv6_route_prefix_deny_list(self):
- copy_unit_to_networkd_unit_path('25-veth.netdev', 'ipv6ra-prefix-client-deny-list.network', 'ipv6ra-prefix.network')
+ copy_unit_to_networkd_unit_path('25-veth.netdev', 'ipv6ra-prefix-client-deny-list.network', 'ipv6ra-prefix.network',
+ '12-dummy.netdev', 'ipv6ra-uplink.network')
start_networkd()
- self.wait_online(['veth99:routable', 'veth-peer:routable'])
+ self.wait_online(['veth99:routable', 'veth-peer:routable', 'dummy98:routable'])
output = check_output('ip address show dev veth-peer')
print(output)
@@ -4750,6 +4765,14 @@ class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities):
self.assertNotIn('inet6 2001:db8:0:1:', output)
self.assertIn('inet6 2001:db8:0:2:', output)
+ output = check_output(*resolvectl_cmd, 'dns', 'veth-peer', env=env)
+ print(output)
+ self.assertRegex(output, '2001:db8:1:1::2')
+
+ output = check_output(*resolvectl_cmd, 'domain', 'veth-peer', env=env)
+ print(output)
+ self.assertIn('example.com', output)
+
class NetworkdMTUTests(unittest.TestCase, Utilities):
links = ['dummy98']