From fc35a9f8d1632c4e7a279228f869bfc77d8f5b9c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 20 Sep 2023 14:29:06 +0900 Subject: [PATCH] network/dhcp4: support IPv6 only mode (RFC 8925) Co-authored-by: Susant Sahani --- man/systemd.network.xml | 13 ++++++ src/network/networkd-address.c | 15 ++++++- src/network/networkd-dhcp4.c | 51 +++++++++++++++++++++++- src/network/networkd-dhcp4.h | 6 ++- src/network/networkd-link.c | 27 +++++++++++++ src/network/networkd-link.h | 1 + src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + src/network/networkd-route-util.c | 23 +++++++---- src/network/networkd-route-util.h | 6 +++ src/network/networkd-route.c | 11 +++++ 12 files changed, 144 insertions(+), 12 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 804d51be11c..d939c29f716 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -2573,6 +2573,19 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix + + IPv6OnlyMode= + + When true, the DHCPv4 configuration will be delayed by the timespan provided by the DHCP + server and skip to configure dynamic IPv4 network connectivity if IPv6 connectivity is provided + within the timespan. See RFC 8925. + Defaults to true when IPv6AcceptRA= is enabled or DHCPv6 client is enabled + (i.e., DHCP=yes), and false otherwise. + + + + + FallbackLeaseLifetimeSec= diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index e2e448b44f5..c1a8cd884a8 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -1669,7 +1669,7 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, uint16_t type; Address *address = NULL; Request *req = NULL; - bool is_new = false; + bool is_new = false, update_dhcp4; int ifindex, r; assert(rtnl); @@ -1778,6 +1778,8 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, assert_not_reached(); } + update_dhcp4 = tmp->family == AF_INET6; + /* Then, find the managed Address and Request objects corresponding to the received address. */ (void) address_get(link, tmp, &address); (void) address_get_request(link, tmp, &req); @@ -1793,7 +1795,7 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, if (req) address_enter_removed(req->userdata); - return 0; + goto finalize; } if (!address) { @@ -1879,6 +1881,15 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, if (r < 0) link_enter_failed(link); +finalize: + if (update_dhcp4) { + r = dhcp4_update_ipv6_connectivity(link); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to notify IPv6 connectivity to DHCPv4 client: %m"); + link_enter_failed(link); + } + } + return 1; } diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 3e8038ede6b..57d40e856ee 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1452,6 +1452,16 @@ static bool link_needs_dhcp_broadcast(Link *link) { return r == true; } +static bool link_dhcp4_ipv6_only_mode(Link *link) { + assert(link); + assert(link->network); + + if (link->network->dhcp_ipv6_only_mode >= 0) + return link->network->dhcp_ipv6_only_mode; + + return link_dhcp6_enabled(link) || link_ipv6_accept_ra_enabled(link); +} + static int dhcp4_configure(Link *link) { sd_dhcp_option *send_option; void *request_options; @@ -1560,6 +1570,12 @@ static int dhcp4_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for 6rd: %m"); } + if (link_dhcp4_ipv6_only_mode(link)) { + r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for IPv6-only preferred option: %m"); + } + SET_FOREACH(request_options, link->network->dhcp_request_options) { uint32_t option = PTR_TO_UINT32(request_options); @@ -1668,7 +1684,7 @@ int dhcp4_update_mac(Link *link) { return r; if (restart) { - r = sd_dhcp_client_start(link->dhcp_client); + r = dhcp4_start(link); if (r < 0) return r; } @@ -1676,10 +1692,35 @@ int dhcp4_update_mac(Link *link) { return 0; } -int dhcp4_start(Link *link) { +int dhcp4_update_ipv6_connectivity(Link *link) { + assert(link); + + if (!link->network) + return 0; + + if (!link->network->dhcp_ipv6_only_mode) + return 0; + + if (!link->dhcp_client) + return 0; + + /* If the client is running, set the current connectivity. */ + if (sd_dhcp_client_is_running(link->dhcp_client)) + return sd_dhcp_client_set_ipv6_connectivity(link->dhcp_client, link_has_ipv6_connectivity(link)); + + /* If the client has been already stopped or not started yet, let's check the current connectivity + * and start the client if necessary. */ + if (link_has_ipv6_connectivity(link)) + return 0; + + return dhcp4_start_full(link, /* set_ipv6_connectivity = */ false); +} + +int dhcp4_start_full(Link *link, bool set_ipv6_connectivity) { int r; assert(link); + assert(link->network); if (!link->dhcp_client) return 0; @@ -1694,6 +1735,12 @@ int dhcp4_start(Link *link) { if (r < 0) return r; + if (set_ipv6_connectivity) { + r = dhcp4_update_ipv6_connectivity(link); + if (r < 0) + return r; + } + return 1; } diff --git a/src/network/networkd-dhcp4.h b/src/network/networkd-dhcp4.h index 7446f0a1311..d36b0585464 100644 --- a/src/network/networkd-dhcp4.h +++ b/src/network/networkd-dhcp4.h @@ -15,7 +15,11 @@ typedef enum DHCPClientIdentifier { void network_adjust_dhcp4(Network *network); int dhcp4_update_mac(Link *link); -int dhcp4_start(Link *link); +int dhcp4_update_ipv6_connectivity(Link *link); +int dhcp4_start_full(Link *link, bool set_ipv6_connectivity); +static inline int dhcp4_start(Link *link) { + return dhcp4_start_full(link, true); +} int dhcp4_lease_lost(Link *link); int dhcp4_check_ready(Link *link); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index a7b7200423c..555b19d580b 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -52,6 +52,7 @@ #include "networkd-nexthop.h" #include "networkd-queue.h" #include "networkd-radv.h" +#include "networkd-route-util.h" #include "networkd-route.h" #include "networkd-routing-policy-rule.h" #include "networkd-setlink.h" @@ -94,6 +95,32 @@ bool link_ipv6_enabled(Link *link) { return false; } +bool link_has_ipv6_connectivity(Link *link) { + LinkAddressState ipv6_address_state; + + assert(link); + + link_get_address_states(link, NULL, &ipv6_address_state, NULL); + + switch (ipv6_address_state) { + case LINK_ADDRESS_STATE_ROUTABLE: + /* If the interface has a routable IPv6 address, then we assume yes. */ + return true; + + case LINK_ADDRESS_STATE_DEGRADED: + /* If the interface has only degraded IPv6 address (mostly, link-local address), then let's check + * there is an IPv6 default gateway. */ + return link_has_default_gateway(link, AF_INET6); + + case LINK_ADDRESS_STATE_OFF: + /* No IPv6 address. */ + return false; + + default: + assert_not_reached(); + } +} + static bool link_is_ready_to_configure_one(Link *link, bool allow_unmanaged) { assert(link); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 8ced02f3c1a..8bbbc4ec85c 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -236,6 +236,7 @@ static inline bool link_has_carrier(Link *link) { bool link_ipv6_enabled(Link *link); int link_ipv6ll_gained(Link *link); +bool link_has_ipv6_connectivity(Link *link); int link_stop_engines(Link *link, bool may_keep_dhcp); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 6b3609989fc..02fda412439 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -258,6 +258,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.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) DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 94506c975de..3de4ea8dec3 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -400,6 +400,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_route_table = RT_TABLE_MAIN, .dhcp_ip_service_type = -1, .dhcp_broadcast = -1, + .dhcp_ipv6_only_mode = -1, .dhcp6_use_address = true, .dhcp6_use_pd_prefix = true, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 0fc08517977..1091ce289c1 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -139,6 +139,7 @@ struct Network { bool dhcp_anonymize; bool dhcp_send_hostname; int dhcp_broadcast; + int dhcp_ipv6_only_mode; bool dhcp_use_dns; bool dhcp_use_dns_set; bool dhcp_routes_to_dns; diff --git a/src/network/networkd-route-util.c b/src/network/networkd-route-util.c index b4620079fca..a204fb96319 100644 --- a/src/network/networkd-route-util.c +++ b/src/network/networkd-route-util.c @@ -47,7 +47,8 @@ static bool route_lifetime_is_valid(const Route *route) { route->lifetime_usec > now(CLOCK_BOOTTIME); } -static Route *link_find_default_gateway(Link *link, int family, Route *gw) { +bool link_find_default_gateway(Link *link, int family, Route **gw) { + bool found = false; Route *route; assert(link); @@ -69,16 +70,24 @@ static Route *link_find_default_gateway(Link *link, int family, Route *gw) { continue; if (!in_addr_is_set(route->gw_family, &route->gw)) continue; - if (gw) { - if (route->gw_weight > gw->gw_weight) + + /* Found a default gateway. */ + if (!gw) + return true; + + /* If we have already found another gw, then let's compare their weight and priority. */ + if (*gw) { + if (route->gw_weight > (*gw)->gw_weight) continue; - if (route->priority >= gw->priority) + if (route->priority >= (*gw)->priority) continue; } - gw = route; + + *gw = route; + found = true; } - return gw; + return found; } int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret) { @@ -98,7 +107,7 @@ int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret) { if (link->state != LINK_STATE_CONFIGURED) continue; - gw = link_find_default_gateway(link, family, gw); + link_find_default_gateway(link, family, &gw); } if (!gw) diff --git a/src/network/networkd-route-util.h b/src/network/networkd-route-util.h index 1b082fc63f0..f326888c934 100644 --- a/src/network/networkd-route-util.h +++ b/src/network/networkd-route-util.h @@ -9,9 +9,15 @@ typedef struct Link Link; typedef struct Manager Manager; typedef struct Address Address; +typedef struct Route Route; unsigned routes_max(void); +bool link_find_default_gateway(Link *link, int family, Route **gw); +static inline bool link_has_default_gateway(Link *link, int family) { + return link_find_default_gateway(link, family, NULL); +} + int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret); bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_union *gw); diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 023d81b5784..7218d799fc8 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -1620,6 +1620,7 @@ static int process_route_one( _cleanup_(route_freep) Route *tmp = in; Route *route = NULL; + bool update_dhcp4; int r; assert(manager); @@ -1628,6 +1629,8 @@ static int process_route_one( /* link may be NULL. This consumes 'in'. */ + update_dhcp4 = link && tmp->family == AF_INET6 && tmp->dst_prefixlen == 0; + (void) route_get(manager, link, tmp, &route); switch (type) { @@ -1680,6 +1683,14 @@ static int process_route_one( assert_not_reached(); } + if (update_dhcp4) { + r = dhcp4_update_ipv6_connectivity(link); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to notify IPv6 connectivity to DHCPv4 client: %m"); + link_enter_failed(link); + } + } + return 1; } -- 2.39.5