]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network/dhcp4: support IPv6 only mode (RFC 8925)
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 20 Sep 2023 05:29:06 +0000 (14:29 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 11 Oct 2023 12:42:13 +0000 (21:42 +0900)
Co-authored-by: Susant Sahani <ssahani@gmail.com>
12 files changed:
man/systemd.network.xml
src/network/networkd-address.c
src/network/networkd-dhcp4.c
src/network/networkd-dhcp4.h
src/network/networkd-link.c
src/network/networkd-link.h
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/network/networkd-route-util.c
src/network/networkd-route-util.h
src/network/networkd-route.c

index 804d51be11c08cd341094fb2dea1eb766d467502..d939c29f716a5e227e4a165daaf0b16d5872a8e8 100644 (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>
index e2e448b44f5e6e8fa2c14ed96ae33fdcbc36e083..c1a8cd884a81528af0c3219bff855a501cc6d359 100644 (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;
 }
 
index 3e8038ede6b71ea648d0530f6526350b42cc21a7..57d40e856ee0c4309721026f6ca80bbf730992ff 100644 (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;
 }
 
index 7446f0a1311441d28e69356f701503caddb2863f..d36b0585464187ae9db62b5a764acb6bad29130d 100644 (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);
 
index a7b7200423c4691b6ff5e8672c49755fcd222ee8..555b19d580b57b27235cf2e4ab7e979655fd9426 100644 (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);
 
index 8ced02f3c1aeb0872b2c4a8815bde18ffe53bf04..8bbbc4ec85ce82fb380a2a5a9f53ed261c8f3838 100644 (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);
 
index 6b3609989fcad5f178b7b113e1fb0130b2722f65..02fda412439e3840af633db7c8c65a197c5ac3c4 100644 (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)
index 94506c975de3d4ff88761892daebfd64b437d53f..3de4ea8dec31a47a5a3243fe6c0990bc81fea3bc 100644 (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,
index 0fc08517977d1ed79f940b9f605c84364abec8ee..1091ce289c1b134a878f56bb99c2986120921e4a 100644 (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;
index b4620079fcabd27361203b5815e993203251279e..a204fb9631950a9f74624f34798f68b09353ff3c 100644 (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)
index 1b082fc63f0a9562979549016e513e0d89e16828..f326888c9347dd2c9d87ed58966264a054ba26dc 100644 (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);
index 023d81b5784287a87782ee26828b4d2461d7d54c..7218d799fc842098e9b19499a3ce7e4344e02676 100644 (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;
 }