From ac24e418d9bc988ecf114c464701b35934948178 Mon Sep 17 00:00:00 2001
From: Susant Sahani <ssahani@vmware.com>
Date: Wed, 8 Apr 2020 16:01:21 +0200
Subject: [PATCH] network: Allow DHCPv6 client to be started even if no O or M
 bit in RA.

---
 man/systemd.network.xml                  |  6 +++---
 src/network/networkd-ndisc.c             | 23 +++++++++++++++++++----
 src/network/networkd-ndisc.h             | 12 ++++++++++++
 src/network/networkd-network-gperf.gperf |  2 +-
 src/network/networkd-network.h           |  6 +++---
 5 files changed, 38 insertions(+), 11 deletions(-)

diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index d7c3eeadef6..75a581816d3 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1824,11 +1824,11 @@
         <varlistentry>
           <term><varname>DHCPv6Client=</varname></term>
           <listitem>
-            <para>Takes a boolean. When true (the default), the DHCPv6 client will be started when the
-            RA has the managed or other information flag.</para>
+            <para>Takes a boolean, or the special value <literal>always</literal>. When true (the default), the DHCPv6 client will be started when the
+            RA has the managed or other information flag. If set to <literal>always</literal>, the DHCPv6 client will be started even if there is no
+            managed or other information flag in the RA.</para>
           </listitem>
         </varlistentry>
-
       </variablelist>
   </refsect1>
 
diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c
index fe1de6387ee..8af7b3c0c3c 100644
--- a/src/network/networkd-ndisc.c
+++ b/src/network/networkd-ndisc.c
@@ -13,6 +13,7 @@
 #include "networkd-manager.h"
 #include "networkd-ndisc.h"
 #include "networkd-route.h"
+#include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
 
@@ -794,10 +795,14 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
         if (r < 0)
                 return log_link_warning_errno(link, r, "Failed to get RA flags: %m");
 
-        if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER) &&
-            link->network->ipv6_accept_ra_start_dhcp6_client) {
-                /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
-                r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED));
+        if ((flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER) && link->network->ipv6_accept_ra_start_dhcp6_client)) {
+
+                if (link->network->ipv6_accept_ra_start_dhcp6_client == IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS)
+                        r = dhcp6_request_address(link, false);
+                else
+                        /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
+                        r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED));
+
                 if (r < 0 && r != -EBUSY)
                         log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m");
                 else {
@@ -1065,3 +1070,13 @@ int config_parse_address_generation_type(
 
         return 0;
 }
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_start_dhcp6_client, ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client,
+                         "Failed to parse DHCPv6Client= setting")
+static const char* const ipv6_accept_ra_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = {
+        [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO]     = "no",
+        [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS] = "always",
+        [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES]    = "yes",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES);
diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h
index 02c2f8afda5..68cd1c2bd7b 100644
--- a/src/network/networkd-ndisc.h
+++ b/src/network/networkd-ndisc.h
@@ -15,6 +15,14 @@ typedef enum IPv6TokenAddressGeneration {
         _IPV6_TOKEN_ADDRESS_GENERATION_INVALID = -1,
 } IPv6TokenAddressGeneration;
 
+typedef enum IPv6AcceptRAStartDHCP6Client {
+        IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO,
+        IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS,
+        IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES,
+        _IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX,
+        _IPV6_ACCEPT_RA_START_DHCP6_CLIENT_INVALID = -1,
+} IPv6AcceptRAStartDHCP6Client;
+
 typedef struct NDiscRDNSS {
         usec_t valid_until;
         struct in6_addr address;
@@ -45,3 +53,7 @@ void ndisc_flush(Link *link);
 
 CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_black_listed_prefix);
 CONFIG_PARSER_PROTOTYPE(config_parse_address_generation_type);
+CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_accept_ra_start_dhcp6_client);
+
+const char* ipv6_accept_ra_start_dhcp6_client_to_string(IPv6AcceptRAStartDHCP6Client i) _const_;
+IPv6AcceptRAStartDHCP6Client ipv6_accept_ra_start_dhcp6_client_from_string(const char *s) _pure_;
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index c63f57f9624..9597176b1c6 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -199,7 +199,7 @@ IPv6AcceptRA.UseAutonomousPrefix,            config_parse_bool,
 IPv6AcceptRA.UseOnLinkPrefix,                config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_onlink_prefix)
 IPv6AcceptRA.UseDNS,                         config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_dns)
 IPv6AcceptRA.UseDomains,                     config_parse_dhcp_use_domains,                            0,                             offsetof(Network, ipv6_accept_ra_use_domains)
-IPv6AcceptRA.DHCPv6Client,                   config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_start_dhcp6_client)
+IPv6AcceptRA.DHCPv6Client,                   config_parse_ipv6_accept_ra_start_dhcp6_client,           0,                             offsetof(Network, ipv6_accept_ra_start_dhcp6_client)
 IPv6AcceptRA.RouteTable,                     config_parse_section_route_table,                         0,                             0
 IPv6AcceptRA.BlackList,                      config_parse_ndisc_black_listed_prefix,                   0,                             0
 DHCPServer.MaxLeaseTimeSec,                  config_parse_sec,                                         0,                             offsetof(Network, dhcp_server_max_lease_time_usec)
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 1c600ae7bd8..240f6689dd2 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -230,12 +230,12 @@ struct Network {
         bool ipv6_accept_ra_use_dns;
         bool ipv6_accept_ra_use_autonomous_prefix;
         bool ipv6_accept_ra_use_onlink_prefix;
-        bool ipv6_accept_ra_start_dhcp6_client;
         bool active_slave;
         bool primary_slave;
-        DHCPUseDomains ipv6_accept_ra_use_domains;
-        uint32_t ipv6_accept_ra_route_table;
         bool ipv6_accept_ra_route_table_set;
+        DHCPUseDomains ipv6_accept_ra_use_domains;
+        IPv6AcceptRAStartDHCP6Client ipv6_accept_ra_start_dhcp6_client;
+        uint32_t ipv6_accept_ra_route_table;
         Set *ndisc_black_listed_prefix;
         OrderedHashmap *ipv6_tokens;