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/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/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 6d1496306c..6a1083fd5c 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 d83da26f5a..e8419426f2 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -752,6 +752,8 @@ 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); } 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/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); 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=