network: implement RFC4039 DHCP Rapid Commit

This implements the DHCPv4 equivalent of the DHCPv6 Rapid Commit option,
enabling a lease to be selected in an accelerated 2-message exchange
instead of the typical 4-message exchange.
This commit is contained in:
Ronan Pigott 2023-10-24 11:01:32 -07:00
parent 5516b0dd20
commit 808b65a087
11 changed files with 115 additions and 20 deletions

5
NEWS
View file

@ -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

View file

@ -2219,6 +2219,21 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>RapidCommit=</varname></term>
<listitem>
<para>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
<ulink url="https://tools.ietf.org/html/rfc4039">RFC 4039</ulink> for details.
Defaults to true.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>Anonymize=</varname></term>
<listitem>

View file

@ -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;

View file

@ -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);

View file

@ -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)

View file

@ -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)

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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;

View file

@ -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,