From 99ccb8ff8910a38551f00022b6a6db4d72cd0992 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Fri, 22 May 2020 08:39:14 +0200 Subject: [PATCH 1/3] sd-dhcp6: Introduce vendor specific information RFC: 8415 21.17. Vendor-specific Information Option This option is used by clients and servers to exchange vendor- specific information. The format of the Vendor-specific Information option is: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | OPTION_VENDOR_OPTS | option-len | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | enterprise-number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . vendor-option-data . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Figure 30: Vendor-specific Information Option Format option-code OPTION_VENDOR_OPTS (17). option-len 4 + length of vendor-option-data field. enterprise-number The vendor's registered Enterprise Number as maintained by IANA [IANA-PEN]. A 4-octet field containing an unsigned integer. vendor-option-data Vendor options, interpreted by vendor-specific code on the clients and servers. A variable-length field (4 octets less than the value in the option-len field). The definition of the information carried in this option is vendor specific. The vendor is indicated in the enterprise-number field. Use of vendor-specific information allows enhanced operation, utilizing additional features in a vendor's DHCP implementation. A DHCP client that does not receive requested vendor-specific information will still configure the node's IPv6 stack to be functional. The vendor-option-data field MUST be encoded as a sequence of code/length/value fields of format identical to the DHCP options (see Section 21.1). The sub-option codes are defined by the vendor identified in the enterprise-number field and are not managed by IANA. Each of the sub-options is formatted as follows: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | sub-opt-code | sub-option-len | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . sub-option-data . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Figure 31: Vendor-specific Options Format sub-opt-code The code for the sub-option. A 2-octet field. sub-option-len An unsigned integer giving the length of the sub-option-data field in this sub-option in octets. A 2-octet field. sub-option-data The data area for the sub-option. The length, in octets, is specified by sub-option-len. Multiple instances of the Vendor-specific Information option may appear in a DHCP message. Each instance of the option is interpreted according to the option codes defined by the vendor identified by the Enterprise Number in that option. Servers and clients MUST NOT send more than one instance of the Vendor-specific Information option with the same Enterprise Number. Each instance of the Vendor-specific Information option MAY contain multiple sub-options. A client that is interested in receiving a Vendor-specific Information option: - MUST specify the Vendor-specific Information option in an Option Request option. - MAY specify an associated Vendor Class option (see Section 21.16). - MAY specify the Vendor-specific Information option with appropriate data. Servers only return the Vendor-specific Information options if specified in Option Request options from clients and: - MAY use the Enterprise Numbers in the associated Vendor Class options to restrict the set of Enterprise Numbers in the Vendor-specific Information options returned. - MAY return all configured Vendor-specific Information options. - MAY use other information in the packet or in its configuration to determine which set of Enterprise Numbers in the Vendor-specific Information options to return. --- src/libsystemd-network/dhcp6-internal.h | 3 ++ src/libsystemd-network/dhcp6-option.c | 36 +++++++++++++++++++++- src/libsystemd-network/sd-dhcp6-client.c | 39 ++++++++++++++++++++++++ src/systemd/meson.build | 1 + src/systemd/sd-dhcp6-client.h | 5 ++- src/systemd/sd-dhcp6-option.h | 2 +- 6 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 2880bc4770..b0d1216eed 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -11,12 +11,14 @@ #include "sd-event.h" #include "list.h" +#include "hashmap.h" #include "macro.h" #include "sparse-endian.h" typedef struct sd_dhcp6_option { unsigned n_ref; + uint32_t enterprise_identifier; uint16_t option; void *data; size_t length; @@ -99,6 +101,7 @@ int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Add int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char **user_class); int dhcp6_option_append_vendor_class(uint8_t **buf, size_t *buflen, char **user_class); +int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options); int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen, uint8_t **optvalue); int dhcp6_option_parse_status(DHCP6Option *option, size_t len); diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index f763474e6e..fa43587686 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -79,6 +79,39 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, return 0; } +int dhcp6_option_append_vendor_option(uint8_t **buf, size_t *buflen, OrderedHashmap *vendor_options) { + sd_dhcp6_option *options; + Iterator i; + int r; + + assert(buf); + assert(*buf); + assert(buflen); + assert(vendor_options); + + ORDERED_HASHMAP_FOREACH(options, vendor_options, i) { + _cleanup_free_ uint8_t *p = NULL; + size_t total; + + total = 4 + 2 + 2 + options->length; + + p = malloc(total); + if (!p) + return -ENOMEM; + + unaligned_write_be32(p, options->enterprise_identifier); + unaligned_write_be16(p + 4, options->option); + unaligned_write_be16(p + 6, options->length); + memcpy(p + 8, options->data, options->length); + + r = dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_VENDOR_OPTS, total, p); + if (r < 0) + return r; + } + + return 0; +} + int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia) { uint16_t len; uint8_t *ia_hdr; @@ -683,7 +716,7 @@ static sd_dhcp6_option* dhcp6_option_free(sd_dhcp6_option *i) { return mfree(i); } -int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, sd_dhcp6_option **ret) { +int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, sd_dhcp6_option **ret) { assert_return(ret, -EINVAL); assert_return(length == 0 || data, -EINVAL); @@ -698,6 +731,7 @@ int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, sd_dhc *p = (sd_dhcp6_option) { .n_ref = 1, .option = option, + .enterprise_identifier = enterprise_identifier, .length = length, .data = TAKE_PTR(q), }; diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 76a16a18c1..8d13aef4e7 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -81,6 +81,7 @@ struct sd_dhcp6_client { usec_t information_request_time_usec; usec_t information_refresh_time_usec; OrderedHashmap *extra_options; + OrderedHashmap *vendor_options; }; static const uint16_t default_req_opts[] = { @@ -225,6 +226,25 @@ int sd_dhcp6_client_set_prefix_delegation_hint( return 0; } +int sd_dhcp6_client_add_vendor_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->vendor_options, &dhcp6_option_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(client->vendor_options, v, v); + if (r < 0) + return r; + + sd_dhcp6_option_ref(v); + + return 1; +} + static int client_ensure_duid(sd_dhcp6_client *client) { if (client->duid_len != 0) return 0; @@ -622,6 +642,13 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return r; } + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp6_option_append_vendor_option(&opt, &optlen, + client->vendor_options); + if (r < 0) + return r; + } + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) { r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd, &client->hint_pd_prefix); if (r < 0) @@ -680,6 +707,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return r; } + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options); + if (r < 0) + return r; + } + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) { r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL); if (r < 0) @@ -726,6 +759,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return r; } + if (!ordered_hashmap_isempty(client->vendor_options)) { + r = dhcp6_option_append_vendor_option(&opt, &optlen, client->vendor_options); + if (r < 0) + return r; + } + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) { r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL); if (r < 0) diff --git a/src/systemd/meson.build b/src/systemd/meson.build index 63d0829b67..62baf7784e 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -24,6 +24,7 @@ _not_installed_headers = ''' sd-dhcp-client.h sd-dhcp-lease.h sd-dhcp-option.h + sd-dhcp6-option.h sd-dhcp-server.h sd-ipv4acd.h sd-ipv4ll.h diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index c342be4b29..58a17a5a3f 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -143,7 +143,10 @@ int sd_dhcp6_client_get_address_request(sd_dhcp6_client *client, int *request); int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client, int request); -int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id); +int sd_dhcp6_client_set_transaction_id(sd_dhcp6_client *client, + uint32_t transaction_id); +int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, + sd_dhcp6_option *v); int sd_dhcp6_client_get_lease( sd_dhcp6_client *client, diff --git a/src/systemd/sd-dhcp6-option.h b/src/systemd/sd-dhcp6-option.h index c0f1c4df08..88a4986315 100644 --- a/src/systemd/sd-dhcp6-option.h +++ b/src/systemd/sd-dhcp6-option.h @@ -26,7 +26,7 @@ _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); +int sd_dhcp6_option_new(uint16_t option, const void *data, size_t length, uint32_t enterprise_identifier, 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); From b4ccc5de7d92e196666f4d2dffd1c3719c051318 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Tue, 26 May 2020 10:46:54 +0200 Subject: [PATCH 2/3] network: Inroduce DHCP6- send vendor options network: Inroduce DHCP6- send vendor options ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | OPTION_VENDOR_OPTS | option-len | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | enterprise-number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . vendor-option-data . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | sub-opt-code | sub-option-len | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . sub-option-data . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Figure 31: Vendor-specific Options Format sub-opt-code The code for the sub-option. A 2-octet field. sub-option-len An unsigned integer giving the length of the sub-option-data field in this sub-option in octets. A 2-octet field. sub-option-data The data area for the sub-option. The length, in octets, is specified by sub-option-len. ``` --- man/systemd.network.xml | 16 +++++++++++ src/network/networkd-dhcp-common.c | 27 ++++++++++++++++--- src/network/networkd-dhcp6.c | 9 +++++++ src/network/networkd-network-gperf.gperf | 1 + src/network/networkd-network.c | 1 + src/network/networkd-network.h | 1 + .../fuzz-network-parser/directives.network | 1 + 7 files changed, 52 insertions(+), 4 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index e8f3f364f1..ae82ae7e02 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1765,6 +1765,22 @@ + + SendVendorOption= + + Send an arbitrary vendor option in the DHCPv6 request. Takes an enterprise identifier, DHCP option number, + data type, and data separated with a colon + (enterprise identifier:option:type: + value). Enterprise identifier is an unsigned integer ranges 1..4294967294. + The option number must be an integer in the range 1..254. Data type takes one of uint8, + 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, + then all options specified earlier are cleared. Defaults to unset. + + + ForceDHCPv6PDOtherInformation= diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index 3d9dab7e3c..66dd8ea08e 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -437,13 +437,13 @@ int config_parse_dhcp_send_option( _cleanup_(sd_dhcp_option_unrefp) sd_dhcp_option *opt4 = NULL, *old4 = NULL; _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *opt6 = NULL, *old6 = NULL; + uint32_t uint32_data, enterprise_identifier = 0; _cleanup_free_ char *word = NULL, *q = NULL; OrderedHashmap **options = data; + uint16_t u16, uint16_data; union in_addr_union addr; DHCPOptionDataType type; uint8_t u8, uint8_data; - uint16_t u16, uint16_data; - uint32_t uint32_data; const void *udata; const char *p; ssize_t sz; @@ -460,10 +460,29 @@ int config_parse_dhcp_send_option( } p = rvalue; + if (ltype == AF_INET6 && streq(lvalue, "SendVendorOption")) { + r = extract_first_word(&p, &word, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0 || isempty(p)) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Invalid DHCP option, ignoring assignment: %s", rvalue); + return 0; + } + + r = safe_atou32(word, &enterprise_identifier); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to parse DHCPv6 enterprise identifier data, ignoring assignment: %s", p); + return 0; + } + word = mfree(word); + } + r = extract_first_word(&p, &word, ":", 0); if (r == -ENOMEM) return log_oom(); - if (r <= 0) { + if (r <= 0 || isempty(p)) { log_syntax(unit, LOG_ERR, filename, line, r, "Invalid DHCP option, ignoring assignment: %s", rvalue); return 0; @@ -588,7 +607,7 @@ int config_parse_dhcp_send_option( } if (ltype == AF_INET6) { - r = sd_dhcp6_option_new(u16, udata, sz, &opt6); + r = sd_dhcp6_option_new(u16, udata, sz, enterprise_identifier, &opt6); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, "Failed to store DHCP option '%s', ignoring assignment: %m", rvalue); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index d0972f3593..a160fd70d9 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -756,6 +756,7 @@ 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 *vendor_option; sd_dhcp6_option *send_option; void *request_options; const DUID *duid; @@ -854,6 +855,14 @@ int dhcp6_configure(Link *link) { return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set vendor class: %m"); } + ORDERED_HASHMAP_FOREACH(vendor_option, link->network->dhcp6_client_send_vendor_options, i) { + r = sd_dhcp6_client_add_vendor_option(client, vendor_option); + if (r == -EEXIST) + continue; + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set vendor option: %m"); + } + r = sd_dhcp6_client_set_callback(client, dhcp6_handler, link); if (r < 0) return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set callback: %m"); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index d318b7d891..4de2e5e862 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -198,6 +198,7 @@ DHCPv6.MUDURL, config_parse_dhcp6_mud_url, DHCPv6.RequestOptions, config_parse_dhcp_request_options, AF_INET6, 0 DHCPv6.UserClass, config_parse_dhcp_user_class, AF_INET6, offsetof(Network, dhcp6_user_class) DHCPv6.VendorClass, config_parse_dhcp_vendor_class, 0, offsetof(Network, dhcp6_vendor_class) +DHCPv6.SendVendorOption, config_parse_dhcp_send_option, AF_INET6, offsetof(Network, dhcp6_client_send_vendor_options) DHCPv6.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information) DHCPv6.AssignAcquiredDelegatedPrefixAddress, config_parse_bool, 0, offsetof(Network, dhcp6_pd_assign_prefix) DHCPv6.PrefixDelegationHint, config_parse_dhcp6_pd_hint, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index ae466a2310..c895262a0d 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -749,6 +749,7 @@ static Network *network_free(Network *network) { ordered_hashmap_free(network->dhcp_server_send_options); ordered_hashmap_free(network->dhcp_server_send_vendor_options); ordered_hashmap_free(network->ipv6_tokens); + ordered_hashmap_free(network->dhcp6_client_send_vendor_options); return mfree(network); } diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 441bba1273..0e879be01b 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -137,6 +137,7 @@ struct Network { char **dhcp6_vendor_class; struct in6_addr dhcp6_pd_address; OrderedHashmap *dhcp6_client_send_options; + OrderedHashmap *dhcp6_client_send_vendor_options; Set *dhcp6_request_options; /* DHCP Server Support */ diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index a1a6cdd233..faa38a1c1e 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -120,6 +120,7 @@ RequestOptions= UserClass= VendorClass= AssignAcquiredDelegatedPrefixAddress= +SendVendorOption= [Route] Destination= Protocol= From 1c3ec1cd459eae06b2c3fa2e13b7e96f2e559018 Mon Sep 17 00:00:00 2001 From: Susant Sahani Date: Fri, 22 May 2020 08:53:36 +0200 Subject: [PATCH 3/3] network: dhcp6 - fix memory leak --- src/network/networkd-network.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index c895262a0d..e34dd0e4cd 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -749,6 +749,7 @@ static Network *network_free(Network *network) { ordered_hashmap_free(network->dhcp_server_send_options); ordered_hashmap_free(network->dhcp_server_send_vendor_options); ordered_hashmap_free(network->ipv6_tokens); + ordered_hashmap_free(network->dhcp6_client_send_options); ordered_hashmap_free(network->dhcp6_client_send_vendor_options); return mfree(network);