diff options
author | Susant Sahani <ssahani@vmware.com> | 2019-09-26 20:06:02 +0200 |
---|---|---|
committer | Yu Watanabe <watanabe.yu+github@gmail.com> | 2019-10-15 00:14:02 +0900 |
commit | cb29c1560515f758e6122d111e82ab302b805671 (patch) | |
tree | bf08bf944ab07d4ecb0db448ca17594c44ba239c | |
parent | 349176ae6c095725ddc11599dd03c7f16a5bb099 (diff) | |
download | systemd-cb29c1560515f758e6122d111e82ab302b805671.tar.gz systemd-cb29c1560515f758e6122d111e82ab302b805671.tar.bz2 systemd-cb29c1560515f758e6122d111e82ab302b805671.zip |
network: DHCPv4 client: add support to send arbitary option and data
-rw-r--r-- | man/systemd.network.xml | 9 | ||||
-rw-r--r-- | src/libsystemd-network/dhcp-client-internal.h | 4 | ||||
-rw-r--r-- | src/libsystemd-network/meson.build | 1 | ||||
-rw-r--r-- | src/libsystemd-network/sd-dhcp-client.c | 79 | ||||
-rw-r--r-- | src/network/networkd-dhcp4.c | 93 | ||||
-rw-r--r-- | src/network/networkd-dhcp4.h | 1 | ||||
-rw-r--r-- | src/network/networkd-network-gperf.gperf | 1 | ||||
-rw-r--r-- | src/network/networkd-network.c | 2 | ||||
-rw-r--r-- | src/network/networkd-network.h | 5 | ||||
-rw-r--r-- | src/systemd/sd-dhcp-client.h | 7 | ||||
-rw-r--r-- | test/fuzz/fuzz-network-parser/directives.network | 1 |
11 files changed, 201 insertions, 2 deletions
diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 657ba66245..58bc7e140b 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1586,6 +1586,15 @@ </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 index 0000000000..2c48d095f4 --- /dev/null +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +extern const struct hash_ops dhcp_option_hash_ops; diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 56d470ff68..7fa0c67956 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -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 diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 02f3569edc..550f614d0c 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -35,6 +35,14 @@ #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); } diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 6b85428758..70ab9b2a54 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -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", diff --git a/src/network/networkd-dhcp4.h b/src/network/networkd-dhcp4.h index fce11ef671..dbaec18781 100644 --- a/src/network/networkd-dhcp4.h +++ b/src/network/networkd-dhcp4.h @@ -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); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 689b1a123e..220564c5eb 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 3ea76a034a..f18d611b36 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -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); } diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 35469c05ed..86135c62e5 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -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; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 98e3281397..a44e6e35a2 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -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; diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 24d94033fc..63dc8bbdf8 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -94,6 +94,7 @@ RequestOptions= SendRelease= MaxAttempts= IPServiceType= +SendOptions= [DHCPv6] UseNTP= UseDNS= |