network: DHCPv4 client: add support to send arbitary option and data
authorSusant Sahani <ssahani@vmware.com>
Thu, 26 Sep 2019 18:06:02 +0000 (20:06 +0200)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 14 Oct 2019 15:14:02 +0000 (00:14 +0900)
man/systemd.network.xml
src/libsystemd-network/dhcp-client-internal.h [new file with mode: 0644]
src/libsystemd-network/meson.build
src/libsystemd-network/sd-dhcp-client.c
src/network/networkd-dhcp4.c
src/network/networkd-dhcp4.h
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/network/networkd-network.h
src/systemd/sd-dhcp-client.h
test/fuzz/fuzz-network-parser/directives.network

index 657ba662455e16e35a4c3b2bb8e2e0db78f46bdd..58bc7e140bb1a8b2ae713b9ad0c1216bc6d8948f 100644 (file)
           </listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><varname>SendOptions=</varname></term>
+          <listitem>
+            <para>Send a raw option with value via DHCPv4 client. Takes a DHCP option and base64 encoded
+            data separated with a colon (option:value). The option ranges [1-254]. This option can be
+            specified multiple times. If an empty string is specified, then all options specified earlier
+            are cleared. Defaults to unset.</para>
+          </listitem>
+        </varlistentry>
        </variablelist>
    </refsect1>
 
diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h
new file mode 100644 (file)
index 0000000..2c48d09
--- /dev/null
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+extern const struct hash_ops dhcp_option_hash_ops;
index 56d470ff685c3e4cdd9fbf963418728f2ef5b2e2..7fa0c67956213bfc9604e98ae4d897818ab15875 100644 (file)
@@ -3,6 +3,7 @@
 sources = files('''
         sd-dhcp-client.c
         sd-dhcp-server.c
+        dhcp-client-internal.h
         dhcp-network.c
         dhcp-option.c
         dhcp-packet.c
index 02f3569edca877a0b117417318a33a4fc2aa7603..550f614d0c729d7b9d8e5d2df66325c4cede02d9 100644 (file)
 #define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC)
 #define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE)
 
+struct sd_dhcp_option {
+        unsigned n_ref;
+
+        uint8_t option;
+        void *data;
+        size_t length;
+};
+
 struct sd_dhcp_client {
         unsigned n_ref;
 
@@ -90,6 +98,7 @@ struct sd_dhcp_client {
         usec_t start_time;
         uint64_t attempt;
         uint64_t max_attempts;
+        OrderedHashmap *options;
         usec_t request_sent;
         sd_event_source *timeout_t1;
         sd_event_source *timeout_t2;
@@ -530,6 +539,66 @@ int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempt
         return 0;
 }
 
+static sd_dhcp_option* dhcp_option_free(sd_dhcp_option *i) {
+        if (!i)
+                return NULL;
+
+        free(i->data);
+        return mfree(i);
+}
+
+int sd_dhcp_option_new(uint8_t option, void *data, size_t length, sd_dhcp_option **ret) {
+        _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *p = NULL;
+        _cleanup_free_ void *q = NULL;
+
+        assert(ret);
+
+        q = memdup(data, length);
+        if (!q)
+                return -ENOMEM;
+
+        p = new(sd_dhcp_option, 1);
+        if (!p)
+                return -ENOMEM;
+
+        *p = (sd_dhcp_option) {
+                .n_ref = 1,
+                .option = option,
+                .length = length,
+                .data = TAKE_PTR(q),
+        };
+
+        *ret = TAKE_PTR(p);
+        return 0;
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_option, sd_dhcp_option, dhcp_option_free);
+DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
+                dhcp_option_hash_ops,
+                void,
+                trivial_hash_func,
+                trivial_compare_func,
+                sd_dhcp_option,
+                sd_dhcp_option_unref);
+
+int sd_dhcp_client_set_dhcp_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+        int r;
+
+        assert_return(client, -EINVAL);
+        assert_return(v, -EINVAL);
+
+        r = ordered_hashmap_ensure_allocated(&client->options, &dhcp_option_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = ordered_hashmap_put(client->options, UINT_TO_PTR(v->option), v);
+        if (r < 0)
+                return r;
+
+        sd_dhcp_option_ref(v);
+        return 0;
+}
+
 int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
         assert_return(client, -EINVAL);
 
@@ -791,6 +860,8 @@ static int dhcp_client_send_raw(
 static int client_send_discover(sd_dhcp_client *client) {
         _cleanup_free_ DHCPPacket *discover = NULL;
         size_t optoffset, optlen;
+        sd_dhcp_option *j;
+        Iterator i;
         int r;
 
         assert(client);
@@ -852,6 +923,13 @@ static int client_send_discover(sd_dhcp_client *client) {
                         return r;
         }
 
+        ORDERED_HASHMAP_FOREACH(j, client->options, i) {
+                r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+                                       j->option, j->length, j->data);
+                if (r < 0)
+                        return r;
+        }
+
         r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
                                SD_DHCP_OPTION_END, 0, NULL);
         if (r < 0)
@@ -1991,6 +2069,7 @@ static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) {
         free(client->hostname);
         free(client->vendor_class_identifier);
         client->user_class = strv_free(client->user_class);
+        ordered_hashmap_free(client->options);
         return mfree(client);
 }
 
index 6b85428758e5ab87bbfa67ab1d3e93485c85ff4d..70ab9b2a543387a8cccab4de7aac9601e0147b63 100644 (file)
@@ -5,6 +5,8 @@
 #include <linux/if_arp.h>
 
 #include "alloc-util.h"
+#include "dhcp-client-internal.h"
+#include "hexdecoct.h"
 #include "hostname-util.h"
 #include "parse-util.h"
 #include "network-internal.h"
@@ -1186,6 +1188,7 @@ int dhcp4_set_client_identifier(Link *link) {
 }
 
 int dhcp4_configure(Link *link) {
+        sd_dhcp_option *send_option;
         void *request_options;
         Iterator i;
         int r;
@@ -1292,6 +1295,12 @@ int dhcp4_configure(Link *link) {
                         return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for '%u': %m", option);
         }
 
+        ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_send_options, i) {
+                r = sd_dhcp_client_set_dhcp_option(link->dhcp_client, send_option);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set send option: %m");
+        }
+
         r = dhcp4_set_hostname(link);
         if (r < 0)
                 return r;
@@ -1557,6 +1566,90 @@ int config_parse_dhcp_request_options(
         return 0;
 }
 
+int config_parse_dhcp_send_options(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *opt = NULL, *old = NULL;
+        _cleanup_free_ char *word = NULL;
+        _cleanup_free_ void *q = NULL;
+        Network *network = data;
+        const char *p;
+        uint8_t u;
+        size_t sz;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (isempty(rvalue)) {
+                network->dhcp_send_options = ordered_hashmap_free(network->dhcp_send_options);
+                return 0;
+        }
+
+        p = rvalue;
+        r = extract_first_word(&p, &word, ":", 0);
+        if (r == -ENOMEM)
+                return log_oom();
+        if (r <= 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r,
+                           "Invalid DHCP send option, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+
+        r = safe_atou8(word, &u);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r,
+                           "Invalid DHCP send option, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+        if (u < 1 || u >= 255) {
+                log_syntax(unit, LOG_ERR, filename, line, 0,
+                           "Invalid DHCP send option, valid range is 1-254, ignoring assignment: %s", rvalue);
+                return 0;
+        }
+
+        r = unbase64mem(p, (size_t) -1, &q, &sz);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r,
+                           "Failed to decode base64 data, ignoring assignment: %s", p);
+                return 0;
+        }
+
+        r = sd_dhcp_option_new(u, q, sz, &opt);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r,
+                           "Failed to store DHCP send option '%s', ignoring assignment: %m", rvalue);
+                return 0;
+        }
+
+        r = ordered_hashmap_ensure_allocated(&network->dhcp_send_options, &dhcp_option_hash_ops);
+        if (r < 0)
+                return log_oom();
+
+        /* Overwrite existing option */
+        old = ordered_hashmap_remove(network->dhcp_send_options, UINT_TO_PTR(u));
+        r = ordered_hashmap_put(network->dhcp_send_options, UINT_TO_PTR(u), opt);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r,
+                           "Failed to store DHCP send option '%s'", rvalue);
+                return 0;
+        }
+
+        TAKE_PTR(opt);
+        return 0;
+}
+
 static const char* const dhcp_client_identifier_table[_DHCP_CLIENT_ID_MAX] = {
         [DHCP_CLIENT_ID_MAC] = "mac",
         [DHCP_CLIENT_ID_DUID] = "duid",
index fce11ef671e65783c454a9c6cb83639936678bdd..dbaec18781ca2ee2ba5e4f30bd3f64d4b3f709c3 100644 (file)
@@ -27,3 +27,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_black_listed_ip_address);
 CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_max_attempts);
 CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_class);
 CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_request_options);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_options);
index 689b1a123ebbcdfe9e901c3a66b13da62654ad4d..220564c5eb407580981a4772ccd0d1b4c1f16ba5 100644 (file)
@@ -168,6 +168,7 @@ DHCPv4.ListenPort,                      config_parse_uint16,
 DHCPv4.SendRelease,                     config_parse_bool,                               0,                             offsetof(Network, dhcp_send_release)
 DHCPv4.BlackList,                       config_parse_dhcp_black_listed_ip_address,       0,                             0
 DHCPv4.IPServiceType,                   config_parse_ip_service_type,                    0,                             offsetof(Network, ip_service_type)
+DHCPv4.SendOptions,                     config_parse_dhcp_send_options,                  0,                             0
 DHCPv6.UseDNS,                          config_parse_bool,                               0,                             offsetof(Network, dhcp6_use_dns)
 DHCPv6.UseNTP,                          config_parse_bool,                               0,                             offsetof(Network, dhcp6_use_ntp)
 DHCPv6.RapidCommit,                     config_parse_bool,                               0,                             offsetof(Network, rapid_commit)
index 3ea76a034a6edf4d1aa7a1e3d2d01c52e08cdfe6..f18d611b36c14ad784923f204ab6f1774cd3fced 100644 (file)
@@ -619,6 +619,8 @@ static Network *network_free(Network *network) {
 
         set_free_free(network->dnssec_negative_trust_anchors);
 
+        ordered_hashmap_free(network->dhcp_send_options);
+
         return mfree(network);
 }
 
index 35469c05edf25d74c113b39ee309a107cd7fe837..86135c62e5b3d6bdd5c7531444519b255a4f74fe 100644 (file)
@@ -86,10 +86,11 @@ struct Network {
         unsigned dhcp_route_metric;
         uint32_t dhcp_route_table;
         uint16_t dhcp_client_port;
+        int dhcp_critical;
+        int ip_service_type;
         bool dhcp_anonymize;
         bool dhcp_send_hostname;
         bool dhcp_broadcast;
-        int dhcp_critical;
         bool dhcp_use_dns;
         bool dhcp_routes_to_dns;
         bool dhcp_use_ntp;
@@ -104,7 +105,7 @@ struct Network {
         DHCPUseDomains dhcp_use_domains;
         Set *dhcp_black_listed_ip;
         Set *dhcp_request_options;
-        int ip_service_type;
+        OrderedHashmap *dhcp_send_options;
 
         /* DHCPv6 Client support*/
         bool dhcp6_use_dns;
index 98e328139784150c457bc04f97672d73cdd3f018..a44e6e35a2fade55546063f0911fc8f4ea26c751 100644 (file)
@@ -99,6 +99,7 @@ enum {
 };
 
 typedef struct sd_dhcp_client sd_dhcp_client;
+typedef struct sd_dhcp_option sd_dhcp_option;
 
 typedef int (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata);
 int sd_dhcp_client_set_callback(
@@ -178,6 +179,11 @@ int sd_dhcp_client_set_service_type(
                 sd_dhcp_client *client,
                 int type);
 
+int sd_dhcp_option_new(uint8_t option, void *data, size_t length, sd_dhcp_option **ret);
+sd_dhcp_option* sd_dhcp_option_ref(sd_dhcp_option *i);
+sd_dhcp_option* sd_dhcp_option_unref(sd_dhcp_option *i);
+int sd_dhcp_client_set_dhcp_option(sd_dhcp_client *client, sd_dhcp_option *v);
+
 int sd_dhcp_client_stop(sd_dhcp_client *client);
 int sd_dhcp_client_start(sd_dhcp_client *client);
 int sd_dhcp_client_send_release(sd_dhcp_client *client);
@@ -198,6 +204,7 @@ int sd_dhcp_client_detach_event(sd_dhcp_client *client);
 sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client);
 
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_option, sd_dhcp_option_unref);
 
 _SD_END_DECLARATIONS;
 
index 24d94033fc844a3cfe3931c22ac76bfe15ee0817..63dc8bbdf819c278e2a9c04e23b9044007d63c63 100644 (file)
@@ -94,6 +94,7 @@ RequestOptions=
 SendRelease=
 MaxAttempts=
 IPServiceType=
+SendOptions=
 [DHCPv6]
 UseNTP=
 UseDNS=