summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2023-09-20 07:29:06 +0200
committerYu Watanabe <watanabe.yu+github@gmail.com>2023-10-11 14:42:13 +0200
commitfc35a9f8d1632c4e7a279228f869bfc77d8f5b9c (patch)
treebb68dbc95f9639bc6079e0c815312d8cedb422a6
parentsd-dhcp-client: support IPv6 only mode (diff)
downloadsystemd-fc35a9f8d1632c4e7a279228f869bfc77d8f5b9c.tar.xz
systemd-fc35a9f8d1632c4e7a279228f869bfc77d8f5b9c.zip
network/dhcp4: support IPv6 only mode (RFC 8925)
Co-authored-by: Susant Sahani <ssahani@gmail.com>
-rw-r--r--man/systemd.network.xml13
-rw-r--r--src/network/networkd-address.c15
-rw-r--r--src/network/networkd-dhcp4.c51
-rw-r--r--src/network/networkd-dhcp4.h6
-rw-r--r--src/network/networkd-link.c27
-rw-r--r--src/network/networkd-link.h1
-rw-r--r--src/network/networkd-network-gperf.gperf1
-rw-r--r--src/network/networkd-network.c1
-rw-r--r--src/network/networkd-network.h1
-rw-r--r--src/network/networkd-route-util.c23
-rw-r--r--src/network/networkd-route-util.h6
-rw-r--r--src/network/networkd-route.c11
12 files changed, 144 insertions, 12 deletions
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 804d51be11..d939c29f71 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -2574,6 +2574,19 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
</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>
<para>Allows one to set DHCPv4 lease lifetime when DHCPv4 server does not send the lease
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
index e2e448b44f..c1a8cd884a 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 3e8038ede6..57d40e856e 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 7446f0a131..d36b058546 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 a7b7200423..555b19d580 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 8ced02f3c1..8bbbc4ec85 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 6b3609989f..02fda41243 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 94506c975d..3de4ea8dec 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 0fc0851797..1091ce289c 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 b4620079fc..a204fb9631 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 1b082fc63f..f326888c93 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 023d81b578..7218d799fc 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;
}