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=