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=