From 0c7bb54238fd01520b2d8b8a25c30503d0c8fd7c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 4 Aug 2024 17:59:50 +0900 Subject: [PATCH 1/3] network: split out core logic route_type_is_reject() Preparation for later change. --- src/network/networkd-route-nexthop.c | 18 +++++++++--------- src/network/networkd-route-util.c | 8 +++++--- src/network/networkd-route-util.h | 3 ++- src/network/networkd-route.c | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/network/networkd-route-nexthop.c b/src/network/networkd-route-nexthop.c index 11215c36c7..f2d976beb1 100644 --- a/src/network/networkd-route-nexthop.c +++ b/src/network/networkd-route-nexthop.c @@ -148,7 +148,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( route_nexthop_free); static size_t route_n_nexthops(const Route *route) { - if (route->nexthop_id != 0 || route_type_is_reject(route)) + if (route->nexthop_id != 0 || route_is_reject(route)) return 0; if (ordered_set_isempty(route->nexthops)) @@ -246,7 +246,7 @@ int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest) { assert(src); assert(dest); - if (src->nexthop_id != 0 || route_type_is_reject(src)) + if (src->nexthop_id != 0 || route_is_reject(src)) return 0; if (nh) @@ -292,7 +292,7 @@ bool route_nexthops_needs_adjust(const Route *route) { * Hence, we cannot know if the nexthop is blackhole or not. */ return route->type != RTN_BLACKHOLE; - if (route_type_is_reject(route)) + if (route_is_reject(route)) return false; if (ordered_set_isempty(route->nexthops)) @@ -350,7 +350,7 @@ int route_adjust_nexthops(Route *route, Link *link) { return true; /* updated */ } - if (route_type_is_reject(route)) + if (route_is_reject(route)) return false; if (ordered_set_isempty(route->nexthops)) @@ -439,7 +439,7 @@ int route_nexthops_is_ready_to_configure(const Route *route, Manager *manager) { return true; } - if (route_type_is_reject(route)) + if (route_is_reject(route)) return true; if (ordered_set_isempty(route->nexthops)) @@ -468,7 +468,7 @@ int route_nexthops_to_string(const Route *route, char **ret) { return 0; } - if (route_type_is_reject(route)) { + if (route_is_reject(route)) { buf = strdup("gw: n/a"); if (!buf) return -ENOMEM; @@ -625,7 +625,7 @@ int route_nexthops_set_netlink_message(const Route *route, sd_netlink_message *m if (route->nexthop_id != 0) return sd_netlink_message_append_u32(message, RTA_NH_ID, route->nexthop_id); - if (route_type_is_reject(route)) + if (route_is_reject(route)) return 0; /* We request IPv6 multipath routes separately. Even though, if weight is non-zero, we need to use @@ -750,7 +750,7 @@ int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *messag if (r < 0 && r != -ENODATA) return log_warning_errno(r, "rtnl: received route message with invalid nexthop id, ignoring: %m"); - if (route->nexthop_id != 0 || route_type_is_reject(route)) + if (route->nexthop_id != 0 || route_is_reject(route)) /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's * fib6_nh_init() in net/ipv6/route.c. However, we'd like to make it consistent with IPv4 * routes. Hence, skip reading of RTA_OIF. */ @@ -899,7 +899,7 @@ int route_section_verify_nexthops(Route *route) { "Ignoring [Route] section from line %u.", route->section->filename, route->section->line); - if (route_type_is_reject(route) && + if (route_is_reject(route) && (route->gateway_from_dhcp_or_ra || in_addr_is_set(route->nexthop.family, &route->nexthop.gw) || !ordered_set_isempty(route->nexthops))) diff --git a/src/network/networkd-route-util.c b/src/network/networkd-route-util.c index 8d63b9b058..9bb1e0f707 100644 --- a/src/network/networkd-route-util.c +++ b/src/network/networkd-route-util.c @@ -39,10 +39,12 @@ unsigned routes_max(void) { return cached; } -bool route_type_is_reject(const Route *route) { - assert(route); +bool route_type_is_reject(uint8_t type) { + return IN_SET(type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW); +} - return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW); +bool route_is_reject(const Route *route) { + return route_type_is_reject(ASSERT_PTR(route)->type); } static bool route_lifetime_is_valid(const Route *route) { diff --git a/src/network/networkd-route-util.h b/src/network/networkd-route-util.h index 2df7faadc8..2905a4913b 100644 --- a/src/network/networkd-route-util.h +++ b/src/network/networkd-route-util.h @@ -13,7 +13,8 @@ typedef struct Route Route; unsigned routes_max(void); -bool route_type_is_reject(const Route *route); +bool route_type_is_reject(uint8_t type); +bool route_is_reject(const Route *route); bool link_find_default_gateway(Link *link, int family, Route **gw); static inline bool link_has_default_gateway(Link *link, int family) { diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 9b6be2f2bf..d9a6e259db 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -912,7 +912,7 @@ int link_request_route( assert(route); assert(route->source != NETWORK_CONFIG_SOURCE_FOREIGN); - if (route->family == AF_INET || route_type_is_reject(route) || ordered_set_isempty(route->nexthops)) + if (route->family == AF_INET || route_is_reject(route) || ordered_set_isempty(route->nexthops)) return link_request_route_one(link, route, NULL, message_counter, netlink_handler); RouteNextHop *nh; From a61869d4a59220d19ed2d4f79f4fe11dfccd9375 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 4 Aug 2024 18:18:51 +0900 Subject: [PATCH 2/3] network/dhcp-pd: allow to customize route type for delegated prefix Closes #33929. --- man/systemd.network.xml | 34 ++++++++++ src/network/networkd-dhcp-prefix-delegation.c | 64 ++++++++++++++++++- src/network/networkd-dhcp-prefix-delegation.h | 1 + src/network/networkd-network-gperf.gperf | 2 + src/network/networkd-network.c | 2 + src/network/networkd-network.h | 2 + 6 files changed, 104 insertions(+), 1 deletion(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index b4d438823d..ebcb6d0481 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2762,6 +2762,23 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix + + UnassignedSubnetPolicy= + + Takes none, or one of the reject types: unreachable, + prohibit, blackhole, or throw. If a reject + type is specified, the reject route corresponding to the acquired 6RD prefix will be configured. + For example, when unreachable, + unreachable 2001:db8::/56 dev lo proto dhcp metric 1024 pref medium + will be configured. See RFC 7084. + If none is specified, such route will not be configured. This may be useful when + custom firewall rules that handle packets for unassigned subnets will be configured. + Defaults to unreachable. + + + + + IPv6OnlyMode= @@ -3002,6 +3019,23 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix + + UnassignedSubnetPolicy= + + Takes none or one of the reject types: unreachable, + prohibit, blackhole, or throw. If a reject + type is specified, the reject route corresponding to the delegated prefix will be configured. + For example, when unreachable, + unreachable 2001:db8::/56 dev lo proto dhcp metric 1024 pref medium + will be configured. See RFC 7084. + If none is specified, such route will not be configured. This may be useful when + custom firewall rules that handle packets for unassigned subnets will be configured. + Defaults to unreachable. + + + + + RapidCommit= diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index 2e660b7763..1ffb4888dd 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -13,6 +13,7 @@ #include "networkd-manager.h" #include "networkd-queue.h" #include "networkd-radv.h" +#include "networkd-route-util.h" #include "networkd-route.h" #include "networkd-setlink.h" #include "parse-util.h" @@ -697,6 +698,7 @@ static int dhcp_request_unreachable_route( usec_t lifetime_usec, NetworkConfigSource source, const union in_addr_union *server_address, + uint8_t type, /* RTN_* */ unsigned *counter, route_netlink_handler_t callback, bool *configured) { @@ -710,10 +712,14 @@ static int dhcp_request_unreachable_route( assert(addr); assert(IN_SET(source, NETWORK_CONFIG_SOURCE_DHCP4, NETWORK_CONFIG_SOURCE_DHCP6)); assert(server_address); + assert(type == RTN_UNSPEC || route_type_is_reject(type)); assert(counter); assert(callback); assert(configured); + if (type == RTN_UNSPEC) + return 0; /* Disabled. */ + if (prefixlen >= 64) { log_link_debug(link, "Not adding a blocking route for DHCP delegated prefix %s since the prefix has length >= 64.", IN6_ADDR_PREFIX_TO_STRING(addr, prefixlen)); @@ -729,7 +735,7 @@ static int dhcp_request_unreachable_route( route->family = AF_INET6; route->dst.in6 = *addr; route->dst_prefixlen = prefixlen; - route->type = RTN_UNREACHABLE; + route->type = type; route->protocol = RTPROT_DHCP; route->priority = IP6_RT_PRIO_USER; route->lifetime_usec = lifetime_usec; @@ -758,8 +764,12 @@ static int dhcp4_request_unreachable_route( usec_t lifetime_usec, const union in_addr_union *server_address) { + assert(link); + assert(link->network); + return dhcp_request_unreachable_route(link, addr, prefixlen, lifetime_usec, NETWORK_CONFIG_SOURCE_DHCP4, server_address, + link->network->dhcp_6rd_prefix_route_type, &link->dhcp4_messages, dhcp4_unreachable_route_handler, &link->dhcp4_configured); } @@ -771,8 +781,12 @@ static int dhcp6_request_unreachable_route( usec_t lifetime_usec, const union in_addr_union *server_address) { + assert(link); + assert(link->network); + return dhcp_request_unreachable_route(link, addr, prefixlen, lifetime_usec, NETWORK_CONFIG_SOURCE_DHCP6, server_address, + link->network->dhcp6_pd_prefix_route_type, &link->dhcp6_messages, dhcp6_unreachable_route_handler, &link->dhcp6_configured); } @@ -1297,3 +1311,51 @@ int config_parse_dhcp_pd_subnet_id( return 0; } + +int config_parse_dhcp_pd_prefix_route_type( + 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) { + + uint8_t *p = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *p = RTN_UNREACHABLE; /* Defaults to unreachable. */ + return 0; + } + + if (streq(rvalue, "none")) { + *p = RTN_UNSPEC; /* Indicate that the route is disabled. */ + return 0; + } + + r = route_type_from_string(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + if (!route_type_is_reject(r)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid route type is specified to %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + *p = r; + return 0; +} diff --git a/src/network/networkd-dhcp-prefix-delegation.h b/src/network/networkd-dhcp-prefix-delegation.h index 4a8cca92b6..0d68a51d0f 100644 --- a/src/network/networkd-dhcp-prefix-delegation.h +++ b/src/network/networkd-dhcp-prefix-delegation.h @@ -23,3 +23,4 @@ void dhcp4_pd_prefix_lost(Link *uplink); int dhcp_pd_reconfigure_address(Address *address, Link *link); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_pd_subnet_id); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_pd_prefix_route_type); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 23e25ffc1f..aa849fe535 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -269,6 +269,7 @@ DHCPv4.InitialCongestionWindow, config_parse_tcp_window, DHCPv4.InitialAdvertisedReceiveWindow, config_parse_tcp_window, 0, offsetof(Network, dhcp_advertised_receive_window) DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0 DHCPv4.Use6RD, config_parse_bool, 0, offsetof(Network, dhcp_use_6rd) +DHCPv4.UnassignedSubnetPolicy, config_parse_dhcp_pd_prefix_route_type, 0, offsetof(Network, dhcp_6rd_prefix_route_type) DHCPv4.IPv6OnlyMode, config_parse_tristate, 0, offsetof(Network, dhcp_ipv6_only_mode) DHCPv4.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_netlabel) DHCPv4.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp_nft_set_context) @@ -288,6 +289,7 @@ DHCPv6.UserClass, config_parse_dhcp_user_or_vendor_cl DHCPv6.VendorClass, config_parse_dhcp_user_or_vendor_class, AF_INET6, offsetof(Network, dhcp6_vendor_class) DHCPv6.SendVendorOption, config_parse_dhcp_send_option, AF_INET6, offsetof(Network, dhcp6_client_send_vendor_options) DHCPv6.PrefixDelegationHint, config_parse_dhcp6_pd_prefix_hint, 0, 0 +DHCPv6.UnassignedSubnetPolicy, config_parse_dhcp_pd_prefix_route_type, 0, offsetof(Network, dhcp6_pd_prefix_route_type) DHCPv6.WithoutRA, config_parse_dhcp6_client_start_mode, 0, offsetof(Network, dhcp6_client_start_mode) DHCPv6.SendOption, config_parse_dhcp_send_option, AF_INET6, offsetof(Network, dhcp6_client_send_options) DHCPv6.IAID, config_parse_iaid, AF_INET6, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 8232db06c9..4e4e5eb2a8 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -402,6 +402,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_ip_service_type = -1, .dhcp_broadcast = -1, .dhcp_ipv6_only_mode = -1, + .dhcp_6rd_prefix_route_type = RTN_UNREACHABLE, .dhcp6_use_address = true, .dhcp6_use_pd_prefix = true, @@ -415,6 +416,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp6_duid.type = _DUID_TYPE_INVALID, .dhcp6_client_start_mode = _DHCP6_CLIENT_START_MODE_INVALID, .dhcp6_send_release = true, + .dhcp6_pd_prefix_route_type = RTN_UNREACHABLE, .dhcp_pd = -1, .dhcp_pd_announce = true, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 92d367eccf..66a8328e29 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -167,6 +167,7 @@ struct Network { bool dhcp_use_timezone; bool dhcp_use_hostname; bool dhcp_use_6rd; + uint8_t dhcp_6rd_prefix_route_type; bool dhcp_send_release; bool dhcp_send_decline; UseDomains dhcp_use_domains; @@ -195,6 +196,7 @@ struct Network { DUID dhcp6_duid; uint8_t dhcp6_pd_prefix_length; struct in6_addr dhcp6_pd_prefix_hint; + uint8_t dhcp6_pd_prefix_route_type; char *dhcp6_hostname; char *dhcp6_mudurl; char **dhcp6_user_class; From d133508493f13ed7d98356966cc5feb049b919f5 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Aug 2024 11:18:45 +0900 Subject: [PATCH 3/3] test-network: add test case for UnassignedSubnetPolicy= --- .../conf/25-dhcp6pd-upstream-no-address.network | 1 + .../conf/25-dhcp6pd-upstream-no-assign.network | 1 + test/test-network/systemd-networkd-tests.py | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/test/test-network/conf/25-dhcp6pd-upstream-no-address.network b/test/test-network/conf/25-dhcp6pd-upstream-no-address.network index 01f0e9e6d5..051bd40029 100644 --- a/test/test-network/conf/25-dhcp6pd-upstream-no-address.network +++ b/test/test-network/conf/25-dhcp6pd-upstream-no-address.network @@ -11,6 +11,7 @@ DHCPPrefixDelegation=yes [DHCPv6] WithoutRA=solicit UseAddress=no +UnassignedSubnetPolicy=none [DHCPPrefixDelegation] UplinkInterface=:self diff --git a/test/test-network/conf/25-dhcp6pd-upstream-no-assign.network b/test/test-network/conf/25-dhcp6pd-upstream-no-assign.network index 5f76390cec..7662251de1 100644 --- a/test/test-network/conf/25-dhcp6pd-upstream-no-assign.network +++ b/test/test-network/conf/25-dhcp6pd-upstream-no-assign.network @@ -10,6 +10,7 @@ DHCPPrefixDelegation=yes [DHCPv6] WithoutRA=solicit +UnassignedSubnetPolicy=blackhole [DHCPPrefixDelegation] UplinkInterface=:self diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 01d871047a..4ab6c0749f 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -7147,6 +7147,11 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities): print(output) self.assertNotIn('inet6 3ffe:501:ffff', output) + print('### ip -6 route show dev lo') + output = check_output('ip -6 route show dev lo') + print(output) + self.assertNotRegex(output, '3ffe:501:ffff:[2-9a-f]00::/56') + self.check_dhcp6_prefix('veth99') @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available on CentOS Stream 10") @@ -7165,6 +7170,11 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities): print(output) self.assertNotIn('inet6 3ffe:501:ffff', output) + print('### ip -6 route show type blackhole') + output = check_output('ip -6 route show type blackhole') + print(output) + self.assertRegex(output, 'blackhole 3ffe:501:ffff:[2-9a-f]00::/56 dev lo proto dhcp') + self.check_dhcp6_prefix('veth99') @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available on CentOS Stream 10")