diff --git a/NEWS b/NEWS
index 9767421f59..62cc4698dc 100644
--- a/NEWS
+++ b/NEWS
@@ -73,6 +73,11 @@ CHANGES WITH 255 in spe:
already use 'prefixstable' addresses with wireless networks, the
stable address chosen will be changed by the update.
+ * The DHCPv4 client gained a RapidCommit option, default true, which
+ enables RFC4039 Rapid Commit behavior to obtain a lease in a
+ simplified 2-message exchange instead of the typical 4-message
+ exchange if also supported by the DHCP server.
+
Changes in systemd-analyze:
* "systemd-analyze plot" has gained tooltips on each unit name with
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index d939c29f71..684a54e08b 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -2219,6 +2219,21 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix
+
+ RapidCommit=
+
+ Takes a boolean. The DHCPv4 client can obtain configuration parameters from a DHCPv4 server
+ through a rapid two-message exchange (discover and ack). When the rapid commit option is set by
+ both the DHCPv4 client and the DHCPv4 server, the two-message exchange is used. Otherwise, the
+ four-message exchange (discover, offer, request, and ack) is used. The two-message exchange
+ provides faster client configuration. See
+ RFC 4039 for details.
+ Defaults to true.
+
+
+
+
+
Anonymize=
diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h
index ce1342e9fd..0b72813733 100644
--- a/src/libsystemd-network/dhcp-lease-internal.h
+++ b/src/libsystemd-network/dhcp-lease-internal.h
@@ -50,6 +50,8 @@ struct sd_dhcp_lease {
struct in_addr *router;
size_t router_size;
+ bool rapid_commit;
+
DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
struct sd_dhcp_route *static_routes;
diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c
index 3e98b683ec..cbdeb42eff 100644
--- a/src/libsystemd-network/fuzz-dhcp-client.c
+++ b/src/libsystemd-network/fuzz-dhcp-client.c
@@ -75,7 +75,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
client->xid = 2;
client->state = DHCP_STATE_SELECTING;
- (void) client_handle_offer(client, (DHCPMessage*) data, size);
+ (void) client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size);
assert_se(sd_dhcp_client_stop(client) >= 0);
diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c
index 87d18269e4..2a65d6d0ea 100644
--- a/src/libsystemd-network/sd-dhcp-client.c
+++ b/src/libsystemd-network/sd-dhcp-client.c
@@ -94,6 +94,7 @@ struct sd_dhcp_client {
bool request_broadcast;
Set *req_opts;
bool anonymize;
+ bool rapid_commit;
be32_t last_addr;
struct hw_addr_data hw_addr;
struct hw_addr_data bcast_addr;
@@ -576,6 +577,13 @@ int sd_dhcp_client_set_iaid_duid_raw(
return 0;
}
+int sd_dhcp_client_set_rapid_commit(sd_dhcp_client *client, bool rapid_commit) {
+ assert_return(client, -EINVAL);
+
+ client->rapid_commit = !client->anonymize && rapid_commit;
+ return 0;
+}
+
int sd_dhcp_client_set_hostname(
sd_dhcp_client *client,
const char *hostname) {
@@ -1131,6 +1139,13 @@ static int client_send_discover(sd_dhcp_client *client) {
return r;
}
+ if (client->rapid_commit) {
+ r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0,
+ SD_DHCP_OPTION_RAPID_COMMIT, 0, NULL);
+ if (r < 0)
+ return r;
+ }
+
r = client_append_common_discover_request_options(client, discover, &optoffset, optlen);
if (r < 0)
return r;
@@ -1359,6 +1374,9 @@ static int client_timeout_resend(
if (r < 0 && client->attempt >= client->max_attempts)
goto error;
+ if (client->rapid_commit)
+ client->request_sent = time_now;
+
break;
case DHCP_STATE_INIT_REBOOT:
@@ -1583,12 +1601,22 @@ static int client_parse_message(
switch (client->state) {
case DHCP_STATE_SELECTING:
- if (r != DHCP_OFFER)
+ if (r == DHCP_ACK) {
+ if (!client->rapid_commit)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "received unexpected ACK, ignoring.");
+ if (!lease->rapid_commit)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "received rapid ACK without Rapid Commit option, ignoring.");
+ } else if (r == DHCP_OFFER) {
+ if (lease->rapid_commit)
+ return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
+ "received OFFER with Rapid Commit option, ignoring");
+ if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0)
+ lease->lifetime = client->fallback_lease_lifetime;
+ } else
return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
- "received message was not an OFFER, ignoring.");
-
- if (lease->lifetime == 0 && client->fallback_lease_lifetime > 0)
- lease->lifetime = client->fallback_lease_lifetime;
+ "received unexpected message, ignoring.");
break;
@@ -1641,7 +1669,7 @@ static int client_parse_message(
return 0;
}
-static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *message, size_t len) {
+static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage *message, size_t len) {
_cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL;
int r;
@@ -1654,6 +1682,11 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *message, siz
dhcp_lease_unref_and_replace(client->lease, lease);
+ if (client->lease->rapid_commit) {
+ log_dhcp_client(client, "ACK");
+ return SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
+ }
+
if (client_notify(client, SD_DHCP_CLIENT_EVENT_SELECTING) < 0)
return -ENOMSG;
@@ -1955,6 +1988,27 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
return client_enter_bound_now(client, notify_event);
}
+static int client_restart(sd_dhcp_client *client) {
+ int r;
+ assert(client);
+
+ client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
+
+ r = client_initialize(client);
+ if (r < 0)
+ return r;
+
+ r = client_start_delayed(client);
+ if (r < 0)
+ return r;
+
+ log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC));
+
+ client->start_delay = CLAMP(client->start_delay * 2,
+ RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC);
+ return 0;
+}
+
static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) {
DHCP_CLIENT_DONT_DESTROY(client);
int r;
@@ -1966,13 +2020,26 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
switch (client->state) {
case DHCP_STATE_SELECTING:
- r = client_handle_offer(client, message, len);
+ r = client_handle_offer_or_rapid_ack(client, message, len);
if (ERRNO_IS_NEG_RESOURCE(r))
goto error;
+
+ if (r == -EADDRNOTAVAIL) {
+ /* got a rapid NAK, let's restart the client */
+ r = client_restart(client);
+ if (r < 0)
+ goto error;
+
+ return 0;
+ }
if (r < 0)
return 0; /* invalid message, let's ignore it */
- r = client_enter_requesting(client);
+ if (client->lease->rapid_commit)
+ /* got a succssful rapid commit */
+ r = client_enter_bound(client, r);
+ else
+ r = client_enter_requesting(client);
break;
case DHCP_STATE_REBOOTING:
@@ -1985,20 +2052,10 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, i
goto error;
if (r == -EADDRNOTAVAIL) {
/* got a NAK, let's restart the client */
- client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
-
- r = client_initialize(client);
+ r = client_restart(client);
if (r < 0)
goto error;
- r = client_start_delayed(client);
- if (r < 0)
- goto error;
-
- log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC));
-
- client->start_delay = CLAMP(client->start_delay * 2,
- RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC);
return 0;
}
if (r < 0)
diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c
index e0e8e81750..1442746900 100644
--- a/src/libsystemd-network/sd-dhcp-lease.c
+++ b/src/libsystemd-network/sd-dhcp-lease.c
@@ -735,6 +735,12 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
log_debug_errno(r, "Failed to parse router addresses, ignoring: %m");
break;
+ case SD_DHCP_OPTION_RAPID_COMMIT:
+ if (len > 0)
+ log_debug("Invalid DHCP Rapid Commit option, ignorning.");
+ lease->rapid_commit = true;
+ break;
+
case SD_DHCP_OPTION_DOMAIN_NAME_SERVER:
r = lease_parse_in_addrs(option, len, &lease->servers[SD_DHCP_LEASE_DNS].addr, &lease->servers[SD_DHCP_LEASE_DNS].size);
if (r < 0)
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
index 5ad8128da7..031507b663 100644
--- a/src/network/networkd-dhcp4.c
+++ b/src/network/networkd-dhcp4.c
@@ -1485,6 +1485,10 @@ static int dhcp4_configure(Link *link) {
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to attach device: %m");
+ r = sd_dhcp_client_set_rapid_commit(link->dhcp_client, link->network->dhcp_use_rapid_commit);
+ if (r < 0)
+ return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set rapid commit: %m");
+
r = sd_dhcp_client_set_mac(link->dhcp_client,
link->hw_addr.bytes,
link->bcast_addr.length > 0 ? link->bcast_addr.bytes : NULL,
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 02fda41243..b87ae006f1 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -261,6 +261,7 @@ DHCPv4.Use6RD, config_parse_bool,
DHCPv4.IPv6OnlyMode, config_parse_tristate, 0, offsetof(Network, dhcp_ipv6_only_mode)
DHCPv4.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_netlabel)
DHCPv4.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp_nft_set_context)
+DHCPv4.RapidCommit config_parse_bool, 0, offsetof(Network, dhcp_use_rapid_commit)
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix)
DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 3de4ea8dec..4a38bc88ae 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -396,6 +396,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.dhcp_send_hostname = true,
.dhcp_send_release = true,
.dhcp_route_metric = DHCP_ROUTE_METRIC,
+ .dhcp_use_rapid_commit = true,
.dhcp_client_identifier = _DHCP_CLIENT_ID_INVALID,
.dhcp_route_table = RT_TABLE_MAIN,
.dhcp_ip_service_type = -1,
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
index 1091ce289c..e0891f9c18 100644
--- a/src/network/networkd-network.h
+++ b/src/network/networkd-network.h
@@ -140,6 +140,7 @@ struct Network {
bool dhcp_send_hostname;
int dhcp_broadcast;
int dhcp_ipv6_only_mode;
+ bool dhcp_use_rapid_commit;
bool dhcp_use_dns;
bool dhcp_use_dns_set;
bool dhcp_routes_to_dns;
diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h
index 0996aeeb88..3a8abc82ed 100644
--- a/src/systemd/sd-dhcp-client.h
+++ b/src/systemd/sd-dhcp-client.h
@@ -104,6 +104,9 @@ __extension__ int sd_dhcp_client_set_iaid_duid_raw(
uint16_t duid_type,
const uint8_t *duid,
size_t duid_len);
+__extension__ int sd_dhcp_client_set_rapid_commit(
+ sd_dhcp_client *client,
+ bool rapid_commit);
int sd_dhcp_client_get_client_id(
sd_dhcp_client *client,
uint8_t *ret_type,