diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index ca82f33bb4..2f94dc518d 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -1470,6 +1470,14 @@
+
+ SendRelease=
+
+ When true, the DHCPv4 client sends a DHCP release packet when it stops.
+ Defaults to false.
+
+
+
RapidCommit=
diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c
index 84ce8e0da8..6e077c0860 100644
--- a/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/libsystemd-network/sd-dhcp-client.c
@@ -606,7 +606,7 @@ static int client_message_init(
assert(ret);
assert(_optlen);
assert(_optoffset);
- assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST));
+ assert(IN_SET(type, DHCP_DISCOVER, DHCP_REQUEST, DHCP_RELEASE));
optlen = DHCP_MIN_OPTIONS_SIZE;
size = sizeof(DHCPPacket) + optlen;
@@ -697,7 +697,7 @@ static int client_message_init(
MAY contain the Parameter Request List option. */
/* NOTE: in case that there would be an option to do not send
* any PRL at all, the size should be checked before sending */
- if (client->req_opts_size > 0) {
+ if (client->req_opts_size > 0 && type != DHCP_RELEASE) {
r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0,
SD_DHCP_OPTION_PARAMETER_REQUEST_LIST,
client->req_opts_size, client->req_opts);
@@ -729,7 +729,7 @@ static int client_message_init(
*/
/* RFC7844 section 3:
SHOULD NOT contain any other option. */
- if (!client->anonymize) {
+ if (!client->anonymize && type != DHCP_RELEASE) {
max_size = htobe16(size);
r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0,
SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE,
@@ -861,6 +861,41 @@ static int client_send_discover(sd_dhcp_client *client) {
return 0;
}
+static int client_send_release(sd_dhcp_client *client) {
+ _cleanup_free_ DHCPPacket *release = NULL;
+ size_t optoffset, optlen;
+ int r;
+
+ assert(client);
+ assert(!IN_SET(client->state, DHCP_STATE_STOPPED));
+
+ r = client_message_init(client, &release, DHCP_RELEASE,
+ &optlen, &optoffset);
+ if (r < 0)
+ return r;
+
+ /* Fill up release IP and MAC */
+ release->dhcp.ciaddr = client->lease->address;
+ memcpy(&release->dhcp.chaddr, &client->mac_addr, client->mac_addr_len);
+
+ r = dhcp_option_append(&release->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_END, 0, NULL);
+ if (r < 0)
+ return r;
+
+ r = dhcp_network_send_udp_socket(client->fd,
+ client->lease->server_address,
+ DHCP_PORT_SERVER,
+ &release->dhcp,
+ sizeof(DHCPMessage) + optoffset);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "RELEASE");
+
+ return 0;
+}
+
static int client_send_request(sd_dhcp_client *client) {
_cleanup_free_ DHCPPacket *request = NULL;
size_t optoffset, optlen;
@@ -1858,6 +1893,14 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
return r;
}
+int sd_dhcp_client_send_release(sd_dhcp_client *client) {
+ assert_return(client, -EINVAL);
+
+ client_send_release(client);
+
+ return 0;
+}
+
int sd_dhcp_client_stop(sd_dhcp_client *client) {
DHCP_CLIENT_DONT_DESTROY(client);
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index 85e088e204..81c694822c 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -564,6 +564,9 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m");
}
+ if (link->network->dhcp_send_release)
+ (void) sd_dhcp_client_send_release(client);
+
_fallthrough_;
case SD_DHCP_CLIENT_EVENT_EXPIRED:
case SD_DHCP_CLIENT_EVENT_IP_CHANGE:
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index a57d9395f6..cbb1dd8180 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -149,6 +149,7 @@ DHCP.RouteTable, config_parse_section_route_table,
DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone)
DHCP.IAID, config_parse_iaid, 0, 0
DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port)
+DHCP.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp_send_release)
DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, rapid_commit)
DHCP.BlackList, config_parse_dhcp_black_listed_ip_address, 0, 0
DHCP.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information)
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 9217e838d2..d00062fe59 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -128,6 +128,7 @@ struct Network {
bool rapid_commit;
bool dhcp_use_hostname;
bool dhcp_route_table_set;
+ bool dhcp_send_release;
DHCPUseDomains dhcp_use_domains;
Set *dhcp_black_listed_ip;
diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h
index 5dbfe8e4a1..ab62368e9c 100644
--- a/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/sd-dhcp-client.h
@@ -176,6 +176,7 @@ int sd_dhcp_client_get_lease(
int sd_dhcp_client_stop(sd_dhcp_client *client);
int sd_dhcp_client_start(sd_dhcp_client *client);
+int sd_dhcp_client_send_release(sd_dhcp_client *client);
sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client);
sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client);
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index 5d48f0825b..3742d02d3d 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -64,6 +64,7 @@ ListenPort=
UseTimezone=
RouteTable=
BlackList=
+SendRelease=
[Route]
Destination=
Protocol=