sd-dhcp-client: several refactoring for state machine of the DHCP client (#41796)

This commit is contained in:
Yu Watanabe
2026-04-24 16:06:02 +09:00
committed by GitHub
8 changed files with 507 additions and 458 deletions
+73 -2
View File
@@ -3,8 +3,11 @@
#include "sd-dhcp-client.h"
#include "sd-forward.h"
#include "dhcp-client-id-internal.h"
#include "ether-addr-util.h"
#include "network-common.h"
#include "sd-forward.h"
#include "socket-util.h"
typedef enum DHCPState {
DHCP_STATE_STOPPED,
@@ -22,7 +25,64 @@ typedef enum DHCPState {
DECLARE_STRING_TABLE_LOOKUP_TO_STRING(dhcp_state, DHCPState);
typedef struct sd_dhcp_client sd_dhcp_client;
struct sd_dhcp_client {
unsigned n_ref;
DHCPState state;
sd_event *event;
int event_priority;
sd_event_source *timeout_resend;
int ifindex;
char *ifname;
sd_device *dev;
uint16_t port;
uint16_t server_port;
union sockaddr_union link;
sd_event_source *receive_message;
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;
uint16_t arp_type;
sd_dhcp_client_id client_id;
char *hostname;
char *vendor_class_identifier;
char *mudurl;
char **user_class;
uint32_t mtu;
usec_t fallback_lease_lifetime;
uint32_t xid;
usec_t start_time;
usec_t t1_time;
usec_t t2_time;
usec_t expire_time;
uint64_t discover_attempt;
uint64_t request_attempt;
uint64_t max_discover_attempts;
OrderedHashmap *extra_options;
OrderedHashmap *vendor_options;
sd_event_source *timeout_t1;
sd_event_source *timeout_t2;
sd_event_source *timeout_expire;
sd_dhcp_client_callback_t callback;
void *userdata;
sd_dhcp_client_callback_t state_callback;
void *state_userdata;
sd_dhcp_lease *lease;
usec_t start_delay;
int ip_service_type;
int socket_priority;
bool socket_priority_set;
bool ipv6_acquired;
bool bootp;
bool send_release;
};
int dhcp_client_set_state_callback(
sd_dhcp_client *client,
@@ -30,6 +90,17 @@ int dhcp_client_set_state_callback(
void *userdata);
int dhcp_client_get_state(sd_dhcp_client *client);
int client_receive_message_raw(
sd_event_source *s,
int fd,
uint32_t revents,
void *userdata);
int client_receive_message_udp(
sd_event_source *s,
int fd,
uint32_t revents,
void *userdata);
/* If we are invoking callbacks of a dhcp-client, ensure unreffing the
* client from the callback doesn't destroy the object we are working
* on */
+187
View File
@@ -0,0 +1,187 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-event.h"
#include "dhcp-client-internal.h"
#include "dhcp-client-send.h"
#include "dhcp-lease-internal.h" /* IWYU pragma: keep */
#include "dhcp-network.h"
#include "dhcp-packet.h"
#include "fd-util.h"
#include "socket-util.h"
static int client_get_socket(sd_dhcp_client *client, int domain) {
int r, d, fd;
assert(client);
assert(IN_SET(domain, AF_PACKET, AF_INET));
if (!client->receive_message)
return -EBADF;
fd = sd_event_source_get_io_fd(client->receive_message);
if (fd < 0)
return fd;
r = getsockopt_int(fd, SOL_SOCKET, SO_DOMAIN, &d);
if (r < 0)
return r;
if (d != domain)
return -EBADF;
return fd;
}
static int client_setup_io_event(
sd_dhcp_client *client,
int fd,
sd_event_io_handler_t callback,
const char *description) {
int r;
assert(client);
assert(fd >= 0);
assert(callback);
assert(description);
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
r = sd_event_add_io(client->event, &s, fd, EPOLLIN, callback, client);
if (r < 0)
return r;
r = sd_event_source_set_priority(s, client->event_priority);
if (r < 0)
return r;
r = sd_event_source_set_description(s, description);
if (r < 0)
return r;
r = sd_event_source_set_io_fd_own(s, true);
if (r < 0)
return r;
sd_event_source_disable_unref(client->receive_message);
client->receive_message = TAKE_PTR(s);
return 0;
}
int dhcp_client_send_raw(
sd_dhcp_client *client,
bool expect_reply,
DHCPPacket *packet,
size_t optoffset) {
_cleanup_close_ int fd_close = -EBADF;
int r, fd;
assert(client);
assert(packet);
fd = client_get_socket(client, AF_PACKET);
if (fd < 0) {
fd = dhcp_network_bind_raw_socket(
client->ifindex,
&client->link,
client->xid,
&client->hw_addr,
&client->bcast_addr,
client->arp_type,
client->port,
client->socket_priority_set,
client->socket_priority);
if (fd < 0)
return fd;
fd_close = fd;
}
dhcp_packet_append_ip_headers(
packet,
INADDR_ANY,
client->port,
INADDR_BROADCAST,
client->server_port,
sizeof(DHCPPacket) + optoffset,
client->ip_service_type);
r = dhcp_network_send_raw_socket(
fd,
&client->link,
packet,
sizeof(DHCPPacket) + optoffset);
if (r < 0)
return r;
if (!expect_reply) {
/* We do not expect any replies, hence stop the IO event source if enabled. */
client->receive_message = sd_event_source_disable_unref(client->receive_message);
return 0;
}
if (fd_close < 0)
return 0; /* Already opened socket is reused. Not necessary to setup new IO event source. */
r = client_setup_io_event(client, fd, client_receive_message_raw, "dhcp4-receive-message-raw");
if (r < 0)
return r;
TAKE_FD(fd_close);
return 0;
}
int dhcp_client_send_udp(
sd_dhcp_client *client,
bool expect_reply,
DHCPPacket *packet,
size_t optoffset) {
_cleanup_close_ int fd_close = -EBADF;
int r, fd;
assert(client);
assert(packet);
if (!client->lease || client->lease->address == 0)
return -EADDRNOTAVAIL;
fd = client_get_socket(client, AF_INET);
if (fd < 0) {
fd = dhcp_network_bind_udp_socket(
client->ifindex,
client->lease->address,
client->port,
client->ip_service_type);
if (fd < 0)
return fd;
fd_close = fd;
}
r = dhcp_network_send_udp_socket(
fd,
client->lease->server_address,
client->server_port,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
if (r < 0)
return r;
if (!expect_reply) {
/* We do not expect any replies, hence stop the IO event source if enabled. */
client->receive_message = sd_event_source_disable_unref(client->receive_message);
return 0;
}
if (fd_close < 0)
return 0; /* Already opened socket is reused. Not necessary to setup new IO event source. */
r = client_setup_io_event(client, fd, client_receive_message_udp, "dhcp4-receive-message-udp");
if (r < 0)
return r;
TAKE_FD(fd_close);
return 0;
}
+18
View File
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "sd-forward.h"
#include "dhcp-protocol.h"
int dhcp_client_send_raw(
sd_dhcp_client *client,
bool expect_reply,
DHCPPacket *packet,
size_t optoffset);
int dhcp_client_send_udp(
sd_dhcp_client *client,
bool expect_reply,
DHCPPacket *packet,
size_t optoffset);
@@ -3,6 +3,7 @@
#include <sys/socket.h>
#include "dhcp-network.h"
#include "fd-util.h"
#include "fuzz.h"
#include "network-internal.h"
#include "sd-dhcp-client.c"
+1
View File
@@ -2,6 +2,7 @@
libsystemd_network_sources = files(
'arp-util.c',
'dhcp-client-send.c',
'dhcp-network.c',
'dhcp-option.c',
'dhcp-packet.c',
+208 -445
View File
@@ -7,28 +7,22 @@
#include <net/if_arp.h>
#include <stdio.h>
#include "sd-dhcp-client.h"
#include "alloc-util.h"
#include "device-util.h"
#include "dhcp-client-id-internal.h"
#include "dhcp-client-internal.h"
#include "dhcp-client-send.h"
#include "dhcp-lease-internal.h"
#include "dhcp-network.h"
#include "dhcp-option.h"
#include "dhcp-packet.h"
#include "dns-domain.h"
#include "errno-util.h"
#include "ether-addr-util.h"
#include "event-util.h"
#include "fd-util.h"
#include "hostname-util.h"
#include "iovec-util.h"
#include "memory-util.h"
#include "network-common.h"
#include "random-util.h"
#include "set.h"
#include "socket-util.h"
#include "sort-util.h"
#include "string-table.h"
#include "string-util.h"
@@ -41,71 +35,11 @@
#define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC)
#define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE)
#define MAX_REQUEST_ATTEMPTS_ON_REBOOTING 2
#define MAX_REQUEST_ATTEMPTS 5
#define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report
* transient failure. */
struct sd_dhcp_client {
unsigned n_ref;
DHCPState state;
sd_event *event;
int event_priority;
sd_event_source *timeout_resend;
int ifindex;
char *ifname;
sd_device *dev;
int fd;
uint16_t port;
uint16_t server_port;
union sockaddr_union link;
sd_event_source *receive_message;
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;
uint16_t arp_type;
sd_dhcp_client_id client_id;
char *hostname;
char *vendor_class_identifier;
char *mudurl;
char **user_class;
uint32_t mtu;
usec_t fallback_lease_lifetime;
uint32_t xid;
usec_t start_time;
usec_t t1_time;
usec_t t2_time;
usec_t expire_time;
uint64_t discover_attempt;
uint64_t request_attempt;
uint64_t max_discover_attempts;
uint64_t max_request_attempts;
OrderedHashmap *extra_options;
OrderedHashmap *vendor_options;
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;
void *state_userdata;
sd_dhcp_lease *lease;
usec_t start_delay;
int ip_service_type;
int socket_priority;
bool socket_priority_set;
bool ipv6_acquired;
bool bootp;
bool send_release;
};
static const uint8_t default_req_opts[] = {
SD_DHCP_OPTION_SUBNET_MASK,
SD_DHCP_OPTION_ROUTER,
@@ -140,16 +74,6 @@ static const uint8_t default_req_opts_anonymize[] = {
SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */
};
static int client_receive_message_raw(
sd_event_source *s,
int fd,
uint32_t revents,
void *userdata);
static int client_receive_message_udp(
sd_event_source *s,
int fd,
uint32_t revents,
void *userdata);
static void client_stop(sd_dhcp_client *client, int error);
static int client_restart(sd_dhcp_client *client);
@@ -683,6 +607,30 @@ static void client_set_state(sd_dhcp_client *client, DHCPState state) {
client->state = state;
switch (state) {
case DHCP_STATE_STOPPED:
case DHCP_STATE_BOUND:
/* In these cases, the next DHCPDISCOVER message will be sent in a new cycle.
* Hence, clear the counter for DHCPDISCOVER messages. */
client->discover_attempt = 0;
break;
case DHCP_STATE_REBOOTING:
case DHCP_STATE_REQUESTING:
case DHCP_STATE_RENEWING:
case DHCP_STATE_REBINDING:
/* In these cases, the next DHCPREQUEST message will be the first message in this new state.
* Hence, clear the counter for DHCPREQUEST messages. */
client->request_attempt = 0;
break;
default:
/* otherwise, do not reset the counters. */
;
}
// FIXME: If the state callback changes the state, we may not safely free/stop the client, and the
// state machine diagram becomes needlessly complicated. Introduce a guard to avoid that. */
if (client->state_callback)
client->state_callback(client, state, client->state_userdata);
}
@@ -702,21 +650,21 @@ static int client_notify(sd_dhcp_client *client, int event) {
return 0;
}
static void client_initialize(sd_dhcp_client *client) {
static void client_disable_event_sources(sd_dhcp_client *client) {
assert(client);
client->receive_message = sd_event_source_disable_unref(client->receive_message);
client->fd = safe_close(client->fd);
(void) event_source_disable(client->timeout_resend);
(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->discover_attempt = 0;
client->request_attempt = 0;
static void client_initialize(sd_dhcp_client *client) {
assert(client);
client_disable_event_sources(client);
client_set_state(client, DHCP_STATE_STOPPED);
client->xid = 0;
@@ -963,18 +911,6 @@ static int client_append_fqdn_option(
return r;
}
static int dhcp_client_send_raw(
sd_dhcp_client *client,
DHCPPacket *packet,
size_t len) {
dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port,
INADDR_BROADCAST, client->server_port, len, client->ip_service_type);
return dhcp_network_send_raw_socket(client->fd, &client->link,
packet, len);
}
static int client_append_common_discover_request_options(sd_dhcp_client *client, DHCPPacket *packet, size_t *optoffset, size_t optlen) {
sd_dhcp_option *j;
int r;
@@ -1051,7 +987,6 @@ static int client_send_dhcp_discover(sd_dhcp_client *client) {
int r;
assert(client);
assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset);
if (r < 0)
@@ -1089,12 +1024,11 @@ static int client_send_dhcp_discover(sd_dhcp_client *client) {
if (r < 0)
return r;
r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset);
r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset);
if (r < 0)
return r;
log_dhcp_client(client, "DISCOVER");
return 0;
}
@@ -1104,7 +1038,6 @@ static int client_send_bootp_discover(sd_dhcp_client *client) {
int r;
assert(client);
assert(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_SELECTING));
r = client_message_init(client, DHCP_DISCOVER, &discover, &optlen, &optoffset);
if (r < 0)
@@ -1127,7 +1060,7 @@ static int client_send_bootp_discover(sd_dhcp_client *client) {
optoffset = 60;
}
r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset);
r = dhcp_client_send_raw(client, /* expect_reply= */ true, discover, optoffset);
if (r < 0)
return r;
@@ -1135,6 +1068,15 @@ static int client_send_bootp_discover(sd_dhcp_client *client) {
return 0;
}
static int client_send_discover(sd_dhcp_client *client) {
assert(client);
assert(client->state == DHCP_STATE_SELECTING);
return client->bootp ?
client_send_bootp_discover(client) :
client_send_dhcp_discover(client);
}
static int client_send_request(sd_dhcp_client *client) {
_cleanup_free_ DHCPPacket *request = NULL;
size_t optoffset, optlen;
@@ -1169,10 +1111,9 @@ static int client_send_request(sd_dhcp_client *client) {
4, &client->lease->address);
if (r < 0)
return r;
break;
case DHCP_STATE_INIT_REBOOT:
case DHCP_STATE_REBOOTING:
/* ’server identifier’ MUST NOT be filled in, ’requested IP address’
option MUST be filled in with client’s notion of its previously
assigned address. ’ciaddr’ MUST be zero.
@@ -1198,16 +1139,10 @@ static int client_send_request(sd_dhcp_client *client) {
This message MUST be broadcast to the 0xffffffff IP broadcast address.
*/
request->dhcp.ciaddr = client->lease->address;
break;
case DHCP_STATE_INIT:
case DHCP_STATE_SELECTING:
case DHCP_STATE_REBOOTING:
case DHCP_STATE_BOUND:
case DHCP_STATE_STOPPED:
default:
return -EINVAL;
assert_not_reached();
}
r = client_append_common_discover_request_options(client, request, &optoffset, optlen);
@@ -1220,13 +1155,9 @@ static int client_send_request(sd_dhcp_client *client) {
return r;
if (client->state == DHCP_STATE_RENEWING)
r = dhcp_network_send_udp_socket(client->fd,
client->lease->server_address,
client->server_port,
&request->dhcp,
sizeof(DHCPMessage) + optoffset);
r = dhcp_client_send_udp(client, /* expect_reply= */ true, request, optoffset);
else
r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset);
r = dhcp_client_send_raw(client, /* expect_reply= */ true, request, optoffset);
if (r < 0)
return r;
@@ -1236,8 +1167,8 @@ static int client_send_request(sd_dhcp_client *client) {
log_dhcp_client(client, "REQUEST (requesting)");
break;
case DHCP_STATE_INIT_REBOOT:
log_dhcp_client(client, "REQUEST (init-reboot)");
case DHCP_STATE_REBOOTING:
log_dhcp_client(client, "REQUEST (rebooting)");
break;
case DHCP_STATE_RENEWING:
@@ -1249,14 +1180,12 @@ static int client_send_request(sd_dhcp_client *client) {
break;
default:
log_dhcp_client(client, "REQUEST (invalid)");
assert_not_reached();
}
return 0;
}
static int client_start(sd_dhcp_client *client);
static int client_timeout_resend(
sd_event_source *s,
uint64_t usec,
@@ -1284,39 +1213,43 @@ static int client_timeout_resend(
next_timeout = client_compute_reacquisition_timeout(time_now, client->expire_time);
break;
case DHCP_STATE_REBOOTING:
/* start over as we did not receive a timely ack or nak */
client_initialize(client);
r = client_start(client);
if (r < 0)
goto error;
log_dhcp_client(client, "REBOOTED");
return 0;
case DHCP_STATE_INIT:
case DHCP_STATE_INIT_REBOOT:
client_set_state(client, DHCP_STATE_SELECTING);
_fallthrough_;
case DHCP_STATE_SELECTING:
if (client->discover_attempt >= client->max_discover_attempts)
if (client->discover_attempt >= client->max_discover_attempts) {
r = -ETIMEDOUT;
goto error;
}
client->discover_attempt++;
next_timeout = client_compute_request_timeout(client->discover_attempt);
break;
case DHCP_STATE_INIT_REBOOT:
client_set_state(client, DHCP_STATE_REBOOTING);
_fallthrough_;
case DHCP_STATE_REBOOTING:
/* There is nothing explicitly mentioned about retry interval on reboot. Let's reuse the same
* algorithm as in the requesting state below, but slightly speed up for faster reboot. */
if (client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING)
goto restart;
client->request_attempt++;
next_timeout = client_compute_request_timeout(client->request_attempt) / 4;
break;
case DHCP_STATE_REQUESTING:
case DHCP_STATE_BOUND:
if (client->request_attempt >= client->max_request_attempts)
goto error;
if (client->request_attempt >= MAX_REQUEST_ATTEMPTS)
goto restart;
client->request_attempt++;
next_timeout = client_compute_request_timeout(client->request_attempt);
break;
case DHCP_STATE_STOPPED:
r = -EINVAL;
goto error;
default:
assert_not_reached();
}
@@ -1330,64 +1263,45 @@ static int client_timeout_resend(
goto error;
switch (client->state) {
case DHCP_STATE_INIT:
if (client->bootp)
r = client_send_bootp_discover(client);
else
r = client_send_dhcp_discover(client);
if (r >= 0) {
client_set_state(client, DHCP_STATE_SELECTING);
client->discover_attempt = 0;
} else if (client->discover_attempt >= client->max_discover_attempts)
goto error;
break;
case DHCP_STATE_SELECTING:
if (client->bootp)
r = client_send_bootp_discover(client);
else
r = client_send_dhcp_discover(client);
r = client_send_discover(client);
if (r < 0 && client->discover_attempt >= client->max_discover_attempts)
goto error;
if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS)
client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE);
break;
case DHCP_STATE_REBOOTING:
r = client_send_request(client);
if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS_ON_REBOOTING)
goto restart;
break;
case DHCP_STATE_INIT_REBOOT:
case DHCP_STATE_REQUESTING:
case DHCP_STATE_RENEWING:
case DHCP_STATE_REBINDING:
r = client_send_request(client);
if (r < 0 && client->request_attempt >= client->max_request_attempts)
goto error;
if (client->state == DHCP_STATE_INIT_REBOOT)
client_set_state(client, DHCP_STATE_REBOOTING);
if (r < 0 && client->request_attempt >= MAX_REQUEST_ATTEMPTS)
goto restart;
break;
case DHCP_STATE_REBOOTING:
case DHCP_STATE_BOUND:
break;
case DHCP_STATE_STOPPED:
default:
r = -EINVAL;
goto error;
assert_not_reached();
}
if (client->discover_attempt >= TRANSIENT_FAILURE_ATTEMPTS)
client_notify(client, SD_DHCP_CLIENT_EVENT_TRANSIENT_FAILURE);
return 0;
error:
restart:
/* Avoid REQUEST infinite loop. Per RFC 2131 section 3.1.5: if the client receives
neither a DHCPACK or a DHCPNAK message after employing the retransmission algorithm,
the client reverts to INIT state and restarts the initialization process */
if (client->request_attempt >= client->max_request_attempts) {
log_dhcp_client(client, "Max REQUEST attempts reached. Restarting...");
r = client_restart(client);
if (r >= 0)
return 0;
}
log_dhcp_client(client, "Max REQUEST attempts reached. Restarting...");
r = client_restart(client);
if (r >= 0)
return 0;
error:
client_stop(client, r);
/* Errors were dealt with when stopping the client, don't spill
@@ -1395,40 +1309,10 @@ error:
return 0;
}
static int client_initialize_io_events(
sd_dhcp_client *client,
sd_event_io_handler_t io_callback) {
int r;
assert(client);
assert(client->event);
assert(io_callback);
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
r = sd_event_add_io(client->event, &s, client->fd, EPOLLIN, io_callback, client);
if (r < 0)
return r;
r = sd_event_source_set_priority(s, client->event_priority);
if (r < 0)
return r;
r = sd_event_source_set_description(s, "dhcp4-receive-message");
if (r < 0)
return r;
sd_event_source_disable_unref(client->receive_message);
client->receive_message = TAKE_PTR(s);
return 0;
}
static int client_initialize_time_events(sd_dhcp_client *client) {
assert(client);
assert(client->event);
(void) event_source_disable(client->timeout_ipv6_only_mode);
return event_reset_time_relative(
client->event,
&client->timeout_resend,
@@ -1442,45 +1326,20 @@ static int client_initialize_time_events(sd_dhcp_client *client) {
/* force_reset= */ true);
}
static int client_initialize_events(sd_dhcp_client *client, sd_event_io_handler_t io_callback) {
int r;
assert(client);
assert(io_callback);
r = client_initialize_io_events(client, io_callback);
if (r < 0)
return r;
return client_initialize_time_events(client);
}
static int client_start_delayed(sd_dhcp_client *client) {
int r;
assert(client);
DHCP_CLIENT_DONT_DESTROY(client);
assert_return(client, -EINVAL);
assert_return(client->event, -EINVAL);
assert_return(client->ifindex > 0, -EINVAL);
assert_return(client->fd < 0, -EBUSY);
assert_return(client->xid == 0, -EINVAL);
assert_return(IN_SET(client->state, DHCP_STATE_STOPPED, DHCP_STATE_INIT_REBOOT), -EBUSY);
client_disable_event_sources(client);
client->lease = sd_dhcp_lease_unref(client->lease);
client->xid = random_u32();
r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
&client->hw_addr, &client->bcast_addr,
client->arp_type, client->port,
client->socket_priority_set, client->socket_priority);
if (r < 0)
return r;
client->fd = r;
client->start_time = now(CLOCK_BOOTTIME);
if (client->state == DHCP_STATE_STOPPED)
client->state = DHCP_STATE_INIT;
if (client->state != DHCP_STATE_INIT_REBOOT)
client_set_state(client, DHCP_STATE_INIT);
return client_initialize_events(client, client_receive_message_raw);
return client_initialize_time_events(client);
}
static int client_start(sd_dhcp_client *client) {
@@ -1490,6 +1349,34 @@ static int client_start(sd_dhcp_client *client) {
return client_start_delayed(client);
}
static int client_restart(sd_dhcp_client *client) {
assert(client);
DHCP_CLIENT_DONT_DESTROY(client);
/* This is called when we receive a DHCPNAK or could not receive any replies. */
/* First, if we have a bound lease, then notify it is expired. */
if (IN_SET(client->state, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING)) {
client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
if (client->state == DHCP_STATE_STOPPED)
return 0; /* The notify callback stopped the client. */
}
/* On reboot, DHCPNAK or no reply suggests that the network is changed or the address is already
* used by another host. Let's restart the client immediately without any delay to speed up the
* reboot process. */
if (client->state == DHCP_STATE_REBOOTING)
return client_start(client);
/* Otherwise, we should restart the client with a short delay. */
client->start_delay = CLAMP(client->start_delay * 2,
RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC);
log_dhcp_client(client, "REBOOT in %s", FORMAT_TIMESPAN(client->start_delay, USEC_PER_SEC));
return client_start_delayed(client);
}
static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) {
sd_dhcp_client *client = userdata;
DHCP_CLIENT_DONT_DESTROY(client);
@@ -1499,16 +1386,12 @@ static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userda
client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED);
/* lease was lost, start over if not freed or stopped in callback */
if (client->state != DHCP_STATE_STOPPED) {
client_initialize(client);
if (client->state == DHCP_STATE_STOPPED)
return 0; /* The notify callback stopped the client. */
r = client_start(client);
if (r < 0) {
client_stop(client, r);
return 0;
}
}
r = client_start(client);
if (r < 0)
client_stop(client, r);
return 0;
}
@@ -1516,49 +1399,24 @@ static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userda
static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) {
sd_dhcp_client *client = ASSERT_PTR(userdata);
DHCP_CLIENT_DONT_DESTROY(client);
int r;
/* Explicitly close the unicast socket opened during renewing. On success path, the socket will be
* closed anyway on sending broadcast DHCPREQUEST, but let's explicitly close it here for failure
* path to ignore all unicast replies from now on. */
client->receive_message = sd_event_source_disable_unref(client->receive_message);
client->fd = safe_close(client->fd);
client_set_state(client, DHCP_STATE_REBINDING);
client->discover_attempt = 0;
client->request_attempt = 0;
r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid,
&client->hw_addr, &client->bcast_addr,
client->arp_type, client->port,
client->socket_priority_set, client->socket_priority);
if (r < 0) {
client_stop(client, r);
return 0;
}
client->fd = r;
r = client_initialize_events(client, client_receive_message_raw);
if (r < 0)
client_stop(client, r);
return 0;
return client_timeout_resend(s, usec, userdata);
}
static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) {
sd_dhcp_client *client = userdata;
sd_dhcp_client *client = ASSERT_PTR(userdata);
DHCP_CLIENT_DONT_DESTROY(client);
int r;
if (client->lease)
client_set_state(client, DHCP_STATE_RENEWING);
else if (client->state != DHCP_STATE_INIT)
client_set_state(client, DHCP_STATE_INIT_REBOOT);
client->discover_attempt = 0;
client->request_attempt = 0;
client_set_state(client, DHCP_STATE_RENEWING);
r = client_initialize_time_events(client);
if (r < 0)
client_stop(client, r);
return 0;
return client_timeout_resend(s, usec, userdata);
}
static int dhcp_option_parse_and_verify(
@@ -1750,39 +1608,15 @@ static int client_handle_offer_or_rapid_ack(sd_dhcp_client *client, DHCPMessage
return 0;
}
static int client_enter_requesting_now(sd_dhcp_client *client) {
assert(client);
client_set_state(client, DHCP_STATE_REQUESTING);
client->discover_attempt = 0;
client->request_attempt = 0;
return event_reset_time(client->event, &client->timeout_resend,
CLOCK_BOOTTIME, 0, 0,
client_timeout_resend, client,
client->event_priority, "dhcp4-resend-timer",
/* force_reset= */ true);
}
static int client_enter_requesting_delayed(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_requesting_now(client);
if (r < 0)
client_stop(client, r);
return 0;
}
static int client_enter_requesting(sd_dhcp_client *client) {
assert(client);
assert(client->lease);
(void) event_source_disable(client->timeout_resend);
client_disable_event_sources(client);
if (client->lease->ipv6_only_preferred_usec > 0) {
client_set_state(client, DHCP_STATE_REQUESTING);
if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) {
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.");
@@ -1792,16 +1626,19 @@ static int client_enter_requesting(sd_dhcp_client *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));
return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode,
CLOCK_BOOTTIME,
client->lease->ipv6_only_preferred_usec, 0,
client_enter_requesting_delayed, client,
client->event_priority, "dhcp4-ipv6-only-mode-timer",
/* force_reset= */ true);
}
return client_enter_requesting_now(client);
return event_reset_time_relative(
client->event,
&client->timeout_resend,
CLOCK_BOOTTIME,
client->lease->ipv6_only_preferred_usec,
/* accuracy= */ 0,
client_timeout_resend,
client,
client->event_priority,
"dhcp4-resend-timer",
/* force_reset= */ true);
}
static bool lease_equal(const sd_dhcp_lease *a, const sd_dhcp_lease *b) {
@@ -1954,108 +1791,28 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
return 0;
}
static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) {
static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
int r;
assert(client);
assert(client->lease);
if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING))
notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
client_disable_event_sources(client);
client->start_delay = 0;
client_set_state(client, DHCP_STATE_BOUND);
client->discover_attempt = 0;
client->request_attempt = 0;
client->last_addr = client->lease->address;
r = client_set_lease_timeouts(client);
if (r < 0)
log_dhcp_client_errno(client, r, "could not set lease timeouts: %m");
if (client->bootp) {
client->receive_message = sd_event_source_disable_unref(client->receive_message);
client->fd = safe_close(client->fd);
} else {
r = dhcp_network_bind_udp_socket(client->ifindex, client->lease->address, client->port, client->ip_service_type);
if (r < 0)
return log_dhcp_client_errno(client, r, "could not bind UDP socket: %m");
client->receive_message = sd_event_source_disable_unref(client->receive_message);
close_and_replace(client->fd, r);
r = client_initialize_io_events(client, client_receive_message_udp);
if (r < 0)
return r;
}
return log_dhcp_client_errno(client, r, "Failed to set lease timeouts: %m");
client_notify(client, notify_event);
return 0;
}
static int client_enter_bound_delayed(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);
/* RFC 8925 section 3.2
* If the client is in the INIT-REBOOT state, it SHOULD stop the DHCPv4 configuration process or
* disable the IPv4 stack completely for V6ONLY_WAIT seconds or until the network attachment event,
* whichever happens first.
*
* In the below, the condition uses REBOOTING, instead of INIT-REBOOT, as the client state has
* already transitioned from INIT-REBOOT to REBOOTING after sending a DHCPREQUEST message. */
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_enter_bound_delayed, client,
client->event_priority, "dhcp4-ipv6-only-mode",
/* force_reset= */ true);
}
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);
client_initialize(client);
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;
}
@@ -2165,7 +1922,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, s
return 0;
}
static int client_receive_message_udp(
int client_receive_message_udp(
sd_event_source *s,
int fd,
uint32_t revents,
@@ -2218,7 +1975,7 @@ static int client_receive_message_udp(
return 0;
}
static int client_receive_message_raw(
int client_receive_message_raw(
sd_event_source *s,
int fd,
uint32_t revents,
@@ -2286,11 +2043,9 @@ int sd_dhcp_client_send_renew(sd_dhcp_client *client) {
if (!sd_dhcp_client_is_running(client) || client->state != DHCP_STATE_BOUND || client->bootp)
return 0; /* do nothing */
client->start_delay = 0;
client->discover_attempt = 1;
client->request_attempt = 1;
client_set_state(client, DHCP_STATE_RENEWING);
client->start_delay = 0;
return client_initialize_time_events(client);
}
@@ -2305,11 +2060,8 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
int r;
assert_return(client, -EINVAL);
/* Note, do not reset the flag in client_initialize(), as it is also called on expire. */
client->ipv6_acquired = false;
client_initialize(client);
assert_return(client->event, -EINVAL);
assert_return(client->ifindex > 0, -EINVAL);
/* If no client identifier exists, construct an RFC 4361-compliant one */
if (!sd_dhcp_client_id_is_set(&client->client_id)) {
@@ -2397,15 +2149,10 @@ static int client_send_release_or_decline(sd_dhcp_client *client, uint8_t type)
switch (type) {
case DHCP_RELEASE:
r = dhcp_network_send_udp_socket(
client->fd,
client->lease->server_address,
client->server_port,
&packet->dhcp,
sizeof(DHCPMessage) + optoffset);
r = dhcp_client_send_udp(client, /* expect_reply= */ false, packet, optoffset);
break;
case DHCP_DECLINE:
r = dhcp_client_send_raw(client, packet, sizeof(DHCPPacket) + optoffset);
r = dhcp_client_send_raw(client, /* expect_reply= */ false, packet, optoffset);
break;
default:
assert_not_reached();
@@ -2451,32 +2198,51 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) {
return 0;
}
int sd_dhcp_client_is_waiting_for_ipv6_connectivity(sd_dhcp_client *client) {
/* Note that we intentionally do not implement the following behavior:
*
* RFC 8925, section 3.2:
* If the client is in the INIT-REBOOT state, it SHOULD stop the DHCPv4 configuration process or
* disable the IPv4 stack completely for V6ONLY_WAIT seconds or until the next network attachment
* event, whichever occurs first.
*
* Delaying the application of an acquired IPv4 address after DHCPACK introduces several issues:
*
* - If T1 is reached before the address is assigned to the interface, the client cannot send a
* unicast DHCPREQUEST during RENEWING.
*
* - If the client is stopped before the address is configured, it cannot send a DHCPRELEASE message,
* which also requires a valid source address.
*
* While these issues could be worked around, doing so would significantly complicate the
* implementation and violate assumptions in the DHCP state machine as defined in RFC 2131.
*
* Instead, we only honor the IPv6-Only Preferred delay (Option 108) in the REQUESTING state, i.e.
* before any DHCPREQUEST has been sent. */
return
client &&
client->state == DHCP_STATE_REQUESTING &&
client->request_attempt == 0 &&
client->lease &&
client->lease->ipv6_only_preferred_usec > 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;
if (have && sd_dhcp_client_is_waiting_for_ipv6_connectivity(client)) {
log_dhcp_client(client,
"Acquired IPv6 connectivity before sending REQUEST, stopping DHCPv4 client.");
return sd_dhcp_client_stop(client);
}
return 0;
}
int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client) {
assert_return(client, -EINVAL);
assert_return(sd_dhcp_client_is_running(client), -ESTALE);
assert_return(client->fd >= 0, -EINVAL);
if (sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) <= 0)
return 0;
client_initialize(client);
return client_start(client);
}
int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) {
int r;
@@ -2530,7 +2296,6 @@ static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) {
sd_event_source_unref(client->timeout_t1);
sd_event_source_unref(client->timeout_t2);
sd_event_source_unref(client->timeout_expire);
sd_event_source_unref(client->timeout_ipv6_only_mode);
sd_dhcp_client_detach_event(client);
@@ -2564,13 +2329,11 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) {
.n_ref = 1,
.state = DHCP_STATE_STOPPED,
.ifindex = -1,
.fd = -EBADF,
.mtu = DHCP_MIN_PACKET_SIZE,
.port = DHCP_PORT_CLIENT,
.server_port = DHCP_PORT_SERVER,
.anonymize = !!anonymize,
.max_discover_attempts = UINT64_MAX,
.max_request_attempts = 5,
.ip_service_type = -1,
};
/* NOTE: this could be moved to a function. */
+18 -10
View File
@@ -1733,6 +1733,8 @@ int dhcp4_update_mac(Link *link) {
}
int dhcp4_update_ipv6_connectivity(Link *link) {
int r;
assert(link);
if (!link->network)
@@ -1744,16 +1746,20 @@ int dhcp4_update_ipv6_connectivity(Link *link) {
if (!link->dhcp_client)
return 0;
/* If the client is running, set the current connectivity. */
if (sd_dhcp_client_is_running(link->dhcp_client))
return sd_dhcp_client_set_ipv6_connectivity(link->dhcp_client, link_has_ipv6_connectivity(link));
bool have = link_has_ipv6_connectivity(link);
r = sd_dhcp_client_set_ipv6_connectivity(link->dhcp_client, have);
if (r < 0)
return r;
/* If the client has been already stopped or not started yet, let's check the current connectivity
* and start the client if necessary. */
if (link_has_ipv6_connectivity(link))
return 0;
/* If we do not have IPv6 connectivity, and the client has been already stopped or not started yet,
* let's start the client if possible. */
if (!have && !sd_dhcp_client_is_running(link->dhcp_client)) {
r = dhcp4_start_full(link, /* set_ipv6_connectivity= */ false);
if (r < 0)
return r;
}
return dhcp4_start_full(link, /* set_ipv6_connectivity= */ false);
return 0;
}
int dhcp4_start_full(Link *link, bool set_ipv6_connectivity) {
@@ -1805,8 +1811,10 @@ int dhcp4_renew(Link *link) {
return dhcp4_start(link);
/* The client may be waiting for IPv6 connectivity. Let's restart the client in that case. */
if (dhcp_client_get_state(link->dhcp_client) != DHCP_STATE_BOUND)
return sd_dhcp_client_interrupt_ipv6_only_mode(link->dhcp_client);
if (sd_dhcp_client_is_waiting_for_ipv6_connectivity(link->dhcp_client)) {
sd_dhcp_client_stop(link->dhcp_client);
return dhcp4_start(link);
}
/* Otherwise, send a RENEW command. */
return sd_dhcp_client_send_renew(link->dhcp_client);
+1 -1
View File
@@ -159,7 +159,7 @@ int sd_dhcp_client_start(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);
int sd_dhcp_client_interrupt_ipv6_only_mode(sd_dhcp_client *client);
int sd_dhcp_client_is_waiting_for_ipv6_connectivity(sd_dhcp_client *client);
_SD_DECLARE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_client);