sd-dhcp-client: support IPv6 only mode

This makes sd-dhcp-client optionally request IPv6 only preferred
option (RFC 8925).
This commit is contained in:
Yu Watanabe
2023-10-11 21:42:13 +09:00
parent 34bea0a1e9
commit a91b888fff
4 changed files with 106 additions and 5 deletions
@@ -35,6 +35,7 @@ struct sd_dhcp_lease {
usec_t t2;
usec_t lifetime;
triple_timestamp timestamp;
usec_t ipv6_only_preferred_usec;
/* each 0 if unset */
be32_t address;
+94 -5
View File
@@ -116,6 +116,7 @@ struct sd_dhcp_client {
sd_event_source *timeout_t1;
sd_event_source *timeout_t2;
sd_event_source *timeout_expire;
sd_event_source *timeout_ipv6_only_mode;
sd_dhcp_client_callback_t callback;
void *userdata;
sd_dhcp_client_callback_t state_callback;
@@ -125,6 +126,7 @@ struct sd_dhcp_client {
int ip_service_type;
int socket_priority;
bool socket_priority_set;
bool ipv6_acquired;
};
static const uint8_t default_req_opts[] = {
@@ -280,6 +282,12 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) {
return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option));
}
static int client_request_contains(sd_dhcp_client *client, uint8_t option) {
assert(client);
return set_contains(client->req_opts, UINT8_TO_PTR(option));
}
int sd_dhcp_client_set_request_address(
sd_dhcp_client *client,
const struct in_addr *last_addr) {
@@ -778,6 +786,7 @@ static int client_initialize(sd_dhcp_client *client) {
(void) event_source_disable(client->timeout_t1);
(void) event_source_disable(client->timeout_t2);
(void) event_source_disable(client->timeout_expire);
(void) event_source_disable(client->timeout_ipv6_only_mode);
client->attempt = 0;
@@ -1424,6 +1433,8 @@ static int client_initialize_time_events(sd_dhcp_client *client) {
assert(client);
assert(client->event);
(void) event_source_disable(client->timeout_ipv6_only_mode);
if (client->start_delay > 0) {
assert_se(sd_event_now(client->event, CLOCK_BOOTTIME, &usec) >= 0);
usec = usec_add(usec, client->start_delay);
@@ -1613,6 +1624,16 @@ static int client_parse_message(
return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
"received lease lacks subnet mask, and a fallback one cannot be generated, ignoring.");
/* RFC 8925 section 3.2
* If the client did not include the IPv6-Only Preferred option code in the Parameter Request List in
* the DHCPDISCOVER or DHCPREQUEST message, it MUST ignore the IPv6-Only Preferred option in any
* messages received from the server. */
if (lease->ipv6_only_preferred_usec > 0 &&
!client_request_contains(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) {
log_dhcp_client(client, "Received message with unrequested IPv6-only preferred option, ignoring the option.");
lease->ipv6_only_preferred_usec = 0;
}
*ret = TAKE_PTR(lease);
return 0;
}
@@ -1639,13 +1660,26 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *message, siz
static int client_enter_requesting(sd_dhcp_client *client) {
assert(client);
assert(client->lease);
if (client->lease->ipv6_only_preferred_usec > 0) {
if (client->ipv6_acquired) {
log_dhcp_client(client,
"Received an OFFER with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client.");
return sd_dhcp_client_stop(client);
}
log_dhcp_client(client,
"Received an OFFER with IPv6-only preferred option, delaying to send REQUEST with %s.",
FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC));
}
client_set_state(client, DHCP_STATE_REQUESTING);
client->attempt = 0;
return event_reset_time_relative(client->event, &client->timeout_resend,
CLOCK_BOOTTIME,
0, 0,
client->lease->ipv6_only_preferred_usec, 0,
client_timeout_resend, client,
client->event_priority, "dhcp4-resend-timer",
/* force_reset = */ true);
@@ -1814,7 +1848,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
return 0;
}
static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) {
int r;
assert(client);
@@ -1822,9 +1856,6 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING))
notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
client->start_delay = 0;
(void) event_source_disable(client->timeout_resend);
client_set_state(client, DHCP_STATE_BOUND);
client->attempt = 0;
@@ -1853,6 +1884,47 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
return 0;
}
static int client_timeout_ipv6_only_mode(sd_event_source *s, uint64_t usec, void *userdata) {
sd_dhcp_client *client = ASSERT_PTR(userdata);
DHCP_CLIENT_DONT_DESTROY(client);
int r;
r = client_enter_bound_now(client, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE);
if (r < 0)
client_stop(client, r);
return 0;
}
static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
assert(client);
assert(client->lease);
client->start_delay = 0;
(void) event_source_disable(client->timeout_resend);
if (client->state == DHCP_STATE_REBOOTING && client->lease->ipv6_only_preferred_usec > 0) {
if (client->ipv6_acquired) {
log_dhcp_client(client,
"Received an ACK with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client.");
return sd_dhcp_client_stop(client);
}
log_dhcp_client(client,
"Received an ACK with IPv6-only preferred option, delaying to enter bound state with %s.",
FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC));
return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode,
CLOCK_BOOTTIME,
client->lease->ipv6_only_preferred_usec, 0,
client_timeout_ipv6_only_mode, client,
client->event_priority, "dhcp4-ipv6-only-mode",
/* force_reset = */ true);
}
return client_enter_bound_now(client, notify_event);
}
static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) {
DHCP_CLIENT_DONT_DESTROY(client);
int r;
@@ -2108,6 +2180,9 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
assert_return(client, -EINVAL);
/* Note, do not reset the flag in client_initialize(), as it is also called on expire. */
client->ipv6_acquired = false;
r = client_initialize(client);
if (r < 0)
return r;
@@ -2224,6 +2299,20 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) {
return 0;
}
int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have) {
if (!client)
return 0;
/* We have already received a message with IPv6-Only preferred option, and are waiting for IPv6
* connectivity or timeout, let's stop the client. */
if (have && sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) > 0)
return sd_dhcp_client_stop(client);
/* Otherwise, save that the host already has IPv6 connectivity. */
client->ipv6_acquired = have;
return 0;
}
int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) {
int r;
+10
View File
@@ -884,6 +884,16 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
log_debug_errno(r, "Failed to parse 6rd option, ignoring: %m");
break;
case SD_DHCP_OPTION_IPV6_ONLY_PREFERRED:
r = lease_parse_be32_seconds(option, len, /* max_as_infinity = */ false, &lease->ipv6_only_preferred_usec);
if (r < 0)
log_debug_errno(r, "Failed to parse IPv6 only preferred option, ignoring: %m");
else if (lease->ipv6_only_preferred_usec < MIN_V6ONLY_WAIT_USEC &&
!network_test_mode_enabled())
lease->ipv6_only_preferred_usec = MIN_V6ONLY_WAIT_USEC;
break;
case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST:
r = dhcp_lease_insert_private_option(lease, code, option, len);
if (r < 0)
+1
View File
@@ -152,6 +152,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client);
int sd_dhcp_client_send_release(sd_dhcp_client *client);
int sd_dhcp_client_send_decline(sd_dhcp_client *client);
int sd_dhcp_client_send_renew(sd_dhcp_client *client);
int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have);
sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client);
sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client);