diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 7a330c12fa..7baf1a9df0 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -2093,13 +2093,39 @@ IPv6Token=prefixstable:2002:da8:1::
- DenyList=
+ RouterDenyList=
+
+ A whitespace-separated list of IPv6 router addresses. Any information advertised by
+ the listed router is ignored.
+
+
+
+
+ RouterAllowList=
+
+ A whitespace-separated list of IPv6 router addresses. Only information advertised by
+ the listed router is accepted. Note that if RouterAllowList= is
+ configured then RouterDenyList= is ignored.
+
+
+
+
+ PrefixDenyList=
A whitespace-separated list of IPv6 prefixes. IPv6 prefixes supplied via router
advertisements in the list are ignored.
+
+ PrefixAllowList=
+
+ A whitespace-separated list of IPv6 prefixes. IPv6 prefixes supplied via router
+ advertisements in the list are allowed. Note that if PrefixAllowList= is
+ configured then PrefixDenyList= is ignored.
+
+
+
RouteDenyList=
@@ -2108,6 +2134,15 @@ IPv6Token=prefixstable:2002:da8:1::
+
+ RouteAllowList=
+
+ A whitespace-separated list of IPv6 route prefixes. IPv6 route prefixes supplied via
+ router advertisements in the list are allowed. Note that if RouteAllowList= is
+ configured then RouteDenyList= is ignored.
+
+
+
DHCPv6Client=
diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c
index db7ada60a8..d02a2a9279 100644
--- a/src/libsystemd-network/sd-ndisc.c
+++ b/src/libsystemd-network/sd-ndisc.c
@@ -221,8 +221,7 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda
if (!rt)
return -ENOMEM;
- r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address,
- &rt->timestamp);
+ r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, &rt->timestamp);
if (r < 0) {
switch (r) {
case -EADDRNOTAVAIL:
diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c
index 61d3c98795..3f1837f591 100644
--- a/src/network/networkd-ndisc.c
+++ b/src/network/networkd-ndisc.c
@@ -69,6 +69,15 @@ void network_adjust_ipv6_accept_ra(Network *network) {
if (network->ipv6_accept_ra < 0)
/* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */
network->ipv6_accept_ra = !FLAGS_SET(network->ip_forward, ADDRESS_FAMILY_IPV6);
+
+ /* When RouterAllowList=, PrefixAllowList= or RouteAllowList= are specified, then
+ * RouterDenyList=, PrefixDenyList= or RouteDenyList= are ignored, respectively. */
+ if (!set_isempty(network->ndisc_allow_listed_router))
+ network->ndisc_deny_listed_router = set_free_free(network->ndisc_deny_listed_router);
+ if (!set_isempty(network->ndisc_allow_listed_prefix))
+ network->ndisc_deny_listed_prefix = set_free_free(network->ndisc_deny_listed_prefix);
+ if (!set_isempty(network->ndisc_allow_listed_route_prefix))
+ network->ndisc_deny_listed_route_prefix = set_free_free(network->ndisc_deny_listed_route_prefix);
}
static int ndisc_remove_old_one(Link *link, const struct in6_addr *router, bool force);
@@ -820,7 +829,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
_cleanup_(route_freep) Route *route = NULL;
- union in_addr_union gateway;
+ union in_addr_union gateway, dst;
uint32_t lifetime;
unsigned preference, prefixlen;
usec_t time_now;
@@ -835,21 +844,30 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
if (lifetime == 0)
return 0;
- r = sd_ndisc_router_get_address(rt, &gateway.in6);
+ r = sd_ndisc_router_route_get_address(rt, &dst.in6);
if (r < 0)
- return log_link_error_errno(link, r, "Failed to get gateway address from RA: %m");
+ return log_link_error_errno(link, r, "Failed to get route address: %m");
- if (set_contains(link->network->ndisc_deny_listed_route_prefix, &gateway.in6)) {
+ if ((!set_isempty(link->network->ndisc_allow_listed_route_prefix) &&
+ !set_contains(link->network->ndisc_allow_listed_route_prefix, &dst.in6)) ||
+ set_contains(link->network->ndisc_deny_listed_route_prefix, &dst.in6)) {
if (DEBUG_LOGGING) {
_cleanup_free_ char *buf = NULL;
- (void) in_addr_to_string(AF_INET6, &gateway, &buf);
- log_link_debug(link, "Route Prefix '%s' is deny-listed, ignoring", strnull(buf));
+ (void) in_addr_to_string(AF_INET6, &dst, &buf);
+ if (!set_isempty(link->network->ndisc_allow_listed_route_prefix))
+ log_link_debug(link, "Route prefix '%s' is not in allow list, ignoring", strnull(buf));
+ else
+ log_link_debug(link, "Route prefix '%s' is in deny list, ignoring", strnull(buf));
}
return 0;
}
- if (link_has_ipv6_address(link, &gateway.in6) == 0) {
+ r = sd_ndisc_router_get_address(rt, &gateway.in6);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get gateway address from RA: %m");
+
+ if (link_has_ipv6_address(link, &gateway.in6) > 0) {
if (DEBUG_LOGGING) {
_cleanup_free_ char *buf = NULL;
@@ -880,15 +898,12 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
route->priority = link->network->dhcp6_route_metric;
route->protocol = RTPROT_RA;
route->pref = preference;
- route->gw.in6 = gateway.in6;
+ route->gw = gateway;
route->gw_family = AF_INET6;
+ route->dst = dst;
route->dst_prefixlen = prefixlen;
route->lifetime = time_now + lifetime * USEC_PER_SEC;
- r = sd_ndisc_router_route_get_address(rt, &route->dst.in6);
- if (r < 0)
- return log_link_error_errno(link, r, "Failed to get route address: %m");
-
r = ndisc_route_configure(route, link, rt);
if (r < 0)
return log_link_error_errno(link, r, "Could not set additional route: %m");
@@ -1095,12 +1110,17 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
if (r < 0)
return log_link_error_errno(link, r, "Failed to get prefix address: %m");
- if (set_contains(link->network->ndisc_deny_listed_prefix, &a.in6)) {
+ if ((!set_isempty(link->network->ndisc_allow_listed_prefix) &&
+ !set_contains(link->network->ndisc_allow_listed_prefix, &a.in6)) ||
+ set_contains(link->network->ndisc_deny_listed_prefix, &a.in6)) {
if (DEBUG_LOGGING) {
_cleanup_free_ char *b = NULL;
(void) in_addr_to_string(AF_INET6, &a, &b);
- log_link_debug(link, "Prefix '%s' is deny-listed, ignoring", strna(b));
+ if (!set_isempty(link->network->ndisc_allow_listed_prefix))
+ log_link_debug(link, "Prefix '%s' is not in allow list, ignoring", strna(b));
+ else
+ log_link_debug(link, "Prefix '%s' is in deny list, ignoring", strna(b));
}
break;
}
@@ -1151,7 +1171,7 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
}
static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
- struct in6_addr router;
+ union in_addr_union router;
uint64_t flags;
NDiscAddress *na;
NDiscRoute *nr;
@@ -1162,21 +1182,36 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
assert(link->manager);
assert(rt);
+ r = sd_ndisc_router_get_address(rt, &router.in6);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get router address from RA: %m");
+
+ if ((!set_isempty(link->network->ndisc_allow_listed_router) &&
+ !set_contains(link->network->ndisc_allow_listed_router, &router.in6)) ||
+ set_contains(link->network->ndisc_deny_listed_router, &router.in6)) {
+ if (DEBUG_LOGGING) {
+ _cleanup_free_ char *buf = NULL;
+
+ (void) in_addr_to_string(AF_INET6, &router, &buf);
+ if (!set_isempty(link->network->ndisc_allow_listed_router))
+ log_link_debug(link, "Router '%s' is not in allow list, ignoring", strna(buf));
+ else
+ log_link_debug(link, "Router '%s' is in deny list, ignoring", strna(buf));
+ }
+ return 0;
+ }
+
link->ndisc_addresses_configured = false;
link->ndisc_routes_configured = false;
link_dirty(link);
- r = sd_ndisc_router_get_address(rt, &router);
- if (r < 0)
- return log_link_error_errno(link, r, "Failed to get router address from RA: %m");
-
SET_FOREACH(na, link->ndisc_addresses)
- if (IN6_ARE_ADDR_EQUAL(&na->router, &router))
+ if (IN6_ARE_ADDR_EQUAL(&na->router, &router.in6))
na->marked = true;
SET_FOREACH(nr, link->ndisc_routes)
- if (IN6_ARE_ADDR_EQUAL(&nr->router, &router))
+ if (IN6_ARE_ADDR_EQUAL(&nr->router, &router.in6))
nr->marked = true;
r = sd_ndisc_router_get_flags(rt, &flags);
@@ -1376,7 +1411,7 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
ipv6_token_compare_func,
free);
-int config_parse_ndisc_deny_listed_prefix(
+int config_parse_ndisc_address_filter(
const char *unit,
const char *filename,
unsigned line,
@@ -1389,7 +1424,6 @@ int config_parse_ndisc_deny_listed_prefix(
void *userdata) {
Set **list = data;
- bool is_route;
int r;
assert(filename);
@@ -1402,8 +1436,6 @@ int config_parse_ndisc_deny_listed_prefix(
return 0;
}
- is_route = streq_ptr(lvalue, "RouteDenyList");
-
for (const char *p = rvalue;;) {
_cleanup_free_ char *n = NULL;
_cleanup_free_ struct in6_addr *a = NULL;
@@ -1414,8 +1446,8 @@ int config_parse_ndisc_deny_listed_prefix(
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to parse NDisc deny-listed %sprefix, ignoring assignment: %s",
- is_route ? "route " : "", rvalue);
+ "Failed to parse NDisc %s=, ignoring assignment: %s",
+ lvalue, rvalue);
return 0;
}
if (r == 0)
@@ -1424,8 +1456,8 @@ int config_parse_ndisc_deny_listed_prefix(
r = in_addr_from_string(AF_INET6, n, &ip);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
- "NDisc deny-listed %sprefix is invalid, ignoring assignment: %s",
- is_route ? "route " : "", n);
+ "NDisc %s= entry is invalid, ignoring assignment: %s",
+ lvalue, n);
continue;
}
@@ -1438,10 +1470,8 @@ int config_parse_ndisc_deny_listed_prefix(
return log_oom();
if (r == 0)
log_syntax(unit, LOG_WARNING, filename, line, 0,
- "NDisc deny-listed %sprefix entry %s is duplicated, ignoring assignment.",
- is_route ? "route " : "", n);
- if (r > 0)
- TAKE_PTR(a);
+ "NDisc %s= entry is duplicated, ignoring assignment: %s",
+ lvalue, n);
}
}
diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h
index 1562411224..e2cb82b346 100644
--- a/src/network/networkd-ndisc.h
+++ b/src/network/networkd-ndisc.h
@@ -77,7 +77,7 @@ int ndisc_configure(Link *link);
void ndisc_vacuum(Link *link);
void ndisc_flush(Link *link);
-CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_deny_listed_prefix);
+CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_address_filter);
CONFIG_PARSER_PROTOTYPE(config_parse_address_generation_type);
CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_accept_ra_start_dhcp6_client);
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index b082ffe6e8..2a6cb6deae 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -237,9 +237,12 @@ IPv6AcceptRA.UseDNS, config_parse_bool,
IPv6AcceptRA.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains)
IPv6AcceptRA.DHCPv6Client, config_parse_ipv6_accept_ra_start_dhcp6_client, 0, offsetof(Network, ipv6_accept_ra_start_dhcp6_client)
IPv6AcceptRA.RouteTable, config_parse_section_route_table, 0, 0
-IPv6AcceptRA.DenyList, config_parse_ndisc_deny_listed_prefix, 0, offsetof(Network, ndisc_deny_listed_prefix)
-IPv6AcceptRA.BlackList, config_parse_ndisc_deny_listed_prefix, 0, offsetof(Network, ndisc_deny_listed_prefix)
-IPv6AcceptRA.RouteDenyList, config_parse_ndisc_deny_listed_prefix, 0, offsetof(Network, ndisc_deny_listed_route_prefix)
+IPv6AcceptRA.RouterAllowList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_allow_listed_router)
+IPv6AcceptRA.RouterDenyList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_deny_listed_router)
+IPv6AcceptRA.PrefixAllowList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_allow_listed_prefix)
+IPv6AcceptRA.PrefixDenyList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_deny_listed_prefix)
+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.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec)
DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec)
DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_DNS].emit)
@@ -471,6 +474,8 @@ DHCP.RapidCommit, config_parse_bool,
DHCP.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information)
DHCPv4.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
DHCPv4.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical)
+IPv6AcceptRA.DenyList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_deny_listed_prefix)
+IPv6AcceptRA.BlackList, config_parse_ndisc_address_filter, 0, offsetof(Network, ndisc_deny_listed_prefix)
TrafficControlQueueingDiscipline.Parent, config_parse_qdisc_parent, _QDISC_KIND_INVALID, 0
TrafficControlQueueingDiscipline.NetworkEmulatorDelaySec, config_parse_network_emulator_delay, 0, 0
TrafficControlQueueingDiscipline.NetworkEmulatorDelayJitterSec, config_parse_network_emulator_delay, 0, 0
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index c037904cdd..daece28dbb 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -606,8 +606,12 @@ static Network *network_free(Network *network) {
ordered_set_free(network->router_search_domains);
free(network->router_dns);
+ set_free_free(network->ndisc_deny_listed_router);
+ set_free_free(network->ndisc_allow_listed_router);
set_free_free(network->ndisc_deny_listed_prefix);
+ set_free_free(network->ndisc_allow_listed_prefix);
set_free_free(network->ndisc_deny_listed_route_prefix);
+ set_free_free(network->ndisc_allow_listed_route_prefix);
free(network->bridge_name);
free(network->bond_name);
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index feda19bbc1..32f5ae8d72 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -256,8 +256,12 @@ struct Network {
DHCPUseDomains ipv6_accept_ra_use_domains;
IPv6AcceptRAStartDHCP6Client ipv6_accept_ra_start_dhcp6_client;
uint32_t ipv6_accept_ra_route_table;
+ Set *ndisc_deny_listed_router;
+ Set *ndisc_allow_listed_router;
Set *ndisc_deny_listed_prefix;
+ Set *ndisc_allow_listed_prefix;
Set *ndisc_deny_listed_route_prefix;
+ Set *ndisc_allow_listed_route_prefix;
OrderedSet *ipv6_tokens;
/* LLDP support */
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 6b7cbb3559..f57f0cd561 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -310,9 +310,14 @@ UseDNS=
DHCPv6Client=
UseAutonomousPrefix=
UseOnLinkPrefix=
+RouterAllowList=
+RouterDenyList=
+PrefixAllowList=
+PrefixDenyList=
+RouteAllowList=
+RouteDenyList=
DenyList=
BlackList=
-RouteDenyList=
[DHCPServer]
EmitNTP=
PoolSize=
diff --git a/test/test-network/conf/ipv6ra-prefix-client-deny-list.network b/test/test-network/conf/ipv6ra-prefix-client-deny-list.network
new file mode 100644
index 0000000000..ce7a76f70c
--- /dev/null
+++ b/test/test-network/conf/ipv6ra-prefix-client-deny-list.network
@@ -0,0 +1,11 @@
+[Match]
+Name=veth-peer
+
+[Network]
+DHCP=no
+IPv6AcceptRA=yes
+
+[IPv6AcceptRA]
+RouterDenyList=2001::1
+PrefixDenyList=2001:db8:0:2::
+RouteDenyList=2001:db1:fff::
diff --git a/test/test-network/conf/ipv6ra-prefix-client.network b/test/test-network/conf/ipv6ra-prefix-client.network
index bc40b123c7..58883658f3 100644
--- a/test/test-network/conf/ipv6ra-prefix-client.network
+++ b/test/test-network/conf/ipv6ra-prefix-client.network
@@ -4,3 +4,10 @@ Name=veth-peer
[Network]
DHCP=no
IPv6AcceptRA=yes
+
+[IPv6AcceptRA]
+# PrefixDenyList= and RouteDenyList= will be ignored.
+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::
diff --git a/test/test-network/conf/ipv6ra-prefix.network b/test/test-network/conf/ipv6ra-prefix.network
index a0ac1e4537..cfb03f50c4 100644
--- a/test/test-network/conf/ipv6ra-prefix.network
+++ b/test/test-network/conf/ipv6ra-prefix.network
@@ -15,3 +15,7 @@ Assign=yes
[IPv6RoutePrefix]
Route=2001:db0:fff::/64
LifetimeSec=1000
+
+[IPv6RoutePrefix]
+Route=2001:db1:fff::/64
+LifetimeSec=1000
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index 454e6ce66b..1c4046d4f2 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -4274,6 +4274,7 @@ class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities):
units = [
'25-veth.netdev',
+ 'ipv6ra-prefix-client-deny-list.network',
'ipv6ra-prefix-client.network',
'ipv6ra-prefix.network'
]
@@ -4294,14 +4295,45 @@ class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities):
start_networkd()
self.wait_online(['veth99:routable', 'veth-peer:routable'])
+ output = check_output('ip address show dev veth-peer')
+ print(output)
+ self.assertIn('inet6 2001:db8:0:1:', output)
+ self.assertNotIn('inet6 2001:db8:0:2:', output)
+
output = check_output('ip -6 route show dev veth-peer')
print(output)
- self.assertRegex(output, '2001:db8:0:1::/64 proto ra')
+ self.assertIn('2001:db8:0:1::/64 proto ra', output)
+ self.assertNotIn('2001:db8:0:2::/64 proto ra', output)
+ self.assertIn('2001:db0:fff::/64 via ', output)
+ self.assertNotIn('2001:db1:fff::/64 via ', output)
- output = check_output('ip addr show dev veth99')
+ output = check_output('ip address show dev veth99')
print(output)
- self.assertNotRegex(output, '2001:db8:0:1')
- self.assertRegex(output, '2001:db8:0:2')
+ self.assertNotIn('inet6 2001:db8:0:1:', output)
+ self.assertIn('inet6 2001:db8:0:2:', 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')
+
+ start_networkd()
+ self.wait_online(['veth99:routable', 'veth-peer:routable'])
+
+ output = check_output('ip address show dev veth-peer')
+ print(output)
+ self.assertIn('inet6 2001:db8:0:1:', output)
+ self.assertNotIn('inet6 2001:db8:0:2:', output)
+
+ output = check_output('ip -6 route show dev veth-peer')
+ print(output)
+ self.assertIn('2001:db8:0:1::/64 proto ra', output)
+ self.assertNotIn('2001:db8:0:2::/64 proto ra', output)
+ self.assertIn('2001:db0:fff::/64 via ', output)
+ self.assertNotIn('2001:db1:fff::/64 via ', output)
+
+ output = check_output('ip address show dev veth99')
+ print(output)
+ self.assertNotIn('inet6 2001:db8:0:1:', output)
+ self.assertIn('inet6 2001:db8:0:2:', output)
class NetworkdMTUTests(unittest.TestCase, Utilities):
links = ['dummy98']