dhcp: support the IPv6-Only Preferred option
Add support for handling the IPv6-Only Preferred option. When enabled, the client adds the option code to the "Parameter Request List" option of the DHCPDISCOVER and DHCPREQUEST messages. If the server sends the option back in the DHCPOFFER and DHCPACK, the host stops the DHCP client for the time interval specified in the option. After the timeout expires, DHCP is restarted.
This commit is contained in:
@@ -91,6 +91,9 @@ typedef struct _NMDhcpClientPrivate {
|
||||
|
||||
union {
|
||||
struct {
|
||||
/* Timer for restarting DHCP after the IPv6-only timeout */
|
||||
GSource *ipv6_only_restart_source;
|
||||
|
||||
struct {
|
||||
NML3CfgCommitTypeHandle *l3cfg_commit_handle;
|
||||
GSource *done_source;
|
||||
@@ -1403,6 +1406,51 @@ nm_dhcp_client_start(NMDhcpClient *self, GError **error)
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static gboolean
|
||||
ipv6_only_restart_timeout_cb(gpointer user_data)
|
||||
{
|
||||
NMDhcpClient *self = user_data;
|
||||
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
||||
gs_free_error GError *error = NULL;
|
||||
|
||||
nm_assert(priv->config.addr_family == AF_INET);
|
||||
|
||||
nm_clear_g_source_inst(&priv->v4.ipv6_only_restart_source);
|
||||
if (!nm_dhcp_client_start(self, &error)) {
|
||||
_LOGW("failed to restart the DHCP client after the IPv6-only timeout: %s", error->message);
|
||||
_emit_notify(self,
|
||||
NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
|
||||
.it_looks_bad.reason = error->message);
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* nm_dhcp_client_schedule_ipv6_only_restart():
|
||||
* @self: the client
|
||||
* @timeout: the raw value from the DHCP option
|
||||
*
|
||||
* Stops the DHCPv4 client and restarts it after the timeout announced
|
||||
* by the "IPv6-Only preferred" option.
|
||||
*/
|
||||
void
|
||||
nm_dhcp_client_schedule_ipv6_only_restart(NMDhcpClient *self, guint timeout)
|
||||
{
|
||||
NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
|
||||
|
||||
nm_assert(priv->config.addr_family == AF_INET);
|
||||
nm_assert(!priv->is_stopped);
|
||||
|
||||
timeout = NM_MAX(NM_DHCP_MIN_V6ONLY_WAIT_DEFAULT, timeout);
|
||||
_LOGI("received option \"ipv6-only-preferred\": stopping DHCPv4 for %u seconds", timeout);
|
||||
|
||||
nm_dhcp_client_stop(self, FALSE);
|
||||
nm_clear_g_source_inst(&priv->no_lease_timeout_source);
|
||||
priv->v4.ipv6_only_restart_source =
|
||||
nm_g_timeout_add_seconds_source(timeout, ipv6_only_restart_timeout_cb, self);
|
||||
}
|
||||
|
||||
void
|
||||
nm_dhcp_client_stop_existing(const char *pid_file, const char *binary_name)
|
||||
{
|
||||
@@ -1477,6 +1525,8 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release)
|
||||
|
||||
nm_clear_pointer(&priv->effective_client_id, g_bytes_unref);
|
||||
nm_clear_g_source_inst(&priv->previous_lease_timeout_source);
|
||||
if (priv->config.addr_family == AF_INET)
|
||||
nm_clear_g_source_inst(&priv->v4.ipv6_only_restart_source);
|
||||
|
||||
priv->is_stopped = TRUE;
|
||||
|
||||
@@ -1978,7 +2028,9 @@ dispose(GObject *object)
|
||||
nm_clear_g_source_inst(&priv->previous_lease_timeout_source);
|
||||
nm_clear_g_source_inst(&priv->no_lease_timeout_source);
|
||||
|
||||
if (!NM_IS_IPv4(priv->config.addr_family)) {
|
||||
if (priv->config.addr_family == AF_INET) {
|
||||
nm_clear_g_source_inst(&priv->v4.ipv6_only_restart_source);
|
||||
} else {
|
||||
nm_clear_g_source_inst(&priv->v6.lladdr_timeout_source);
|
||||
nm_clear_g_source_inst(&priv->v6.dad_timeout_source);
|
||||
}
|
||||
|
@@ -27,6 +27,8 @@
|
||||
|
||||
#define NM_DHCP_CLIENT_NOTIFY "dhcp-notify"
|
||||
|
||||
#define NM_DHCP_MIN_V6ONLY_WAIT_DEFAULT 300u /* (seconds). RFC 8925, section 3.4 */
|
||||
|
||||
typedef enum {
|
||||
NM_DHCP_CLIENT_EVENT_TYPE_UNSPECIFIED,
|
||||
|
||||
@@ -172,6 +174,8 @@ typedef struct {
|
||||
/* Whether to send or not the client identifier */
|
||||
bool send_client_id : 1;
|
||||
|
||||
/* Request and honor the "IPv6-only Preferred" option (RFC 8925).*/
|
||||
bool ipv6_only_preferred : 1;
|
||||
} v4;
|
||||
struct {
|
||||
/* If set, the DUID from the connection is used; otherwise
|
||||
@@ -246,6 +250,8 @@ const NML3ConfigData *nm_dhcp_client_get_lease(NMDhcpClient *self);
|
||||
|
||||
void nm_dhcp_client_stop(NMDhcpClient *self, gboolean release);
|
||||
|
||||
void nm_dhcp_client_schedule_ipv6_only_restart(NMDhcpClient *self, guint timeout);
|
||||
|
||||
/* Backend helpers for subclasses */
|
||||
void nm_dhcp_client_stop_existing(const char *pid_file, const char *binary_name);
|
||||
|
||||
|
@@ -462,6 +462,11 @@ dhclient_start(NMDhcpClient *client,
|
||||
"to LOWDELAY (0x10).");
|
||||
}
|
||||
|
||||
if (client_config->v4.ipv6_only_preferred) {
|
||||
_LOGW("the dhclient backend does not support the \"IPv6-Only Preferred\" option; ignoring "
|
||||
"it");
|
||||
}
|
||||
|
||||
/* Usually the system bus address is well-known; but if it's supposed
|
||||
* to be something else, we need to push it to dhclient, since dhclient
|
||||
* sanitizes the environment it gives the action scripts.
|
||||
|
@@ -168,6 +168,27 @@ lease_option_consume_route(const uint8_t **datap,
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
static gboolean
|
||||
lease_get_ipv6_only_wait_time(NDhcp4ClientLease *lease, guint32 *out_val, const char *iface)
|
||||
{
|
||||
const uint8_t *data;
|
||||
size_t len;
|
||||
int r;
|
||||
|
||||
r = _client_lease_query(lease, NM_DHCP_OPTION_DHCP4_IPV6_ONLY_PREFERRED, &data, &len);
|
||||
if (r == 0
|
||||
&& nm_dhcp_lease_data_parse_u32(data,
|
||||
len,
|
||||
out_val,
|
||||
iface,
|
||||
AF_INET,
|
||||
NM_DHCP_OPTION_DHCP4_IPV6_ONLY_PREFERRED)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
lease_parse_address(NMDhcpNettools *self /* for logging context only */,
|
||||
NDhcp4ClientLease *lease,
|
||||
@@ -929,6 +950,22 @@ bound4_handle(NMDhcpNettools *self, guint event, NDhcp4ClientLease *lease)
|
||||
l3cd);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
dhcp4_handle_ipv6_only(NMDhcpNettools *self, NDhcp4ClientEvent *event)
|
||||
{
|
||||
NMDhcpClient *client = NM_DHCP_CLIENT(self);
|
||||
guint32 val;
|
||||
|
||||
if (nm_dhcp_client_get_config(client)->v4.ipv6_only_preferred
|
||||
&& lease_get_ipv6_only_wait_time(event->offer.lease,
|
||||
&val,
|
||||
nm_dhcp_client_get_iface(client))) {
|
||||
nm_dhcp_client_schedule_ipv6_only_restart(client, val);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
dhcp4_event_handle(NMDhcpNettools *self, NDhcp4ClientEvent *event)
|
||||
{
|
||||
@@ -962,18 +999,23 @@ dhcp4_event_handle(NMDhcpNettools *self, NDhcp4ClientEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
n_dhcp4_client_lease_get_yiaddr(event->offer.lease, &yiaddr);
|
||||
if (yiaddr.s_addr == INADDR_ANY) {
|
||||
_LOGD("selecting lease failed: no yiaddr address");
|
||||
return;
|
||||
}
|
||||
|
||||
if (nm_dhcp_client_server_id_is_rejected(NM_DHCP_CLIENT(self), &server_id)) {
|
||||
_LOGD("server-id %s is in the reject-list, ignoring",
|
||||
nm_inet_ntop(AF_INET, &server_id, addr_str));
|
||||
return;
|
||||
}
|
||||
|
||||
if (dhcp4_handle_ipv6_only(self, event))
|
||||
return;
|
||||
|
||||
/* Check yiaddr only after evaluating the ipv6-only-preferred option, because if
|
||||
* the option is present yiaddr can be zero. */
|
||||
n_dhcp4_client_lease_get_yiaddr(event->offer.lease, &yiaddr);
|
||||
if (yiaddr.s_addr == INADDR_ANY) {
|
||||
_LOGD("selecting lease failed: no yiaddr address");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_nm_dhcp_client_accept_offer(NM_DHCP_CLIENT(self), &yiaddr.s_addr)) {
|
||||
/* We don't log about this, the parent class is expected to notify about the reasons. */
|
||||
return;
|
||||
@@ -1001,6 +1043,17 @@ dhcp4_event_handle(NMDhcpNettools *self, NDhcp4ClientEvent *event)
|
||||
_nm_dhcp_client_notify(NM_DHCP_CLIENT(self), NM_DHCP_CLIENT_EVENT_TYPE_FAIL, NULL);
|
||||
return;
|
||||
case N_DHCP4_CLIENT_EVENT_GRANTED:
|
||||
if (dhcp4_handle_ipv6_only(self, event)) {
|
||||
/* RFC 8925 says that when the client receives a DHCPACK, it should
|
||||
* stop the client; but only in the INIT-REBOOT (actually, REBOOTING)
|
||||
* state, otherwise it should continue to use the address.
|
||||
* The GRANTED event is emitted both in the REBOOTING and REQUESTING
|
||||
* state; however if we got the IPv6-only option in the OFFER we have
|
||||
* already stopped the client. Therefore this point can be reached
|
||||
* only in the REBOOTING state.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
bound4_handle(self, event->event, event->granted.lease);
|
||||
return;
|
||||
case N_DHCP4_CLIENT_EVENT_EXTENDED:
|
||||
@@ -1377,6 +1430,11 @@ ip4_start(NMDhcpClient *client, GError **error)
|
||||
}
|
||||
}
|
||||
|
||||
if (client_config->v4.ipv6_only_preferred) {
|
||||
n_dhcp4_client_probe_config_request_option(config,
|
||||
NM_DHCP_OPTION_DHCP4_IPV6_ONLY_PREFERRED);
|
||||
}
|
||||
|
||||
if (client_config->mud_url) {
|
||||
r = n_dhcp4_client_probe_config_append_option(config,
|
||||
NM_DHCP_OPTION_DHCP4_MUD_URL,
|
||||
|
@@ -113,6 +113,7 @@ const NMDhcpOption _nm_dhcp_option_dhcp4_options[] = {
|
||||
REQ(NM_DHCP_OPTION_DHCP4_PXE_CLIENT_ID, "pxe_client_id", FALSE),
|
||||
REQ(NM_DHCP_OPTION_DHCP4_UAP_SERVERS, "uap_servers", FALSE),
|
||||
REQ(NM_DHCP_OPTION_DHCP4_GEOCONF_CIVIC, "geoconf_civic", FALSE),
|
||||
REQ(NM_DHCP_OPTION_DHCP4_IPV6_ONLY_PREFERRED, "ipv6_only_preferred", FALSE),
|
||||
REQ(NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_ADDRESS, "netinfo_server_address", FALSE),
|
||||
REQ(NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_TAG, "netinfo_server_tag", FALSE),
|
||||
REQ(NM_DHCP_OPTION_DHCP4_DEFAULT_URL, "default_url", FALSE),
|
||||
@@ -183,11 +184,11 @@ static const NMDhcpOption *const _sorted_options_4[G_N_ELEMENTS(_nm_dhcp_option_
|
||||
A(13), A(53), A(54), A(55), A(57), A(58), A(59), A(60), A(61), A(62), A(63), A(64),
|
||||
A(65), A(66), A(67), A(68), A(69), A(70), A(71), A(72), A(73), A(74), A(75), A(76),
|
||||
A(77), A(78), A(79), A(80), A(81), A(82), A(83), A(84), A(85), A(86), A(87), A(56),
|
||||
A(88), A(89), A(90), A(91), A(92), A(93), A(14), A(7), A(94), A(95), A(96), A(97),
|
||||
A(88), A(89), A(90), A(91), A(92), A(93), A(94), A(14), A(7), A(95), A(96), A(97),
|
||||
A(98), A(99), A(100), A(101), A(102), A(103), A(104), A(105), A(106), A(107), A(108), A(109),
|
||||
A(110), A(111), A(112), A(113), A(114), A(115), A(116), A(117), A(118), A(119), A(120), A(121),
|
||||
A(122), A(123), A(124), A(125), A(126), A(127), A(128), A(129), A(130), A(131), A(132), A(133),
|
||||
A(134), A(15), A(135), A(136), A(16), A(137), A(138), A(139), A(140), A(141), A(142),
|
||||
A(134), A(135), A(15), A(136), A(137), A(16), A(138), A(139), A(140), A(141), A(142), A(143),
|
||||
#undef A
|
||||
};
|
||||
|
||||
|
@@ -93,6 +93,7 @@ typedef enum {
|
||||
NM_DHCP_OPTION_DHCP4_UAP_SERVERS = 98,
|
||||
NM_DHCP_OPTION_DHCP4_GEOCONF_CIVIC = 99,
|
||||
NM_DHCP_OPTION_DHCP4_NEW_TZDB_TIMEZONE = 101,
|
||||
NM_DHCP_OPTION_DHCP4_IPV6_ONLY_PREFERRED = 108,
|
||||
NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_ADDRESS = 112,
|
||||
NM_DHCP_OPTION_DHCP4_NETINFO_SERVER_TAG = 113,
|
||||
NM_DHCP_OPTION_DHCP4_DEFAULT_URL = 114,
|
||||
@@ -188,7 +189,7 @@ typedef struct {
|
||||
bool include;
|
||||
} NMDhcpOption;
|
||||
|
||||
extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[143];
|
||||
extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[144];
|
||||
extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[18];
|
||||
|
||||
static inline const char *
|
||||
|
Reference in New Issue
Block a user