Merge pull request #29472 from yuwata/network-dhcp-ipv6-only-mode

network/dhcp: add IPv6 only mode support
This commit is contained in:
Yu Watanabe
2023-10-12 13:06:54 +09:00
committed by GitHub
26 changed files with 435 additions and 25 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -116,6 +116,7 @@ struct sd_dhcp_client {
sd_event_source *timeout_t1;
sd_event_source *timeout_t2;
sd_event_source *timeout_expire;
sd_event_source *timeout_ipv6_only_mode;
sd_dhcp_client_callback_t callback;
void *userdata;
sd_dhcp_client_callback_t state_callback;
@@ -125,6 +126,7 @@ struct sd_dhcp_client {
int ip_service_type;
int socket_priority;
bool socket_priority_set;
bool ipv6_acquired;
};
static const uint8_t default_req_opts[] = {
@@ -280,6 +282,12 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) {
return set_ensure_put(&client->req_opts, NULL, UINT8_TO_PTR(option));
}
static int client_request_contains(sd_dhcp_client *client, uint8_t option) {
assert(client);
return set_contains(client->req_opts, UINT8_TO_PTR(option));
}
int sd_dhcp_client_set_request_address(
sd_dhcp_client *client,
const struct in_addr *last_addr) {
@@ -778,6 +786,7 @@ static int client_initialize(sd_dhcp_client *client) {
(void) event_source_disable(client->timeout_t1);
(void) event_source_disable(client->timeout_t2);
(void) event_source_disable(client->timeout_expire);
(void) event_source_disable(client->timeout_ipv6_only_mode);
client->attempt = 0;
@@ -1424,6 +1433,8 @@ static int client_initialize_time_events(sd_dhcp_client *client) {
assert(client);
assert(client->event);
(void) event_source_disable(client->timeout_ipv6_only_mode);
if (client->start_delay > 0) {
assert_se(sd_event_now(client->event, CLOCK_BOOTTIME, &usec) >= 0);
usec = usec_add(usec, client->start_delay);
@@ -1613,6 +1624,16 @@ static int client_parse_message(
return log_dhcp_client_errno(client, SYNTHETIC_ERRNO(ENOMSG),
"received lease lacks subnet mask, and a fallback one cannot be generated, ignoring.");
/* RFC 8925 section 3.2
* If the client did not include the IPv6-Only Preferred option code in the Parameter Request List in
* the DHCPDISCOVER or DHCPREQUEST message, it MUST ignore the IPv6-Only Preferred option in any
* messages received from the server. */
if (lease->ipv6_only_preferred_usec > 0 &&
!client_request_contains(client, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED)) {
log_dhcp_client(client, "Received message with unrequested IPv6-only preferred option, ignoring the option.");
lease->ipv6_only_preferred_usec = 0;
}
*ret = TAKE_PTR(lease);
return 0;
}
@@ -1639,13 +1660,26 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *message, siz
static int client_enter_requesting(sd_dhcp_client *client) {
assert(client);
assert(client->lease);
if (client->lease->ipv6_only_preferred_usec > 0) {
if (client->ipv6_acquired) {
log_dhcp_client(client,
"Received an OFFER with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client.");
return sd_dhcp_client_stop(client);
}
log_dhcp_client(client,
"Received an OFFER with IPv6-only preferred option, delaying to send REQUEST with %s.",
FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC));
}
client_set_state(client, DHCP_STATE_REQUESTING);
client->attempt = 0;
return event_reset_time_relative(client->event, &client->timeout_resend,
CLOCK_BOOTTIME,
0, 0,
client->lease->ipv6_only_preferred_usec, 0,
client_timeout_resend, client,
client->event_priority, "dhcp4-resend-timer",
/* force_reset = */ true);
@@ -1814,7 +1848,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) {
return 0;
}
static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
static int client_enter_bound_now(sd_dhcp_client *client, int notify_event) {
int r;
assert(client);
@@ -1822,9 +1856,6 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING))
notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE;
client->start_delay = 0;
(void) event_source_disable(client->timeout_resend);
client_set_state(client, DHCP_STATE_BOUND);
client->attempt = 0;
@@ -1853,6 +1884,47 @@ static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
return 0;
}
static int client_timeout_ipv6_only_mode(sd_event_source *s, uint64_t usec, void *userdata) {
sd_dhcp_client *client = ASSERT_PTR(userdata);
DHCP_CLIENT_DONT_DESTROY(client);
int r;
r = client_enter_bound_now(client, SD_DHCP_CLIENT_EVENT_IP_ACQUIRE);
if (r < 0)
client_stop(client, r);
return 0;
}
static int client_enter_bound(sd_dhcp_client *client, int notify_event) {
assert(client);
assert(client->lease);
client->start_delay = 0;
(void) event_source_disable(client->timeout_resend);
if (client->state == DHCP_STATE_REBOOTING && client->lease->ipv6_only_preferred_usec > 0) {
if (client->ipv6_acquired) {
log_dhcp_client(client,
"Received an ACK with IPv6-only preferred option, and the host already acquired IPv6 connectivity, stopping DHCPv4 client.");
return sd_dhcp_client_stop(client);
}
log_dhcp_client(client,
"Received an ACK with IPv6-only preferred option, delaying to enter bound state with %s.",
FORMAT_TIMESPAN(client->lease->ipv6_only_preferred_usec, USEC_PER_SEC));
return event_reset_time_relative(client->event, &client->timeout_ipv6_only_mode,
CLOCK_BOOTTIME,
client->lease->ipv6_only_preferred_usec, 0,
client_timeout_ipv6_only_mode, client,
client->event_priority, "dhcp4-ipv6-only-mode",
/* force_reset = */ true);
}
return client_enter_bound_now(client, notify_event);
}
static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) {
DHCP_CLIENT_DONT_DESTROY(client);
int r;
@@ -2108,6 +2180,9 @@ int sd_dhcp_client_start(sd_dhcp_client *client) {
assert_return(client, -EINVAL);
/* Note, do not reset the flag in client_initialize(), as it is also called on expire. */
client->ipv6_acquired = false;
r = client_initialize(client);
if (r < 0)
return r;
@@ -2224,6 +2299,20 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) {
return 0;
}
int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have) {
if (!client)
return 0;
/* We have already received a message with IPv6-Only preferred option, and are waiting for IPv6
* connectivity or timeout, let's stop the client. */
if (have && sd_event_source_get_enabled(client->timeout_ipv6_only_mode, NULL) > 0)
return sd_dhcp_client_stop(client);
/* Otherwise, save that the host already has IPv6 connectivity. */
client->ipv6_acquired = have;
return 0;
}
int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) {
int r;

View File

@@ -884,6 +884,16 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
log_debug_errno(r, "Failed to parse 6rd option, ignoring: %m");
break;
case SD_DHCP_OPTION_IPV6_ONLY_PREFERRED:
r = lease_parse_be32_seconds(option, len, /* max_as_infinity = */ false, &lease->ipv6_only_preferred_usec);
if (r < 0)
log_debug_errno(r, "Failed to parse IPv6 only preferred option, ignoring: %m");
else if (lease->ipv6_only_preferred_usec < MIN_V6ONLY_WAIT_USEC &&
!network_test_mode_enabled())
lease->ipv6_only_preferred_usec = MIN_V6ONLY_WAIT_USEC;
break;
case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST:
r = dhcp_lease_insert_private_option(lease, code, option, len);
if (r < 0)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -152,6 +152,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client);
int sd_dhcp_client_send_release(sd_dhcp_client *client);
int sd_dhcp_client_send_decline(sd_dhcp_client *client);
int sd_dhcp_client_send_renew(sd_dhcp_client *client);
int sd_dhcp_client_set_ipv6_connectivity(sd_dhcp_client *client, int have);
sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client);
sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client);

View File

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

View File

@@ -0,0 +1,10 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=veth99
[Network]
DHCP=ipv4
IPv6AcceptRA=no
[DHCPv4]
IPv6OnlyMode=yes

View File

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

View 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

View File

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