diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index b13fcd1893..5457e668dd 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1635,7 +1635,22 @@
SendOption=
- Send an arbitrary option in the DHCPv4 request. Takes a DHCP option number, data type
+ Send an arbitrary raw option in the DHCPv4 request. Takes a DHCP option number, data type
+ and data separated with a colon
+ (option:type:value).
+ The option number must be an integer in the range 1..254. The type takes one of uint8,
+ uint16, uint32, ipv4address, 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.
+
+
+
+
+ SendVendorOption=
+
+ Send an arbitrary vendor option in the DHCPv4 request. Takes a DHCP option number, data type
and data separated with a colon
(option:type:value).
The option number must be an integer in the range 1..254. The type takes one of uint8,
@@ -1926,6 +1941,20 @@
+
+ SendVendorOption=
+
+ Send a vendor 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
+ 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.
+
+
+
diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index 42c3ceb8b1..41901894f5 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -58,7 +58,8 @@ struct sd_dhcp_server {
struct in_addr *ntp, *dns, *sip;
unsigned n_ntp, n_dns, n_sip;
- OrderedHashmap *raw_option;
+ OrderedHashmap *extra_options;
+ OrderedHashmap *vendor_options;
bool emit_router;
diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c
index 4122d08d96..82553e79ca 100644
--- a/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/libsystemd-network/sd-dhcp-client.c
@@ -89,7 +89,8 @@ struct sd_dhcp_client {
usec_t start_time;
uint64_t attempt;
uint64_t max_attempts;
- OrderedHashmap *options;
+ OrderedHashmap *extra_options;
+ OrderedHashmap *vendor_options;
usec_t request_sent;
sd_event_source *timeout_t1;
sd_event_source *timeout_t2;
@@ -540,17 +541,17 @@ int sd_dhcp_client_set_max_attempts(sd_dhcp_client *client, uint64_t max_attempt
return 0;
}
-int sd_dhcp_client_set_dhcp_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v) {
int r;
assert_return(client, -EINVAL);
assert_return(v, -EINVAL);
- r = ordered_hashmap_ensure_allocated(&client->options, &dhcp_option_hash_ops);
+ r = ordered_hashmap_ensure_allocated(&client->extra_options, &dhcp_option_hash_ops);
if (r < 0)
return r;
- r = ordered_hashmap_put(client->options, UINT_TO_PTR(v->option), v);
+ r = ordered_hashmap_put(client->extra_options, UINT_TO_PTR(v->option), v);
if (r < 0)
return r;
@@ -558,6 +559,25 @@ int sd_dhcp_client_set_dhcp_option(sd_dhcp_client *client, sd_dhcp_option *v) {
return 0;
}
+int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v) {
+ int r;
+
+ assert_return(client, -EINVAL);
+ assert_return(v, -EINVAL);
+
+ r = ordered_hashmap_ensure_allocated(&client->vendor_options, &dhcp_option_hash_ops);
+ if (r < 0)
+ return -ENOMEM;
+
+ r = ordered_hashmap_put(client->vendor_options, v, v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp_option_ref(v);
+
+ return 1;
+}
+
int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
assert_return(client, -EINVAL);
@@ -884,13 +904,22 @@ static int client_send_discover(sd_dhcp_client *client) {
return r;
}
- ORDERED_HASHMAP_FOREACH(j, client->options, i) {
+ ORDERED_HASHMAP_FOREACH(j, client->extra_options, i) {
r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
j->option, j->length, j->data);
if (r < 0)
return r;
}
+ if (!ordered_hashmap_isempty(client->vendor_options)) {
+ r = dhcp_option_append(
+ &discover->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_VENDOR_SPECIFIC,
+ ordered_hashmap_size(client->vendor_options), client->vendor_options);
+ if (r < 0)
+ return r;
+ }
+
r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
SD_DHCP_OPTION_END, 0, NULL);
if (r < 0)
@@ -2073,7 +2102,8 @@ static sd_dhcp_client *dhcp_client_free(sd_dhcp_client *client) {
free(client->hostname);
free(client->vendor_class_identifier);
client->user_class = strv_free(client->user_class);
- ordered_hashmap_free(client->options);
+ ordered_hashmap_free(client->extra_options);
+ ordered_hashmap_free(client->vendor_options);
return mfree(client);
}
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index 546b5d02c4..a0b779bc09 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -143,7 +143,8 @@ static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) {
hashmap_free(server->leases_by_client_id);
- ordered_hashmap_free(server->raw_option);
+ ordered_hashmap_free(server->extra_options);
+ ordered_hashmap_free(server->vendor_options);
free(server->bound_leases);
return mfree(server);
@@ -455,6 +456,8 @@ static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req,
be32_t address) {
_cleanup_free_ DHCPPacket *packet = NULL;
be32_t lease_time;
+ sd_dhcp_option *j;
+ Iterator i;
size_t offset;
int r;
@@ -519,11 +522,18 @@ static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req,
return r;
}
- if (!ordered_hashmap_isempty(server->raw_option)) {
+ ORDERED_HASHMAP_FOREACH(j, server->extra_options, i) {
+ r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+ j->option, j->length, j->data);
+ if (r < 0)
+ return r;
+ }
+
+ if (!ordered_hashmap_isempty(server->vendor_options)) {
r = dhcp_option_append(
&packet->dhcp, req->max_optlen, &offset, 0,
SD_DHCP_OPTION_VENDOR_SPECIFIC,
- ordered_hashmap_size(server->raw_option), server->raw_option);
+ ordered_hashmap_size(server->vendor_options), server->vendor_options);
if (r < 0)
return r;
}
@@ -1188,11 +1198,29 @@ int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v) {
assert_return(server, -EINVAL);
assert_return(v, -EINVAL);
- r = ordered_hashmap_ensure_allocated(&server->raw_option, &dhcp_option_hash_ops);
+ r = ordered_hashmap_ensure_allocated(&server->extra_options, &dhcp_option_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(server->extra_options, UINT_TO_PTR(v->option), v);
+ if (r < 0)
+ return r;
+
+ sd_dhcp_option_ref(v);
+ return 0;
+}
+
+int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v) {
+ int r;
+
+ assert_return(server, -EINVAL);
+ assert_return(v, -EINVAL);
+
+ r = ordered_hashmap_ensure_allocated(&server->vendor_options, &dhcp_option_hash_ops);
if (r < 0)
return -ENOMEM;
- r = ordered_hashmap_put(server->raw_option, v, v);
+ r = ordered_hashmap_put(server->vendor_options, v, v);
if (r < 0)
return r;
diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c
index bee75a6930..2ec742b5e3 100644
--- a/src/network/networkd-dhcp-server.c
+++ b/src/network/networkd-dhcp-server.c
@@ -312,6 +312,14 @@ int dhcp4_server_configure(Link *link) {
return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m");
}
+ ORDERED_HASHMAP_FOREACH(p, link->network->dhcp_server_send_vendor_options, i) {
+ r = sd_dhcp_server_add_vendor_option(link->dhcp_server, p);
+ if (r == -EEXIST)
+ continue;
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to set DHCPv4 option: %m");
+ }
+
if (!sd_dhcp_server_is_running(link->dhcp_server)) {
r = sd_dhcp_server_start(link->dhcp_server);
if (r < 0)
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index 64190375a4..83fb25264a 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -1430,7 +1430,17 @@ int dhcp4_configure(Link *link) {
}
ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_client_send_options, i) {
- r = sd_dhcp_client_set_dhcp_option(link->dhcp_client, send_option);
+ r = sd_dhcp_client_add_option(link->dhcp_client, send_option);
+ if (r == -EEXIST)
+ continue;
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set send option: %m");
+ }
+
+ ORDERED_HASHMAP_FOREACH(send_option, link->network->dhcp_client_send_vendor_options, i) {
+ r = sd_dhcp_client_add_vendor_option(link->dhcp_client, send_option);
+ if (r == -EEXIST)
+ continue;
if (r < 0)
return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set send option: %m");
}
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index c75a07e4f3..fd996327a5 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -184,6 +184,7 @@ DHCPv4.SendDecline, config_parse_bool,
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.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)
DHCPv6.UseNTP, config_parse_bool, 0, offsetof(Network, dhcp6_use_ntp)
@@ -211,6 +212,7 @@ DHCPServer.EmitTimezone, config_parse_bool,
DHCPServer.Timezone, config_parse_timezone, 0, offsetof(Network, dhcp_server_timezone)
DHCPServer.PoolOffset, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_offset)
DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size)
+DHCPServer.SendVendorOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_server_send_vendor_options)
DHCPServer.SendOption, config_parse_dhcp_send_option, 0, offsetof(Network, dhcp_server_send_options)
Bridge.Cost, config_parse_uint32, 0, offsetof(Network, cost)
Bridge.UseBPDU, config_parse_tristate, 0, offsetof(Network, use_bpdu)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 248172f8a2..e7ead446c7 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -723,7 +723,9 @@ static Network *network_free(Network *network) {
set_free_free(network->dnssec_negative_trust_anchors);
ordered_hashmap_free(network->dhcp_client_send_options);
+ ordered_hashmap_free(network->dhcp_client_send_vendor_options);
ordered_hashmap_free(network->dhcp_server_send_options);
+ ordered_hashmap_free(network->dhcp_server_send_vendor_options);
ordered_hashmap_free(network->ipv6_tokens);
return mfree(network);
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index b687282124..f747ccaf10 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -121,7 +121,9 @@ struct Network {
Set *dhcp_black_listed_ip;
Set *dhcp_request_options;
OrderedHashmap *dhcp_client_send_options;
+ OrderedHashmap *dhcp_client_send_vendor_options;
OrderedHashmap *dhcp_server_send_options;
+ OrderedHashmap *dhcp_server_send_vendor_options;
/* DHCPv6 Client support*/
bool dhcp6_use_dns;
diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h
index 0002ea0e59..9dd562fa43 100644
--- a/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/sd-dhcp-client.h
@@ -179,7 +179,8 @@ int sd_dhcp_client_set_service_type(
sd_dhcp_client *client,
int type);
-int sd_dhcp_client_set_dhcp_option(sd_dhcp_client *client, sd_dhcp_option *v);
+int sd_dhcp_client_add_option(sd_dhcp_client *client, sd_dhcp_option *v);
+int sd_dhcp_client_add_vendor_option(sd_dhcp_client *client, sd_dhcp_option *v);
int sd_dhcp_client_stop(sd_dhcp_client *client);
int sd_dhcp_client_start(sd_dhcp_client *client);
diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h
index 5950506c9b..55272b5164 100644
--- a/src/systemd/sd-dhcp-server.h
+++ b/src/systemd/sd-dhcp-server.h
@@ -53,6 +53,7 @@ int sd_dhcp_server_set_sip(sd_dhcp_server *server, const struct in_addr sip[], u
int sd_dhcp_server_set_emit_router(sd_dhcp_server *server, int enabled);
int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v);
+int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v);
int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint32_t t);
int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t);
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index b3465ceb8c..4418fc0149 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -100,6 +100,7 @@ SendRelease=
MaxAttempts=
IPServiceType=
SendOption=
+SendVendorOption=
SendDecline=
RouteMTUBytes=
[DHCPv6]
@@ -273,6 +274,7 @@ DefaultLeaseTimeSec=
EmitTimezone=
DNS=
SendOption=
+SendVendorOption=
[NextHop]
Id=
Gateway=