diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 2ead483519..bb6c35f9ba 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1430,6 +1430,18 @@
sent even if this is set to true.
+
+
+ MUDURL=
+
+ When configured, the Manufacturer Usage Descriptions (MUD) URL will be sent to the
+ DHCPv4 server. Takes an URL of length up to 255 characters. A superficial verification that
+ the string is a valid URL will be performed. DHCPv4 clients are intended to have at most one
+ MUD URL associated with them. See
+ RFC 8520.
+
+
+
UseHostname=
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index 83fb25264a..48e5c15fd0 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -5,6 +5,7 @@
#include
#include
+#include "escape.h"
#include "alloc-util.h"
#include "dhcp-client-internal.h"
#include "hostname-util.h"
@@ -17,6 +18,7 @@
#include "string-table.h"
#include "string-util.h"
#include "sysctl-util.h"
+#include "web-util.h"
static int dhcp_remove_routes(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all);
static int dhcp_remove_router(Link *link, sd_dhcp_lease *lease, const struct in_addr *address, bool remove_all);
@@ -1456,6 +1458,13 @@ int dhcp4_configure(Link *link) {
return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set vendor class identifier: %m");
}
+ if (link->network->dhcp_mudurl) {
+ r = sd_dhcp_client_set_mud_url(link->dhcp_client,
+ link->network->dhcp_mudurl);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set MUD URL: %m");
+ }
+
if (link->network->dhcp_user_class) {
r = sd_dhcp_client_set_user_class(link->dhcp_client, (const char **) link->network->dhcp_user_class);
if (r < 0)
@@ -1744,6 +1753,48 @@ int config_parse_dhcp_ip_service_type(
return 0;
}
+int config_parse_dhcp_mud_url(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_free_ char *unescaped = NULL;
+ Network *network = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ network->dhcp_mudurl = mfree(network->dhcp_mudurl);
+ return 0;
+ }
+
+ r = cunescape(rvalue, 0, &unescaped);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to Failed to unescape MUD URL, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (!http_url_is_valid(unescaped) || strlen(unescaped) > 255) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Failed to parse MUD URL '%s', ignoring: %m", rvalue);
+
+ return 0;
+ }
+
+ return free_and_strdup_warn(&network->dhcp_mudurl, unescaped);
+}
+
static const char* const dhcp_client_identifier_table[_DHCP_CLIENT_ID_MAX] = {
[DHCP_CLIENT_ID_MAC] = "mac",
[DHCP_CLIENT_ID_DUID] = "duid",
diff --git a/src/network/networkd-dhcp4.h b/src/network/networkd-dhcp4.h
index 95fa5ee4b5..b0c30b598c 100644
--- a/src/network/networkd-dhcp4.h
+++ b/src/network/networkd-dhcp4.h
@@ -28,3 +28,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_max_attempts);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_class);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_request_options);
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_ip_service_type);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_mud_url);
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 04d411c4ad..18ba23bfc8 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -170,6 +170,7 @@ DHCPv4.SendHostname, config_parse_bool,
DHCPv4.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname)
DHCPv4.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast)
DHCPv4.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier)
+DHCPv4.MUDURL, config_parse_dhcp_mud_url, 0, 0
DHCPv4.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0
DHCPv4.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class)
DHCPv4.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index a71fac6790..6afe29d53b 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -640,6 +640,7 @@ static Network *network_free(Network *network) {
free(network->description);
free(network->dhcp_vendor_class_identifier);
+ free(network->dhcp_mudurl);
strv_free(network->dhcp_user_class);
free(network->dhcp_hostname);
set_free(network->dhcp_black_listed_ip);
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index fe28789784..66ee01d7f3 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -91,6 +91,7 @@ struct Network {
AddressFamily dhcp;
DHCPClientIdentifier dhcp_client_identifier;
char *dhcp_vendor_class_identifier;
+ char *dhcp_mudurl;
char **dhcp_user_class;
char *dhcp_hostname;
uint64_t dhcp_max_attempts;
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index e1af206941..01b1b50ff6 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -102,6 +102,7 @@ IPServiceType=
SendOption=
SendVendorOption=
SendDecline=
+MUDURL=
RouteMTUBytes=
[DHCPv6]
UseNTP=