From e7d5fe17db9d046b364f933c4dcdd145f47b024a Mon Sep 17 00:00:00 2001 From: Andrew Doran Date: Fri, 1 May 2020 10:30:31 -0400 Subject: [PATCH] DHCP client: make SendOption work for DHCPv6 too. --- man/systemd.network.xml | 10 +- src/libsystemd-network/dhcp-server-internal.h | 1 + src/libsystemd-network/dhcp6-internal.h | 10 ++ src/libsystemd-network/dhcp6-option.c | 40 ++++++ src/libsystemd-network/sd-dhcp6-client.c | 28 ++++ src/network/networkd-dhcp-common.c | 127 ++++++++++++------ src/network/networkd-dhcp-common.h | 1 + src/network/networkd-dhcp6.c | 10 ++ src/network/networkd-network-gperf.gperf | 3 +- src/network/networkd-network.h | 1 + src/systemd/sd-dhcp6-client.h | 3 + src/systemd/sd-dhcp6-option.h | 37 +++++ .../fuzz-network-parser/directives.network | 1 + 13 files changed, 232 insertions(+), 40 deletions(-) create mode 100644 src/systemd/sd-dhcp6-option.h diff --git a/man/systemd.network.xml b/man/systemd.network.xml index dc3e472cdb..c73bdccd8d 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1748,6 +1748,14 @@ Defaults to false. + + + SendOption= + + As in the [DHCPv4] section, however because DHCPv6 uses 16-bit fields to store + option numbers, the option number is an integer in the range 1..65536. + + @@ -1985,7 +1993,7 @@ Send a raw option with value via DHCPv4 server. Takes a DHCP option number, data type and data (option:type:value). The option number is an integer in the range 1..254. The type takes one of uint8, - uint16, uint32, ipv4address, or + uint16, uint32, ipv4address, ipv6address, or string. Special characters in the data string may be escaped using C-style escapes. This setting can be specified multiple times. If an empty string is specified, diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index a45167b198..94ac6c6e24 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -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; diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 517e357d3d..db80585a22 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -14,6 +14,16 @@ #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; diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index ed684d44f3..3b7c89edd7 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -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); diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 99f38382e9..08b4c77d90 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -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); } diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index 0473aba615..e94de0cfe0 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -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, &dhcp6_option_hash_ops); + if (r < 0) + return log_oom(); + + /* 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; + } + + 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); } - - r = ordered_hashmap_ensure_allocated(options, &dhcp_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; - } - - TAKE_PTR(opt); 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); diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index ca86016ef2..0511413a51 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -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; diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 3580498e35..78c99e95bb 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -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; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index e376adbdee..7316ccffd0 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -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) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index cbdeda96fc..90fe222e12 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -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; diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index 091f8287ec..d365fc7638 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -24,6 +24,7 @@ #include #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 index 0000000000..c0f1c4df08 --- /dev/null +++ b/src/systemd/sd-dhcp6-option.h @@ -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 . +***/ + +#include +#include + +#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 diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 1cd18202f4..07a9c8ba2d 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -112,6 +112,7 @@ ForceDHCPv6PDOtherInformation= PrefixDelegationHint= WithoutRA= MUDURL= +SendOption= [Route] Destination= Protocol=