mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 08:25:20 +09:00
Merge pull request #29472 from yuwata/network-dhcp-ipv6-only-mode
network/dhcp: add IPv6 only mode support
This commit is contained in:
@@ -2573,6 +2573,19 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>IPv6OnlyMode=</varname></term>
|
||||
<listitem>
|
||||
<para>When true, the DHCPv4 configuration will be delayed by the timespan provided by the DHCP
|
||||
server and skip to configure dynamic IPv4 network connectivity if IPv6 connectivity is provided
|
||||
within the timespan. See <ulink url="https://tools.ietf.org/html/rfc8925">RFC 8925</ulink>.
|
||||
Defaults to true when <varname>IPv6AcceptRA=</varname> is enabled or DHCPv6 client is enabled
|
||||
(i.e., <varname>DHCP=yes</varname>), and false otherwise.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>FallbackLeaseLifetimeSec=</varname></term>
|
||||
<listitem>
|
||||
@@ -3604,6 +3617,20 @@ ServerAddress=192.168.0.1/24</programlisting>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>IPv6OnlyPreferredSec=</varname></term>
|
||||
|
||||
<listitem>
|
||||
<para>Takes a timespan. Controls the
|
||||
<ulink url="https://tools.ietf.org/html/rfc8925">RFC 8925</ulink> IPv6-Only Preferred option.
|
||||
Specifies the DHCPv4 option to indicate that a host supports an IPv6-only mode and is willing to
|
||||
forgo obtaining an IPv4 address if the network provides IPv6 connectivity. Defaults to unset, and
|
||||
not send the option. The minimum allowed value is 300 seconds.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v255"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SendOption=</varname></term>
|
||||
<listitem>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
|
||||
#include "macro.h"
|
||||
#include "sparse-endian.h"
|
||||
#include "time-util.h"
|
||||
|
||||
/* RFC 8925 - IPv6-Only Preferred Option for DHCPv4 3.4.
|
||||
* MIN_V6ONLY_WAIT: The lower boundary for V6ONLY_WAIT. Value: 300 seconds */
|
||||
#define MIN_V6ONLY_WAIT_USEC (300U * USEC_PER_SEC)
|
||||
|
||||
struct DHCPMessage {
|
||||
uint8_t op;
|
||||
|
||||
@@ -83,6 +83,7 @@ struct sd_dhcp_server {
|
||||
|
||||
usec_t max_lease_time;
|
||||
usec_t default_lease_time;
|
||||
usec_t ipv6_only_preferred_usec;
|
||||
|
||||
sd_dhcp_server_callback_t callback;
|
||||
void *callback_userdata;
|
||||
@@ -105,6 +106,8 @@ typedef struct DHCPRequest {
|
||||
usec_t lifetime;
|
||||
const uint8_t *agent_info_option;
|
||||
char *hostname;
|
||||
const uint8_t *parameter_request_list;
|
||||
size_t parameter_request_list_len;
|
||||
} DHCPRequest;
|
||||
|
||||
extern const struct hash_ops dhcp_lease_hash_ops;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -330,6 +330,15 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool dhcp_request_contains(DHCPRequest *req, uint8_t option) {
|
||||
assert(req);
|
||||
|
||||
if (!req->parameter_request_list)
|
||||
return false;
|
||||
|
||||
return memchr(req->parameter_request_list, option, req->parameter_request_list_len);
|
||||
}
|
||||
|
||||
static int dhcp_server_send_unicast_raw(
|
||||
sd_dhcp_server *server,
|
||||
uint8_t hlen,
|
||||
@@ -649,6 +658,21 @@ static int server_send_offer_or_ack(
|
||||
return r;
|
||||
}
|
||||
|
||||
/* RFC 8925 section 3.3. DHCPv4 Server Behavior
|
||||
* The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if
|
||||
* the option was not present in the Parameter Request List sent by the client. */
|
||||
if (dhcp_request_contains(req, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED) &&
|
||||
server->ipv6_only_preferred_usec > 0) {
|
||||
be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec);
|
||||
|
||||
r = dhcp_option_append(
|
||||
&packet->dhcp, req->max_optlen, &offset, 0,
|
||||
SD_DHCP_OPTION_IPV6_ONLY_PREFERRED,
|
||||
sizeof(sec), &sec);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
ORDERED_SET_FOREACH(j, server->extra_options) {
|
||||
r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
|
||||
j->option, j->length, j->data);
|
||||
@@ -778,6 +802,10 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us
|
||||
return 0;
|
||||
}
|
||||
|
||||
break;
|
||||
case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST:
|
||||
req->parameter_request_list = option;
|
||||
req->parameter_request_list_len = len;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1501,6 +1529,19 @@ int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint64_t t) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_dhcp_server_set_ipv6_only_preferred_usec(sd_dhcp_server *server, uint64_t t) {
|
||||
assert_return(server, -EINVAL);
|
||||
|
||||
/* When 0 is set, disables the IPv6 only mode. */
|
||||
|
||||
/* Refuse too short timespan unless test mode is enabled. */
|
||||
if (t > 0 && t < MIN_V6ONLY_WAIT_USEC && !network_test_mode_enabled())
|
||||
return -EINVAL;
|
||||
|
||||
server->ipv6_only_preferred_usec = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sd_dhcp_server_set_servers(
|
||||
sd_dhcp_server *server,
|
||||
sd_dhcp_lease_server_type_t what,
|
||||
|
||||
@@ -1669,7 +1669,7 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
|
||||
uint16_t type;
|
||||
Address *address = NULL;
|
||||
Request *req = NULL;
|
||||
bool is_new = false;
|
||||
bool is_new = false, update_dhcp4;
|
||||
int ifindex, r;
|
||||
|
||||
assert(rtnl);
|
||||
@@ -1778,6 +1778,8 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
update_dhcp4 = tmp->family == AF_INET6;
|
||||
|
||||
/* Then, find the managed Address and Request objects corresponding to the received address. */
|
||||
(void) address_get(link, tmp, &address);
|
||||
(void) address_get_request(link, tmp, &req);
|
||||
@@ -1793,7 +1795,7 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
|
||||
if (req)
|
||||
address_enter_removed(req->userdata);
|
||||
|
||||
return 0;
|
||||
goto finalize;
|
||||
}
|
||||
|
||||
if (!address) {
|
||||
@@ -1879,6 +1881,15 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message,
|
||||
if (r < 0)
|
||||
link_enter_failed(link);
|
||||
|
||||
finalize:
|
||||
if (update_dhcp4) {
|
||||
r = dhcp4_update_ipv6_connectivity(link);
|
||||
if (r < 0) {
|
||||
log_link_warning_errno(link, r, "Failed to notify IPv6 connectivity to DHCPv4 client: %m");
|
||||
link_enter_failed(link);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
#include "sd-dhcp-server.h"
|
||||
|
||||
#include "dhcp-protocol.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "network-common.h"
|
||||
#include "networkd-address.h"
|
||||
#include "networkd-dhcp-server-bus.h"
|
||||
#include "networkd-dhcp-server-static-lease.h"
|
||||
@@ -402,6 +404,10 @@ static int dhcp4_server_configure(Link *link) {
|
||||
return log_link_error_errno(link, r, "Failed to set default lease time for DHCPv4 server instance: %m");
|
||||
}
|
||||
|
||||
r = sd_dhcp_server_set_ipv6_only_preferred_usec(link->dhcp_server, link->network->dhcp_server_ipv6_only_preferred_usec);
|
||||
if (r < 0)
|
||||
return log_link_error_errno(link, r, "Failed to set IPv6 only preferred time for DHCPv4 server instance: %m");
|
||||
|
||||
r = sd_dhcp_server_set_boot_server_address(link->dhcp_server, &link->network->dhcp_server_boot_server_address);
|
||||
if (r < 0)
|
||||
return log_link_warning_errno(link, r, "Failed to set boot server address for DHCPv4 server instance: %m");
|
||||
@@ -726,3 +732,45 @@ int config_parse_dhcp_server_address(
|
||||
network->dhcp_server_address_prefixlen = prefixlen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_parse_dhcp_server_ipv6_only_preferred(
|
||||
const char *unit,
|
||||
const char *filename,
|
||||
unsigned line,
|
||||
const char *section,
|
||||
unsigned section_line,
|
||||
const char *lvalue,
|
||||
int ltype,
|
||||
const char *rvalue,
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
usec_t t, *usec = ASSERT_PTR(data);
|
||||
int r;
|
||||
|
||||
assert(filename);
|
||||
assert(section);
|
||||
assert(lvalue);
|
||||
assert(rvalue);
|
||||
|
||||
if (isempty(rvalue)) {
|
||||
*usec = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = parse_sec(rvalue, &t);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r,
|
||||
"Failed to parse [%s] %s=, ignoring assignment: %s", section, lvalue, rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (t < MIN_V6ONLY_WAIT_USEC && !network_test_mode_enabled()) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, 0,
|
||||
"Invalid [%s] %s=, ignoring assignment: %s", section, lvalue, rvalue);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*usec = t;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -14,3 +14,4 @@ int link_request_dhcp_server(Link *link);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_agent_suboption);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_address);
|
||||
CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_ipv6_only_preferred);
|
||||
|
||||
@@ -1452,6 +1452,16 @@ static bool link_needs_dhcp_broadcast(Link *link) {
|
||||
return r == true;
|
||||
}
|
||||
|
||||
static bool link_dhcp4_ipv6_only_mode(Link *link) {
|
||||
assert(link);
|
||||
assert(link->network);
|
||||
|
||||
if (link->network->dhcp_ipv6_only_mode >= 0)
|
||||
return link->network->dhcp_ipv6_only_mode;
|
||||
|
||||
return link_dhcp6_enabled(link) || link_ipv6_accept_ra_enabled(link);
|
||||
}
|
||||
|
||||
static int dhcp4_configure(Link *link) {
|
||||
sd_dhcp_option *send_option;
|
||||
void *request_options;
|
||||
@@ -1560,6 +1570,12 @@ static int dhcp4_configure(Link *link) {
|
||||
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for 6rd: %m");
|
||||
}
|
||||
|
||||
if (link_dhcp4_ipv6_only_mode(link)) {
|
||||
r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED);
|
||||
if (r < 0)
|
||||
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for IPv6-only preferred option: %m");
|
||||
}
|
||||
|
||||
SET_FOREACH(request_options, link->network->dhcp_request_options) {
|
||||
uint32_t option = PTR_TO_UINT32(request_options);
|
||||
|
||||
@@ -1668,7 +1684,7 @@ int dhcp4_update_mac(Link *link) {
|
||||
return r;
|
||||
|
||||
if (restart) {
|
||||
r = sd_dhcp_client_start(link->dhcp_client);
|
||||
r = dhcp4_start(link);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -1676,10 +1692,35 @@ int dhcp4_update_mac(Link *link) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dhcp4_start(Link *link) {
|
||||
int dhcp4_update_ipv6_connectivity(Link *link) {
|
||||
assert(link);
|
||||
|
||||
if (!link->network)
|
||||
return 0;
|
||||
|
||||
if (!link->network->dhcp_ipv6_only_mode)
|
||||
return 0;
|
||||
|
||||
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));
|
||||
|
||||
/* 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;
|
||||
|
||||
return dhcp4_start_full(link, /* set_ipv6_connectivity = */ false);
|
||||
}
|
||||
|
||||
int dhcp4_start_full(Link *link, bool set_ipv6_connectivity) {
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
assert(link->network);
|
||||
|
||||
if (!link->dhcp_client)
|
||||
return 0;
|
||||
@@ -1694,6 +1735,12 @@ int dhcp4_start(Link *link) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (set_ipv6_connectivity) {
|
||||
r = dhcp4_update_ipv6_connectivity(link);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@ typedef enum DHCPClientIdentifier {
|
||||
|
||||
void network_adjust_dhcp4(Network *network);
|
||||
int dhcp4_update_mac(Link *link);
|
||||
int dhcp4_start(Link *link);
|
||||
int dhcp4_update_ipv6_connectivity(Link *link);
|
||||
int dhcp4_start_full(Link *link, bool set_ipv6_connectivity);
|
||||
static inline int dhcp4_start(Link *link) {
|
||||
return dhcp4_start_full(link, true);
|
||||
}
|
||||
int dhcp4_lease_lost(Link *link);
|
||||
int dhcp4_check_ready(Link *link);
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#include "networkd-nexthop.h"
|
||||
#include "networkd-queue.h"
|
||||
#include "networkd-radv.h"
|
||||
#include "networkd-route-util.h"
|
||||
#include "networkd-route.h"
|
||||
#include "networkd-routing-policy-rule.h"
|
||||
#include "networkd-setlink.h"
|
||||
@@ -94,6 +95,32 @@ bool link_ipv6_enabled(Link *link) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool link_has_ipv6_connectivity(Link *link) {
|
||||
LinkAddressState ipv6_address_state;
|
||||
|
||||
assert(link);
|
||||
|
||||
link_get_address_states(link, NULL, &ipv6_address_state, NULL);
|
||||
|
||||
switch (ipv6_address_state) {
|
||||
case LINK_ADDRESS_STATE_ROUTABLE:
|
||||
/* If the interface has a routable IPv6 address, then we assume yes. */
|
||||
return true;
|
||||
|
||||
case LINK_ADDRESS_STATE_DEGRADED:
|
||||
/* If the interface has only degraded IPv6 address (mostly, link-local address), then let's check
|
||||
* there is an IPv6 default gateway. */
|
||||
return link_has_default_gateway(link, AF_INET6);
|
||||
|
||||
case LINK_ADDRESS_STATE_OFF:
|
||||
/* No IPv6 address. */
|
||||
return false;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
static bool link_is_ready_to_configure_one(Link *link, bool allow_unmanaged) {
|
||||
assert(link);
|
||||
|
||||
|
||||
@@ -236,6 +236,7 @@ static inline bool link_has_carrier(Link *link) {
|
||||
|
||||
bool link_ipv6_enabled(Link *link);
|
||||
int link_ipv6ll_gained(Link *link);
|
||||
bool link_has_ipv6_connectivity(Link *link);
|
||||
|
||||
int link_stop_engines(Link *link, bool may_keep_dhcp);
|
||||
|
||||
|
||||
@@ -258,6 +258,7 @@ DHCPv4.InitialCongestionWindow, config_parse_tcp_window,
|
||||
DHCPv4.InitialAdvertisedReceiveWindow, config_parse_tcp_window, 0, offsetof(Network, dhcp_advertised_receive_window)
|
||||
DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0
|
||||
DHCPv4.Use6RD, config_parse_bool, 0, offsetof(Network, dhcp_use_6rd)
|
||||
DHCPv4.IPv6OnlyMode, config_parse_tristate, 0, offsetof(Network, dhcp_ipv6_only_mode)
|
||||
DHCPv4.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_netlabel)
|
||||
DHCPv4.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp_nft_set_context)
|
||||
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
|
||||
@@ -313,6 +314,7 @@ DHCPServer.RelayAgentCircuitId, config_parse_dhcp_server_relay_agen
|
||||
DHCPServer.RelayAgentRemoteId, config_parse_dhcp_server_relay_agent_suboption, 0, offsetof(Network, dhcp_server_relay_agent_remote_id)
|
||||
DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec)
|
||||
DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec)
|
||||
DHCPServer.IPv6OnlyPreferredSec, config_parse_dhcp_server_ipv6_only_preferred, 0, offsetof(Network, dhcp_server_ipv6_only_preferred_usec)
|
||||
DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_DNS].emit)
|
||||
DHCPServer.DNS, config_parse_dhcp_server_emit, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_DNS])
|
||||
DHCPServer.EmitNTP, config_parse_bool, 0, offsetof(Network, dhcp_server_emit[SD_DHCP_LEASE_NTP].emit)
|
||||
|
||||
@@ -400,6 +400,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
|
||||
.dhcp_route_table = RT_TABLE_MAIN,
|
||||
.dhcp_ip_service_type = -1,
|
||||
.dhcp_broadcast = -1,
|
||||
.dhcp_ipv6_only_mode = -1,
|
||||
|
||||
.dhcp6_use_address = true,
|
||||
.dhcp6_use_pd_prefix = true,
|
||||
|
||||
@@ -139,6 +139,7 @@ struct Network {
|
||||
bool dhcp_anonymize;
|
||||
bool dhcp_send_hostname;
|
||||
int dhcp_broadcast;
|
||||
int dhcp_ipv6_only_mode;
|
||||
bool dhcp_use_dns;
|
||||
bool dhcp_use_dns_set;
|
||||
bool dhcp_routes_to_dns;
|
||||
@@ -221,6 +222,7 @@ struct Network {
|
||||
struct in_addr dhcp_server_boot_server_address;
|
||||
char *dhcp_server_boot_server_name;
|
||||
char *dhcp_server_boot_filename;
|
||||
usec_t dhcp_server_ipv6_only_preferred_usec;
|
||||
|
||||
/* link-local addressing support */
|
||||
AddressFamily link_local;
|
||||
|
||||
@@ -47,7 +47,8 @@ static bool route_lifetime_is_valid(const Route *route) {
|
||||
route->lifetime_usec > now(CLOCK_BOOTTIME);
|
||||
}
|
||||
|
||||
static Route *link_find_default_gateway(Link *link, int family, Route *gw) {
|
||||
bool link_find_default_gateway(Link *link, int family, Route **gw) {
|
||||
bool found = false;
|
||||
Route *route;
|
||||
|
||||
assert(link);
|
||||
@@ -69,16 +70,24 @@ static Route *link_find_default_gateway(Link *link, int family, Route *gw) {
|
||||
continue;
|
||||
if (!in_addr_is_set(route->gw_family, &route->gw))
|
||||
continue;
|
||||
if (gw) {
|
||||
if (route->gw_weight > gw->gw_weight)
|
||||
|
||||
/* Found a default gateway. */
|
||||
if (!gw)
|
||||
return true;
|
||||
|
||||
/* If we have already found another gw, then let's compare their weight and priority. */
|
||||
if (*gw) {
|
||||
if (route->gw_weight > (*gw)->gw_weight)
|
||||
continue;
|
||||
if (route->priority >= gw->priority)
|
||||
if (route->priority >= (*gw)->priority)
|
||||
continue;
|
||||
}
|
||||
gw = route;
|
||||
|
||||
*gw = route;
|
||||
found = true;
|
||||
}
|
||||
|
||||
return gw;
|
||||
return found;
|
||||
}
|
||||
|
||||
int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret) {
|
||||
@@ -98,7 +107,7 @@ int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret) {
|
||||
if (link->state != LINK_STATE_CONFIGURED)
|
||||
continue;
|
||||
|
||||
gw = link_find_default_gateway(link, family, gw);
|
||||
link_find_default_gateway(link, family, &gw);
|
||||
}
|
||||
|
||||
if (!gw)
|
||||
|
||||
@@ -9,9 +9,15 @@
|
||||
typedef struct Link Link;
|
||||
typedef struct Manager Manager;
|
||||
typedef struct Address Address;
|
||||
typedef struct Route Route;
|
||||
|
||||
unsigned routes_max(void);
|
||||
|
||||
bool link_find_default_gateway(Link *link, int family, Route **gw);
|
||||
static inline bool link_has_default_gateway(Link *link, int family) {
|
||||
return link_find_default_gateway(link, family, NULL);
|
||||
}
|
||||
|
||||
int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret);
|
||||
|
||||
bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_union *gw);
|
||||
|
||||
@@ -1620,6 +1620,7 @@ static int process_route_one(
|
||||
|
||||
_cleanup_(route_freep) Route *tmp = in;
|
||||
Route *route = NULL;
|
||||
bool update_dhcp4;
|
||||
int r;
|
||||
|
||||
assert(manager);
|
||||
@@ -1628,6 +1629,8 @@ static int process_route_one(
|
||||
|
||||
/* link may be NULL. This consumes 'in'. */
|
||||
|
||||
update_dhcp4 = link && tmp->family == AF_INET6 && tmp->dst_prefixlen == 0;
|
||||
|
||||
(void) route_get(manager, link, tmp, &route);
|
||||
|
||||
switch (type) {
|
||||
@@ -1680,6 +1683,14 @@ static int process_route_one(
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (update_dhcp4) {
|
||||
r = dhcp4_update_ipv6_connectivity(link);
|
||||
if (r < 0) {
|
||||
log_link_warning_errno(link, r, "Failed to notify IPv6 connectivity to DHCPv4 client: %m");
|
||||
link_enter_failed(link);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -84,6 +84,7 @@ int sd_dhcp_server_set_static_lease(sd_dhcp_server *server, const struct in_addr
|
||||
|
||||
int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint64_t t);
|
||||
int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint64_t t);
|
||||
int sd_dhcp_server_set_ipv6_only_preferred_usec(sd_dhcp_server *server, uint64_t t);
|
||||
|
||||
int sd_dhcp_server_forcerenew(sd_dhcp_server *server);
|
||||
|
||||
|
||||
10
test/test-network/conf/25-dhcp-client-ipv6-only-mode.network
Normal file
10
test/test-network/conf/25-dhcp-client-ipv6-only-mode.network
Normal file
@@ -0,0 +1,10 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Match]
|
||||
Name=veth99
|
||||
|
||||
[Network]
|
||||
DHCP=ipv4
|
||||
IPv6AcceptRA=no
|
||||
|
||||
[DHCPv4]
|
||||
IPv6OnlyMode=yes
|
||||
@@ -3,7 +3,8 @@
|
||||
Name=veth99
|
||||
|
||||
[Network]
|
||||
DHCP=ipv6
|
||||
# DHCPv4 is also enabled here, but will be stopped when an IPv6 address is acquired.
|
||||
DHCP=yes
|
||||
IPv6Token=::1a:2b:3c:4d
|
||||
|
||||
[Route]
|
||||
|
||||
16
test/test-network/conf/25-dhcp-server-ipv6-only-mode.network
Normal file
16
test/test-network/conf/25-dhcp-server-ipv6-only-mode.network
Normal file
@@ -0,0 +1,16 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
[Match]
|
||||
Name=veth-peer
|
||||
|
||||
[Network]
|
||||
IPv6AcceptRA=false
|
||||
DHCPServer=yes
|
||||
|
||||
[DHCPServer]
|
||||
ServerAddress=192.168.5.1/24
|
||||
PoolOffset=10
|
||||
PoolSize=50
|
||||
Router=192.168.5.3
|
||||
DNS=_server_address 192.168.5.10
|
||||
NTP=_server_address 192.168.5.11
|
||||
IPv6OnlyPreferredSec=20s
|
||||
@@ -770,7 +770,11 @@ def setUpModule():
|
||||
save_timezone()
|
||||
|
||||
create_service_dropin('systemd-networkd', networkd_bin,
|
||||
['[Service]', 'Restart=no', '[Unit]', 'StartLimitIntervalSec=0'])
|
||||
['[Service]',
|
||||
'Restart=no',
|
||||
'Environment=SYSTEMD_NETWORK_TEST_MODE=yes',
|
||||
'[Unit]',
|
||||
'StartLimitIntervalSec=0'])
|
||||
create_service_dropin('systemd-resolved', resolved_bin)
|
||||
create_service_dropin('systemd-timesyncd', timesyncd_bin)
|
||||
|
||||
@@ -5110,7 +5114,10 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
|
||||
self.wait_online(['veth-peer:carrier'])
|
||||
|
||||
# information request mode
|
||||
start_dnsmasq('--dhcp-option=option6:dns-server,[2600::ee]',
|
||||
# The name ipv6-only option may not be supported by older dnsmasq
|
||||
# start_dnsmasq('--dhcp-option=option:ipv6-only,300')
|
||||
start_dnsmasq('--dhcp-option=108,00:00:02:00',
|
||||
'--dhcp-option=option6:dns-server,[2600::ee]',
|
||||
'--dhcp-option=option6:ntp-server,[2600::ff]',
|
||||
ra_mode='ra-stateless')
|
||||
self.wait_online(['veth99:routable', 'veth-peer:routable'])
|
||||
@@ -5140,7 +5147,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
|
||||
|
||||
# solicit mode
|
||||
stop_dnsmasq()
|
||||
start_dnsmasq('--dhcp-option=option6:dns-server,[2600::ee]',
|
||||
start_dnsmasq('--dhcp-option=108,00:00:02:00',
|
||||
'--dhcp-option=option6:dns-server,[2600::ee]',
|
||||
'--dhcp-option=option6:ntp-server,[2600::ff]')
|
||||
networkctl_reconfigure('veth99')
|
||||
self.wait_online(['veth99:routable', 'veth-peer:routable'])
|
||||
@@ -5189,7 +5197,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
|
||||
f.write('\n[DHCPv6]\nRapidCommit=no\n')
|
||||
|
||||
stop_dnsmasq()
|
||||
start_dnsmasq('--dhcp-option=option6:dns-server,[2600::ee]',
|
||||
start_dnsmasq('--dhcp-option=108,00:00:02:00',
|
||||
'--dhcp-option=option6:dns-server,[2600::ee]',
|
||||
'--dhcp-option=option6:ntp-server,[2600::ff]')
|
||||
|
||||
networkctl_reload()
|
||||
@@ -5238,16 +5247,30 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
|
||||
# Note that at this point the DHCPv6 client has not been started because no RA (with managed
|
||||
# bit set) has yet been received and the configuration does not include WithoutRA=true
|
||||
state = get_dhcp6_client_state('veth99')
|
||||
print(f"State = {state}")
|
||||
print(f"DHCPv6 client state = {state}")
|
||||
self.assertEqual(state, 'stopped')
|
||||
|
||||
start_dnsmasq()
|
||||
state = get_dhcp4_client_state('veth99')
|
||||
print(f"DHCPv4 client state = {state}")
|
||||
self.assertEqual(state, 'selecting')
|
||||
|
||||
start_dnsmasq('--dhcp-option=108,00:00:02:00')
|
||||
self.wait_online(['veth99:routable', 'veth-peer:routable'])
|
||||
|
||||
state = get_dhcp6_client_state('veth99')
|
||||
print(f"State = {state}")
|
||||
print(f"DHCPv6 client state = {state}")
|
||||
self.assertEqual(state, 'bound')
|
||||
|
||||
# DHCPv4 client will stop after an DHCPOFFER message received, so we need to wait for a while.
|
||||
for _ in range(100):
|
||||
state = get_dhcp4_client_state('veth99')
|
||||
if state == 'stopped':
|
||||
break
|
||||
time.sleep(.2)
|
||||
|
||||
print(f"DHCPv4 client state = {state}")
|
||||
self.assertEqual(state, 'stopped')
|
||||
|
||||
def test_dhcp_client_ipv6_only_with_custom_client_identifier(self):
|
||||
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only-custom-client-identifier.network')
|
||||
|
||||
@@ -5493,6 +5516,18 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
|
||||
print(f"State = {state}")
|
||||
self.assertEqual(state, 'bound')
|
||||
|
||||
def test_dhcp_client_ipv6_only_mode_without_ipv6_connectivity(self):
|
||||
copy_network_unit('25-veth.netdev',
|
||||
'25-dhcp-server-ipv6-only-mode.network',
|
||||
'25-dhcp-client-ipv6-only-mode.network')
|
||||
start_networkd()
|
||||
self.wait_online(['veth99:routable', 'veth-peer:routable'], timeout='40s')
|
||||
self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24', ipv='-4')
|
||||
|
||||
state = get_dhcp4_client_state('veth99')
|
||||
print(f"State = {state}")
|
||||
self.assertEqual(state, 'bound')
|
||||
|
||||
def test_dhcp_client_ipv4_use_routes_gateway(self):
|
||||
first = True
|
||||
for (routes, gateway, dns_and_ntp_routes, classless) in itertools.product([True, False], repeat=4):
|
||||
|
||||
Reference in New Issue
Block a user