]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
DHCP client: make SendOption work for DHCPv6 too.
authorAndrew Doran <doranand@fb.com>
Fri, 1 May 2020 14:30:31 +0000 (10:30 -0400)
committerLennart Poettering <lennart@poettering.net>
Mon, 11 May 2020 14:31:08 +0000 (16:31 +0200)
13 files changed:
man/systemd.network.xml
src/libsystemd-network/dhcp-server-internal.h
src/libsystemd-network/dhcp6-internal.h
src/libsystemd-network/dhcp6-option.c
src/libsystemd-network/sd-dhcp6-client.c
src/network/networkd-dhcp-common.c
src/network/networkd-dhcp-common.h
src/network/networkd-dhcp6.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.h
src/systemd/sd-dhcp6-client.h
src/systemd/sd-dhcp6-option.h [new file with mode: 0644]
test/fuzz/fuzz-network-parser/directives.network

index dc3e472cdb72cc9fa31dee83e48976f33b1d34d3..c73bdccd8d4c44d5b06c1447903b95e4f1ab1556 100644 (file)
             Defaults to false.</para>
           </listitem>
         </varlistentry>
+
+        <varlistentry>
+          <term><varname>SendOption=</varname></term>
+          <listitem>
+            <para>As in the <literal>[DHCPv4]</literal> section, however because DHCPv6 uses 16-bit fields to store
+            option numbers, the option number is an integer in the range 1..65536.</para>
+          </listitem>
+        </varlistentry>
       </variablelist>
   </refsect1>
 
           <para>Send a raw option with value via DHCPv4 server. Takes a DHCP option number, data type
           and data (<literal><replaceable>option</replaceable>:<replaceable>type</replaceable>:<replaceable>value</replaceable></literal>).
           The option number is an integer in the range 1..254. The type takes one of <literal>uint8</literal>,
-          <literal>uint16</literal>, <literal>uint32</literal>, <literal>ipv4address</literal>, or
+          <literal>uint16</literal>, <literal>uint32</literal>, <literal>ipv4address</literal>, <literal>ipv6address</literal>, or
           <literal>string</literal>. Special characters in the data string may be escaped using
           <ulink url="https://en.wikipedia.org/wiki/Escape_sequences_in_C#Table_of_escape_sequences">C-style
           escapes</ulink>. This setting can be specified multiple times. If an empty string is specified,
index a45167b198e986821550c21dfe2d84eea6004237..94ac6c6e24d8ee9ddba50aae119f6e84b8e2cccb 100644 (file)
@@ -19,6 +19,7 @@ typedef enum DHCPRawOption {
         DHCP_RAW_OPTION_DATA_UINT32,
         DHCP_RAW_OPTION_DATA_STRING,
         DHCP_RAW_OPTION_DATA_IPV4ADDRESS,
+        DHCP_RAW_OPTION_DATA_IPV6ADDRESS,
         _DHCP_RAW_OPTION_DATA_MAX,
         _DHCP_RAW_OPTION_DATA_INVALID,
 } DHCPRawOption;
index 517e357d3df22b6c251d0dd1fc38a79588d2fb0f..db80585a22c310c12da72a428df5bd641873ee79 100644 (file)
 #include "macro.h"
 #include "sparse-endian.h"
 
+typedef struct sd_dhcp6_option {
+        unsigned n_ref;
+
+        uint16_t option;
+        void *data;
+        size_t length;
+} sd_dhcp6_option;
+
+extern const struct hash_ops dhcp6_option_hash_ops;
+
 /* Common option header */
 typedef struct DHCP6Option {
         be16_t code;
index ed684d44f30ffe92e2b864bad56de54cb71cff31..3b7c89edd7b2eeab719d78cdedf49bb79a427833 100644 (file)
@@ -597,3 +597,43 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char *
 
         return idx;
 }
+
+static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) {
+        if (!i)
+                return NULL;
+
+        free(i->data);
+        return mfree(i);
+}
+
+int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, sd_dhcp6_option **ret) {
+        assert_return(ret, -EINVAL);
+        assert_return(length == 0 || data, -EINVAL);
+
+        _cleanup_free_ void *q = memdup(data, length);
+        if (!q)
+                return -ENOMEM;
+
+        sd_dhcp6_option *p = new(sd_dhcp6_option, 1);
+        if (!p)
+                return -ENOMEM;
+
+        *p = (sd_dhcp6_option) {
+                .n_ref = 1,
+                .option = option,
+                .length = length,
+                .data = TAKE_PTR(q),
+        };
+
+        *ret = p;
+        return 0;
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp6_option, sd_dhcp6_option, dhcp6_option_free);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+                dhcp6_option_hash_ops,
+                void,
+                trivial_hash_func,
+                trivial_compare_func,
+                sd_dhcp6_option,
+                sd_dhcp6_option_unref);
index 99f38382e9264eb3cd2a3c920063fd6fc049d5e9..08b4c77d9017babf1762f9cf80a7f2f1b32af1c0 100644 (file)
@@ -78,6 +78,7 @@ struct sd_dhcp6_client {
         size_t duid_len;
         usec_t information_request_time_usec;
         usec_t information_refresh_time_usec;
+        OrderedHashmap *extra_options;
 };
 
 static const uint16_t default_req_opts[] = {
@@ -447,6 +448,24 @@ int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
         return 0;
 }
 
+int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(v, -EINVAL);
+
+        r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp6_option_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v);
+        if (r < 0)
+                return r;
+
+        sd_dhcp6_option_ref(v);
+        return 0;
+}
+
 static void client_notify(sd_dhcp6_client *client, int event) {
         assert(client);
 
@@ -492,7 +511,9 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         _cleanup_free_ DHCP6Message *message = NULL;
         struct in6_addr all_servers =
                 IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT;
+        struct sd_dhcp6_option *j;
         size_t len, optlen = 512;
+        Iterator i;
         uint8_t *opt;
         int r;
         usec_t elapsed_usec;
@@ -672,6 +693,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) {
         if (r < 0)
                 return r;
 
+        ORDERED_HASHMAP_FOREACH(j, client->extra_options, i) {
+                r = dhcp6_option_append(&opt, &optlen, j->option, j->length, j->data);
+                if (r < 0)
+                        return r;
+        }
+
         r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message,
                                           len - optlen);
         if (r < 0)
@@ -1584,6 +1611,7 @@ static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
         free(client->req_opts);
         free(client->fqdn);
         free(client->mudurl);
+        ordered_hashmap_free(client->extra_options);
         return mfree(client);
 }
 
index 0473aba6159eeba5e4a440507f19ef369e082d11..e94de0cfe0b856ad63e4ae1bb6c90ad7d6a45686 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
 #include "dhcp-internal.h"
+#include "dhcp6-internal.h"
 #include "escape.h"
 #include "in-addr-util.h"
 #include "networkd-dhcp-common.h"
@@ -320,13 +321,14 @@ int config_parse_dhcp_send_option(
                 void *data,
                 void *userdata) {
 
-        _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *opt = NULL, *old = NULL;
+        _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *opt4 = NULL, *old4 = NULL;
+        _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *opt6 = NULL, *old6 = NULL;
         _cleanup_free_ char *word = NULL, *q = NULL;
         OrderedHashmap **options = data;
         union in_addr_union addr;
         DHCPOptionDataType type;
-        uint8_t u, uint8_data;
-        uint16_t uint16_data;
+        uint8_t u8, uint8_data;
+        uint16_t u16, uint16_data;
         uint32_t uint32_data;
         const void *udata;
         const char *p;
@@ -353,16 +355,30 @@ int config_parse_dhcp_send_option(
                 return 0;
         }
 
-        r = safe_atou8(word, &u);
-        if (r < 0) {
-                log_syntax(unit, LOG_ERR, filename, line, r,
-                           "Invalid DHCP option, ignoring assignment: %s", rvalue);
-                return 0;
-        }
-        if (u < 1 || u >= 255) {
-                log_syntax(unit, LOG_ERR, filename, line, 0,
-                           "Invalid DHCP option, valid range is 1-254, ignoring assignment: %s", rvalue);
-                return 0;
+        if (ltype == AF_INET6) {
+                r = safe_atou16(word, &u16);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Invalid DHCP option, ignoring assignment: %s", rvalue);
+                         return 0;
+                }
+                if (u16 < 1 || u16 >= 65535) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue);
+                        return 0;
+                }
+        } else {
+                r = safe_atou8(word, &u8);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Invalid DHCP option, ignoring assignment: %s", rvalue);
+                         return 0;
+                }
+                if (u8 < 1 || u8 >= 255) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "Invalid DHCP option, valid range is 1-254, ignoring assignment: %s", rvalue);
+                        return 0;
+                }
         }
 
         word = mfree(word);
@@ -387,7 +403,7 @@ int config_parse_dhcp_send_option(
                 r = safe_atou8(p, &uint8_data);
                 if (r < 0) {
                         log_syntax(unit, LOG_ERR, filename, line, r,
-                                   "Failed to parse DHCPv4 uint8 data, ignoring assignment: %s", p);
+                                   "Failed to parse DHCP uint8 data, ignoring assignment: %s", p);
                         return 0;
                 }
 
@@ -399,7 +415,7 @@ int config_parse_dhcp_send_option(
                 r = safe_atou16(p, &uint16_data);
                 if (r < 0) {
                         log_syntax(unit, LOG_ERR, filename, line, r,
-                                   "Failed to parse DHCPv4 uint16 data, ignoring assignment: %s", p);
+                                   "Failed to parse DHCP uint16 data, ignoring assignment: %s", p);
                         return 0;
                 }
 
@@ -411,7 +427,7 @@ int config_parse_dhcp_send_option(
                 r = safe_atou32(p, &uint32_data);
                 if (r < 0) {
                         log_syntax(unit, LOG_ERR, filename, line, r,
-                                   "Failed to parse DHCPv4 uint32 data, ignoring assignment: %s", p);
+                                   "Failed to parse DHCP uint32 data, ignoring assignment: %s", p);
                         return 0;
                 }
 
@@ -424,7 +440,7 @@ int config_parse_dhcp_send_option(
                 r = in_addr_from_string(AF_INET, p, &addr);
                 if (r < 0) {
                         log_syntax(unit, LOG_ERR, filename, line, r,
-                                   "Failed to parse DHCPv4 ipv4address data, ignoring assignment: %s", p);
+                                   "Failed to parse DHCP ipv4address data, ignoring assignment: %s", p);
                         return 0;
                 }
 
@@ -432,11 +448,23 @@ int config_parse_dhcp_send_option(
                 sz = sizeof(addr.in.s_addr);
                 break;
         }
+        case DHCP_OPTION_DATA_IPV6ADDRESS: {
+                r = in_addr_from_string(AF_INET6, p, &addr);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to parse DHCP ipv6address data, ignoring assignment: %s", p);
+                        return 0;
+                }
+
+                udata = &addr.in6;
+                sz = sizeof(addr.in6.s6_addr);
+                break;
+        }
         case DHCP_OPTION_DATA_STRING:
                 sz = cunescape(p, UNESCAPE_ACCEPT_NUL, &q);
                 if (sz < 0) {
                         log_syntax(unit, LOG_ERR, filename, line, sz,
-                                   "Failed to decode DHCPv4 option data, ignoring assignment: %s", p);
+                                   "Failed to decode DHCP option data, ignoring assignment: %s", p);
                 }
 
                 udata = q;
@@ -445,27 +473,49 @@ int config_parse_dhcp_send_option(
                 return -EINVAL;
         }
 
-        r = sd_dhcp_option_new(u, udata, sz, &opt);
-        if (r < 0) {
-                log_syntax(unit, LOG_ERR, filename, line, r,
-                           "Failed to store DHCPv4 option '%s', ignoring assignment: %m", rvalue);
-                return 0;
-        }
+        if (ltype == AF_INET6) {
+                r = sd_dhcp6_option_new(u16, udata, sz, &opt6);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
+                        return 0;
+                }
 
-        r = ordered_hashmap_ensure_allocated(options, &dhcp_option_hash_ops);
-        if (r < 0)
-                return log_oom();
+                r = ordered_hashmap_ensure_allocated(options, &dhcp6_option_hash_ops);
+                if (r < 0)
+                        return log_oom();
 
-        /* Overwrite existing option */
-        old = ordered_hashmap_remove(*options, UINT_TO_PTR(u));
-        r = ordered_hashmap_put(*options, UINT_TO_PTR(u), opt);
-        if (r < 0) {
-                log_syntax(unit, LOG_ERR, filename, line, r,
-                           "Failed to store DHCPv4 option '%s', ignoring assignment: %m", rvalue);
-                return 0;
-        }
+                /* Overwrite existing option */
+                old6 = ordered_hashmap_get(*options, UINT_TO_PTR(u16));
+                r = ordered_hashmap_replace(*options, UINT_TO_PTR(u16), opt6);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
+                        return 0;
+                }
+                TAKE_PTR(opt6);
+        } else {
+                r = sd_dhcp_option_new(u8, udata, sz, &opt4);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
+                        return 0;
+                }
 
-        TAKE_PTR(opt);
+                r = ordered_hashmap_ensure_allocated(options, &dhcp_option_hash_ops);
+                if (r < 0)
+                        return log_oom();
+
+                /* Overwrite existing option */
+                old4 = ordered_hashmap_get(*options, UINT_TO_PTR(u8));
+                r = ordered_hashmap_replace(*options, UINT_TO_PTR(u8), opt4);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue);
+                        return 0;
+                }
+                TAKE_PTR(opt4);
+        }
         return 0;
 }
 
@@ -486,6 +536,7 @@ static const char * const dhcp_option_data_type_table[_DHCP_OPTION_DATA_MAX] = {
         [DHCP_OPTION_DATA_UINT32]      = "uint32",
         [DHCP_OPTION_DATA_STRING]      = "string",
         [DHCP_OPTION_DATA_IPV4ADDRESS] = "ipv4address",
+        [DHCP_OPTION_DATA_IPV6ADDRESS] = "ipv6address",
 };
 
 DEFINE_STRING_TABLE_LOOKUP(dhcp_option_data_type, DHCPOptionDataType);
index ca86016ef2a7209b649230e442bb2fc906dcd5d5..0511413a5154b5b194d7941256939b09bc01d5ca 100644 (file)
@@ -21,6 +21,7 @@ typedef enum DHCPOptionDataType {
         DHCP_OPTION_DATA_UINT32,
         DHCP_OPTION_DATA_STRING,
         DHCP_OPTION_DATA_IPV4ADDRESS,
+        DHCP_OPTION_DATA_IPV6ADDRESS,
         _DHCP_OPTION_DATA_MAX,
         _DHCP_OPTION_DATA_INVALID,
 } DHCPOptionDataType;
index 3580498e3512cb0b8b53728b8420d9a0ac0943bf..78c99e95bb4b8e0e33e539ce0155fc91ec6f6949 100644 (file)
@@ -620,7 +620,9 @@ static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) {
 
 int dhcp6_configure(Link *link) {
         _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+        sd_dhcp6_option *send_option;
         const DUID *duid;
+        Iterator i;
         int r;
 
         assert(link);
@@ -662,6 +664,14 @@ int dhcp6_configure(Link *link) {
         if (r < 0)
                 return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set DUID: %m");
 
+        ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp6_client_send_options, i) {
+                r = sd_dhcp6_client_add_option(client, send_option);
+                if (r == -EEXIST)
+                        continue;
+                if (r < 0)
+                        return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set option: %m");
+        }
+
         r = dhcp6_set_hostname(client, link);
         if (r < 0)
                 return r;
index e376adbdeeec354acb63ea8d65a9390c22467f81..7316ccffd09848f4cf6053bbf5aacee4172ce0e8 100644 (file)
@@ -185,7 +185,7 @@ DHCPv4.SendRelease,                          config_parse_bool,
 DHCPv4.SendDecline,                          config_parse_bool,                                        0,                             offsetof(Network, dhcp_send_decline)
 DHCPv4.BlackList,                            config_parse_dhcp_black_listed_ip_address,                0,                             0
 DHCPv4.IPServiceType,                        config_parse_dhcp_ip_service_type,                        0,                             offsetof(Network, ip_service_type)
-DHCPv4.SendOption,                           config_parse_dhcp_send_option,                            0,                             offsetof(Network, dhcp_client_send_options)
+DHCPv4.SendOption,                           config_parse_dhcp_send_option,                            AF_INET,                       offsetof(Network, dhcp_client_send_options)
 DHCPv4.SendVendorOption,                     config_parse_dhcp_send_option,                            0,                             offsetof(Network, dhcp_client_send_vendor_options)
 DHCPv4.RouteMTUBytes,                        config_parse_mtu,                                         AF_INET,                       offsetof(Network, dhcp_route_mtu)
 DHCPv6.UseDNS,                               config_parse_bool,                                        0,                             offsetof(Network, dhcp6_use_dns)
@@ -195,6 +195,7 @@ DHCPv6.MUDURL,                               config_parse_dhcp6_mud_url,
 DHCPv6.ForceDHCPv6PDOtherInformation,        config_parse_bool,                                        0,                             offsetof(Network, dhcp6_force_pd_other_information)
 DHCPv6.PrefixDelegationHint,                 config_parse_dhcp6_pd_hint,                               0,                             0
 DHCPv6.WithoutRA,                            config_parse_bool,                                        0,                             offsetof(Network, dhcp6_without_ra)
+DHCPv6.SendOption,                           config_parse_dhcp_send_option,                            AF_INET6,                      offsetof(Network, dhcp6_client_send_options)
 IPv6AcceptRA.UseAutonomousPrefix,            config_parse_bool,                                        0,                             offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
 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)
index cbdeda96fc418bce82e1e86287f09bbffdef6c7c..90fe222e123fb20bc172ad3b145b739569e06a59 100644 (file)
@@ -133,6 +133,7 @@ struct Network {
         uint8_t dhcp6_pd_length;
         char *dhcp6_mudurl;
         struct in6_addr dhcp6_pd_address;
+        OrderedHashmap *dhcp6_client_send_options;
 
         /* DHCP Server Support */
         bool dhcp_server;
index 091f8287ec2aea55001473bd702de0a4dc20852f..d365fc763805c6d7803a8e1a4535ffc9984fce2e 100644 (file)
@@ -24,6 +24,7 @@
 #include <sys/types.h>
 
 #include "sd-dhcp6-lease.h"
+#include "sd-dhcp6-option.h"
 #include "sd-event.h"
 
 #include "_sd-common.h"
@@ -142,6 +143,8 @@ int sd_dhcp6_client_get_lease(
                 sd_dhcp6_client *client,
                 sd_dhcp6_lease **ret);
 
+int sd_dhcp6_client_add_option(sd_dhcp6_client *client, sd_dhcp6_option *v);
+
 int sd_dhcp6_client_stop(sd_dhcp6_client *client);
 int sd_dhcp6_client_start(sd_dhcp6_client *client);
 int sd_dhcp6_client_is_running(sd_dhcp6_client *client);
diff --git a/src/systemd/sd-dhcp6-option.h b/src/systemd/sd-dhcp6-option.h
new file mode 100644 (file)
index 0000000..c0f1c4d
--- /dev/null
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#ifndef foosddhcp6optionhfoo
+#define foosddhcp6optionhfoo
+
+/***
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_dhcp6_option sd_dhcp6_option;
+
+int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, sd_dhcp6_option **ret);
+sd_dhcp6_option *sd_dhcp6_option_ref(sd_dhcp6_option *ra);
+sd_dhcp6_option *sd_dhcp6_option_unref(sd_dhcp6_option *ra);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_option, sd_dhcp6_option_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
index 1cd18202f40b4237fa565e7b640c45b7f893cbef..07a9c8ba2d79010990d859be537f0b381d7af298 100644 (file)
@@ -112,6 +112,7 @@ ForceDHCPv6PDOtherInformation=
 PrefixDelegationHint=
 WithoutRA=
 MUDURL=
+SendOption=
 [Route]
 Destination=
 Protocol=