network/dhcp4: support IPv6 only mode (RFC 8925)

Co-authored-by: Susant Sahani <ssahani@gmail.com>
This commit is contained in:
Yu Watanabe 2023-09-20 14:29:06 +09:00
parent a91b888fff
commit fc35a9f8d1
12 changed files with 144 additions and 12 deletions

View file

@ -2573,6 +2573,19 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>IPv6OnlyMode=</varname></term>
<listitem>
<para>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 <ulink url="https://tools.ietf.org/html/rfc8925">RFC 8925</ulink>.
Defaults to true when <varname>IPv6AcceptRA=</varname> is enabled or DHCPv6 client is enabled
(i.e., <varname>DHCP=yes</varname>), and false otherwise.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>FallbackLeaseLifetimeSec=</varname></term>
<listitem>

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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)

View file

@ -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,

View file

@ -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;

View file

@ -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)

View file

@ -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);

View file

@ -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;
}