sd-dhcp-client: several refactoring for state machine of the DHCP client (#41796)
This commit is contained in:
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
libsystemd_network_sources = files(
|
||||
'arp-util.c',
|
||||
'dhcp-client-send.c',
|
||||
'dhcp-network.c',
|
||||
'dhcp-option.c',
|
||||
'dhcp-packet.c',
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user