2021-08-25 11:30:35 +02:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
|
|
|
|
|
|
#include "src/core/nm-default-daemon.h"
|
|
|
|
|
|
|
|
|
|
#include "nm-l3-ipv6ll.h"
|
|
|
|
|
|
|
|
|
|
#include <linux/if_addr.h>
|
|
|
|
|
|
|
|
|
|
#include "nm-core-utils.h"
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
2021-09-23 10:55:11 +02:00
|
|
|
/* FIXME(l3cfg): ensure that NML3IPv6LL generates the same stable privacy addresses
|
2021-08-25 11:30:35 +02:00
|
|
|
* as previous implementation. */
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
NM_UTILS_LOOKUP_STR_DEFINE(nm_l3_ipv6ll_state_to_string,
|
|
|
|
|
NML3IPv6LLState,
|
|
|
|
|
NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("???"),
|
|
|
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_NONE, "none"),
|
2021-09-15 15:44:04 +02:00
|
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_DEFUNCT, "defunct"),
|
2021-08-25 11:30:35 +02:00
|
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_STARTING, "starting"),
|
|
|
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS,
|
|
|
|
|
"dad-in-progress"),
|
|
|
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_READY, "ready"),
|
|
|
|
|
NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_DAD_FAILED, "dad-failed"), );
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
struct _NML3IPv6LL {
|
2021-11-09 13:28:54 +01:00
|
|
|
NML3Cfg *l3cfg;
|
2021-08-25 11:30:35 +02:00
|
|
|
NML3CfgCommitTypeHandle *l3cfg_commit_handle;
|
|
|
|
|
NML3IPv6LLNotifyFcn notify_fcn;
|
|
|
|
|
gpointer user_data;
|
2021-11-09 13:28:54 +01:00
|
|
|
GSource *starting_on_idle_source;
|
|
|
|
|
GSource *wait_for_addr_source;
|
|
|
|
|
GSource *emit_changed_idle_source;
|
2021-08-25 11:30:35 +02:00
|
|
|
gulong l3cfg_signal_notify_id;
|
|
|
|
|
NML3IPv6LLState state;
|
|
|
|
|
|
|
|
|
|
/* if we have cur_lladdr set, then this might cache the last
|
|
|
|
|
* matching NMPObject from the platform cache. This only serves
|
|
|
|
|
* for optimizing the lookup to the platform cache. */
|
|
|
|
|
const NMPlatformIP6Address *cur_lladdr_obj;
|
|
|
|
|
|
|
|
|
|
struct in6_addr cur_lladdr;
|
|
|
|
|
|
|
|
|
|
/* if we have cur_lladdr and _state_has_lladdr() indicates that
|
|
|
|
|
* the LL address is suitable, this is a NML3ConfigData instance
|
|
|
|
|
* with the configuration. */
|
|
|
|
|
const NML3ConfigData *l3cd;
|
|
|
|
|
|
2021-10-18 17:45:52 +02:00
|
|
|
guint32 route_table;
|
|
|
|
|
|
2021-08-25 11:30:35 +02:00
|
|
|
/* "assume" means that we first look whether there is any suitable
|
|
|
|
|
* IPv6 address on the device, and in that case, try to use that
|
|
|
|
|
* instead of generating a new one. Otherwise, we always try to
|
|
|
|
|
* generate a new LL address. */
|
|
|
|
|
bool assume : 1;
|
|
|
|
|
|
|
|
|
|
struct {
|
|
|
|
|
NMUtilsStableType stable_type;
|
|
|
|
|
guint32 dad_counter;
|
|
|
|
|
struct {
|
|
|
|
|
const char *ifname;
|
|
|
|
|
const char *network_id;
|
|
|
|
|
} stable_privacy;
|
|
|
|
|
struct {
|
|
|
|
|
NMUtilsIPv6IfaceId iid;
|
|
|
|
|
} token;
|
|
|
|
|
} addrgen;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#define _NMLOG_DOMAIN LOGD_IP6
|
|
|
|
|
#define _NMLOG_PREFIX_NAME "ipv6ll"
|
|
|
|
|
#define _NMLOG(level, ...) \
|
|
|
|
|
G_STMT_START \
|
|
|
|
|
{ \
|
|
|
|
|
nm_log((level), \
|
|
|
|
|
(_NMLOG_DOMAIN), \
|
|
|
|
|
NULL, \
|
|
|
|
|
NULL, \
|
|
|
|
|
_NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \
|
|
|
|
|
",ifindex=%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
|
|
|
|
|
NM_HASH_OBFUSCATE_PTR(self), \
|
|
|
|
|
nm_l3cfg_get_ifindex((self)->l3cfg) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
|
|
|
|
|
} \
|
|
|
|
|
G_STMT_END
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#define L3CD_TAG(self) (&(self)->notify_fcn)
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#define _ASSERT(self) \
|
|
|
|
|
G_STMT_START \
|
|
|
|
|
{ \
|
|
|
|
|
NML3IPv6LL *const _self = (self); \
|
|
|
|
|
\
|
|
|
|
|
nm_assert(NM_IS_L3_IPV6LL(_self)); \
|
|
|
|
|
} \
|
|
|
|
|
G_STMT_END
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static void _check(NML3IPv6LL *self);
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
NML3Cfg *
|
|
|
|
|
nm_l3_ipv6ll_get_l3cfg(NML3IPv6LL *self)
|
|
|
|
|
{
|
|
|
|
|
nm_assert(NM_IS_L3_IPV6LL(self));
|
|
|
|
|
|
|
|
|
|
return self->l3cfg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int
|
|
|
|
|
nm_l3_ipv6ll_get_ifindex(NML3IPv6LL *self)
|
|
|
|
|
{
|
|
|
|
|
nm_assert(NM_IS_L3_IPV6LL(self));
|
|
|
|
|
|
|
|
|
|
return nm_l3cfg_get_ifindex(self->l3cfg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NMPlatform *
|
|
|
|
|
nm_l3_ipv6ll_get_platform(NML3IPv6LL *self)
|
|
|
|
|
{
|
|
|
|
|
nm_assert(NM_IS_L3_IPV6LL(self));
|
|
|
|
|
|
|
|
|
|
return nm_l3cfg_get_platform(self->l3cfg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_state_has_lladdr(NML3IPv6LLState state)
|
|
|
|
|
{
|
|
|
|
|
return NM_IN_SET(state, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, NM_L3_IPV6LL_STATE_READY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NML3IPv6LLState
|
|
|
|
|
nm_l3_ipv6ll_get_state(NML3IPv6LL *self, const struct in6_addr **out_lladdr)
|
|
|
|
|
{
|
|
|
|
|
nm_assert(NM_IS_L3_IPV6LL(self));
|
|
|
|
|
|
|
|
|
|
NM_SET_OUT(out_lladdr, _state_has_lladdr(self->state) ? &self->cur_lladdr : NULL);
|
|
|
|
|
return self->state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const NML3ConfigData *
|
|
|
|
|
_l3cd_config_create(int ifindex, const struct in6_addr *lladdr, NMDedupMultiIndex *multi_idx)
|
|
|
|
|
{
|
|
|
|
|
NML3ConfigData *l3cd;
|
|
|
|
|
|
|
|
|
|
nm_assert(ifindex > 0);
|
|
|
|
|
nm_assert(lladdr);
|
|
|
|
|
nm_assert(IN6_IS_ADDR_LINKLOCAL(lladdr));
|
|
|
|
|
|
|
|
|
|
l3cd = nm_l3_config_data_new(multi_idx, ifindex, NM_IP_CONFIG_SOURCE_IP6LL);
|
|
|
|
|
|
|
|
|
|
nm_l3_config_data_add_address_6(
|
|
|
|
|
l3cd,
|
|
|
|
|
NM_PLATFORM_IP6_ADDRESS_INIT(.address = *lladdr,
|
|
|
|
|
.plen = 64,
|
|
|
|
|
.addr_source = NM_IP_CONFIG_SOURCE_IP6LL));
|
|
|
|
|
|
|
|
|
|
nm_l3_config_data_add_route_6(
|
|
|
|
|
l3cd,
|
|
|
|
|
NM_PLATFORM_IP6_ROUTE_INIT(.network.s6_addr16[0] = htons(0xfe80u),
|
|
|
|
|
.plen = 64,
|
|
|
|
|
.metric_any = TRUE,
|
|
|
|
|
.table_any = TRUE,
|
|
|
|
|
.rt_source = NM_IP_CONFIG_SOURCE_IP6LL));
|
|
|
|
|
|
|
|
|
|
return nm_l3_config_data_seal(l3cd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const NML3ConfigData *
|
|
|
|
|
nm_l3_ipv6ll_get_l3cd(NML3IPv6LL *self)
|
|
|
|
|
{
|
|
|
|
|
nm_assert(NM_IS_L3_IPV6LL(self));
|
|
|
|
|
|
|
|
|
|
if (!_state_has_lladdr(self->state)) {
|
|
|
|
|
nm_assert(!self->l3cd);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!self->l3cd) {
|
|
|
|
|
self->l3cd = _l3cd_config_create(nm_l3_ipv6ll_get_ifindex(self),
|
|
|
|
|
&self->cur_lladdr,
|
|
|
|
|
nm_l3cfg_get_multi_idx(self->l3cfg));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self->l3cd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_emit_changed_on_idle_cb(gpointer user_data)
|
|
|
|
|
{
|
2021-11-09 13:28:54 +01:00
|
|
|
NML3IPv6LL *self = user_data;
|
2021-08-25 11:30:35 +02:00
|
|
|
const struct in6_addr *lladdr;
|
|
|
|
|
NML3IPv6LLState state;
|
|
|
|
|
char sbuf[INET6_ADDRSTRLEN];
|
|
|
|
|
|
|
|
|
|
nm_clear_g_source_inst(&self->emit_changed_idle_source);
|
|
|
|
|
|
|
|
|
|
state = nm_l3_ipv6ll_get_state(self, &lladdr);
|
|
|
|
|
|
|
|
|
|
_LOGT("emit changed signal (state=%s%s%s)",
|
|
|
|
|
nm_l3_ipv6ll_state_to_string(state),
|
|
|
|
|
lladdr ? ", " : "",
|
|
|
|
|
lladdr ? _nm_utils_inet6_ntop(lladdr, sbuf) : "");
|
|
|
|
|
|
|
|
|
|
self->notify_fcn(self, state, lladdr, self->user_data);
|
|
|
|
|
|
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_generate_new_address(NML3IPv6LL *self, struct in6_addr *out_lladdr)
|
|
|
|
|
{
|
|
|
|
|
struct in6_addr lladdr;
|
|
|
|
|
|
|
|
|
|
memset(&lladdr, 0, sizeof(struct in6_addr));
|
|
|
|
|
lladdr.s6_addr16[0] = htons(0xfe80u);
|
|
|
|
|
|
|
|
|
|
if (self->addrgen.stable_type == NM_UTILS_STABLE_TYPE_NONE) {
|
|
|
|
|
if (self->addrgen.dad_counter > 0)
|
|
|
|
|
return FALSE;
|
|
|
|
|
self->addrgen.dad_counter++;
|
|
|
|
|
nm_utils_ipv6_addr_set_interface_identifier(&lladdr, &self->addrgen.token.iid);
|
|
|
|
|
} else {
|
|
|
|
|
/* RFC7217 says we MUST limit the number of retries, and it SHOULD try
|
|
|
|
|
* at least IDGEN_RETRIES times (that is, 3 times).
|
|
|
|
|
*
|
|
|
|
|
* 3 times seems really low. Instead, let's try 6 times. */
|
|
|
|
|
G_STATIC_ASSERT(NM_STABLE_PRIVACY_RFC7217_IDGEN_RETRIES == 3);
|
|
|
|
|
if (self->addrgen.dad_counter >= NM_STABLE_PRIVACY_RFC7217_IDGEN_RETRIES + 3)
|
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
|
|
nm_utils_ipv6_addr_set_stable_privacy(self->addrgen.stable_type,
|
|
|
|
|
&lladdr,
|
|
|
|
|
self->addrgen.stable_privacy.ifname,
|
|
|
|
|
self->addrgen.stable_privacy.network_id,
|
|
|
|
|
self->addrgen.dad_counter++);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*out_lladdr = lladdr;
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_pladdr_is_ll_failed(const NMPlatformIP6Address *addr)
|
|
|
|
|
{
|
|
|
|
|
nm_assert(addr);
|
|
|
|
|
nm_assert(IN6_IS_ADDR_LINKLOCAL(&addr->address));
|
|
|
|
|
|
|
|
|
|
return NM_FLAGS_ANY(addr->n_ifa_flags, IFA_F_DADFAILED | IFA_F_DEPRECATED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_pladdr_is_ll_tentative(const NMPlatformIP6Address *addr)
|
|
|
|
|
{
|
|
|
|
|
nm_assert(addr);
|
|
|
|
|
nm_assert(IN6_IS_ADDR_LINKLOCAL(&addr->address));
|
|
|
|
|
nm_assert(!_pladdr_is_ll_failed(addr));
|
|
|
|
|
|
|
|
|
|
return NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TENTATIVE)
|
|
|
|
|
&& !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_OPTIMISTIC);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const NMPlatformIP6Address *
|
|
|
|
|
_pladdr_find_ll(NML3IPv6LL *self, gboolean *out_cur_addr_failed)
|
|
|
|
|
{
|
|
|
|
|
NMDedupMultiIter iter;
|
|
|
|
|
NMPLookup lookup;
|
|
|
|
|
const NMPlatformIP6Address *pladdr1 = NULL;
|
2021-11-09 13:28:54 +01:00
|
|
|
const NMPObject *obj;
|
2021-08-25 11:30:35 +02:00
|
|
|
const NMPlatformIP6Address *pladdr_ready = NULL;
|
|
|
|
|
const NMPlatformIP6Address *pladdr_tentative = NULL;
|
|
|
|
|
gboolean cur_addr_check = TRUE;
|
|
|
|
|
gboolean cur_addr_failed = FALSE;
|
|
|
|
|
gboolean pladdr1_looked_up = FALSE;
|
|
|
|
|
|
|
|
|
|
nm_assert(!self->cur_lladdr_obj
|
|
|
|
|
|| IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &self->cur_lladdr_obj->address));
|
|
|
|
|
|
|
|
|
|
*out_cur_addr_failed = FALSE;
|
|
|
|
|
|
|
|
|
|
if (self->state == NM_L3_IPV6LL_STATE_READY && self->cur_lladdr_obj) {
|
|
|
|
|
nm_assert(!_pladdr_is_ll_tentative(self->cur_lladdr_obj));
|
|
|
|
|
pladdr1 = NMP_OBJECT_CAST_IP6_ADDRESS(
|
|
|
|
|
nm_platform_lookup_obj(nm_l3_ipv6ll_get_platform(self),
|
|
|
|
|
NMP_CACHE_ID_TYPE_OBJECT_TYPE,
|
|
|
|
|
NMP_OBJECT_UP_CAST(self->cur_lladdr_obj)));
|
|
|
|
|
if (self->cur_lladdr_obj == pladdr1) {
|
|
|
|
|
/* Fast-path. We are ready and the cur_lladdr_obj is still in the cache. We
|
|
|
|
|
* got the result with a dictionary lookup without need to iterate over
|
|
|
|
|
* all addresses. */
|
|
|
|
|
return self->cur_lladdr_obj;
|
|
|
|
|
}
|
|
|
|
|
pladdr1_looked_up = TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!self->assume) {
|
|
|
|
|
/* We don't accept any suitable LL address, only he one we are waiting for.
|
|
|
|
|
* Let's do a dictionary lookup. */
|
|
|
|
|
|
|
|
|
|
if (IN6_IS_ADDR_LINKLOCAL(&self->cur_lladdr)) {
|
|
|
|
|
if (!pladdr1_looked_up) {
|
|
|
|
|
NMPObject needle;
|
|
|
|
|
|
|
|
|
|
nmp_object_stackinit_id_ip6_address(&needle,
|
|
|
|
|
nm_l3_ipv6ll_get_ifindex(self),
|
|
|
|
|
&self->cur_lladdr);
|
|
|
|
|
pladdr1 = NMP_OBJECT_CAST_IP6_ADDRESS(
|
|
|
|
|
nm_platform_lookup_obj(nm_l3_ipv6ll_get_platform(self),
|
|
|
|
|
NMP_CACHE_ID_TYPE_OBJECT_TYPE,
|
|
|
|
|
&needle));
|
|
|
|
|
}
|
|
|
|
|
if (pladdr1) {
|
|
|
|
|
if (!_pladdr_is_ll_failed(pladdr1))
|
|
|
|
|
return pladdr1;
|
|
|
|
|
*out_cur_addr_failed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
} else
|
|
|
|
|
nm_assert(!pladdr1_looked_up);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!NM_IN_SET(self->state, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, NM_L3_IPV6LL_STATE_READY))
|
|
|
|
|
cur_addr_check = FALSE;
|
|
|
|
|
|
|
|
|
|
nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, nm_l3_ipv6ll_get_ifindex(self));
|
|
|
|
|
|
|
|
|
|
nm_platform_iter_obj_for_each (&iter, nm_l3_ipv6ll_get_platform(self), &lookup, &obj) {
|
|
|
|
|
const NMPlatformIP6Address *pladdr = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
|
|
|
|
|
|
|
|
|
|
if (!IN6_IS_ADDR_LINKLOCAL(&pladdr->address))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (_pladdr_is_ll_failed(pladdr)) {
|
|
|
|
|
if (cur_addr_check && IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &pladdr->address)) {
|
|
|
|
|
/* "pladdr" is the address we are currently doing DAD for. But it failed.
|
|
|
|
|
* We need to recognize and report to the caller, to stop waiting for this
|
|
|
|
|
* address. */
|
|
|
|
|
cur_addr_failed = TRUE;
|
|
|
|
|
cur_addr_check = FALSE;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_pladdr_is_ll_tentative(pladdr)) {
|
|
|
|
|
if (!pladdr_tentative)
|
|
|
|
|
pladdr_tentative = pladdr;
|
|
|
|
|
else if (pladdr == self->cur_lladdr_obj)
|
|
|
|
|
pladdr_tentative = pladdr;
|
|
|
|
|
else if (IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &pladdr->address))
|
|
|
|
|
pladdr_tentative = pladdr;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pladdr == self->cur_lladdr_obj) {
|
|
|
|
|
/* it doesn't get any better. We have our best address. */
|
|
|
|
|
return pladdr;
|
|
|
|
|
}
|
|
|
|
|
if (!pladdr_ready)
|
|
|
|
|
pladdr_ready = pladdr;
|
|
|
|
|
else if (IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &pladdr->address))
|
|
|
|
|
pladdr_ready = pladdr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*out_cur_addr_failed = cur_addr_failed;
|
|
|
|
|
return pladdr_ready ?: pladdr_tentative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
_lladdr_handle_changed(NML3IPv6LL *self)
|
|
|
|
|
{
|
|
|
|
|
const NML3ConfigData *l3cd;
|
|
|
|
|
gboolean changed = FALSE;
|
|
|
|
|
|
|
|
|
|
/* We register the l3cd with l3cfg to start DAD. That is different from
|
|
|
|
|
* NML3IPv4LL, where we use NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD. The difference
|
|
|
|
|
* is that for IPv6 we let kernel do DAD, so we need to actually configure the
|
|
|
|
|
* address. For IPv4, we can run ACD without configuring anything in kernel,
|
|
|
|
|
* and let the user decide how to proceed.
|
|
|
|
|
*
|
|
|
|
|
* Also in this case, we use the most graceful commit-type (NM_L3_CFG_COMMIT_TYPE_ASSUME),
|
|
|
|
|
* but for that to work, we also need NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE flag. */
|
|
|
|
|
|
|
|
|
|
l3cd = nm_l3_ipv6ll_get_l3cd(self);
|
|
|
|
|
|
|
|
|
|
if (l3cd) {
|
|
|
|
|
if (nm_l3cfg_add_config(self->l3cfg,
|
|
|
|
|
L3CD_TAG(self),
|
|
|
|
|
TRUE,
|
|
|
|
|
l3cd,
|
|
|
|
|
NM_L3CFG_CONFIG_PRIORITY_IPV6LL,
|
|
|
|
|
0,
|
2021-10-18 17:45:52 +02:00
|
|
|
self->route_table,
|
2021-08-25 11:30:35 +02:00
|
|
|
NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4,
|
|
|
|
|
NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
2021-09-15 15:38:26 +02:00
|
|
|
NM_DNS_PRIORITY_DEFAULT_NORMAL,
|
|
|
|
|
NM_DNS_PRIORITY_DEFAULT_NORMAL,
|
2021-08-25 11:30:35 +02:00
|
|
|
NM_L3_ACD_DEFEND_TYPE_ALWAYS,
|
|
|
|
|
0,
|
|
|
|
|
NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE,
|
|
|
|
|
NM_L3_CONFIG_MERGE_FLAGS_NONE))
|
|
|
|
|
changed = TRUE;
|
|
|
|
|
} else {
|
|
|
|
|
if (nm_l3cfg_remove_config_all(self->l3cfg, L3CD_TAG(self)))
|
|
|
|
|
changed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self->l3cfg_commit_handle = nm_l3cfg_commit_type_register(self->l3cfg,
|
|
|
|
|
l3cd ? NM_L3_CFG_COMMIT_TYPE_ASSUME
|
|
|
|
|
: NM_L3_CFG_COMMIT_TYPE_NONE,
|
2021-09-28 22:15:24 +02:00
|
|
|
self->l3cfg_commit_handle,
|
|
|
|
|
"ipv6ll");
|
2021-08-25 11:30:35 +02:00
|
|
|
|
|
|
|
|
if (changed)
|
2021-09-24 19:32:03 +02:00
|
|
|
nm_l3cfg_commit_on_idle_schedule(self->l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO);
|
2021-08-25 11:30:35 +02:00
|
|
|
|
|
|
|
|
if (!self->emit_changed_idle_source) {
|
|
|
|
|
_LOGT("schedule changed signal on idle");
|
|
|
|
|
self->emit_changed_idle_source = nm_g_idle_add_source(_emit_changed_on_idle_cb, self);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_set_cur_lladdr(NML3IPv6LL *self, NML3IPv6LLState state, const struct in6_addr *lladdr)
|
|
|
|
|
{
|
|
|
|
|
gboolean changed = FALSE;
|
|
|
|
|
|
|
|
|
|
if (lladdr) {
|
|
|
|
|
nm_assert(IN6_IS_ADDR_LINKLOCAL(lladdr));
|
|
|
|
|
if (!IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, lladdr)) {
|
|
|
|
|
self->cur_lladdr = *lladdr;
|
|
|
|
|
nm_clear_l3cd(&self->l3cd);
|
|
|
|
|
changed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!nm_ip_addr_is_null(AF_INET6, &self->cur_lladdr)) {
|
|
|
|
|
nm_clear_l3cd(&self->l3cd);
|
|
|
|
|
self->cur_lladdr = nm_ip_addr_zero.addr6;
|
|
|
|
|
changed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
nm_assert(!self->l3cd);
|
|
|
|
|
nm_assert(!_state_has_lladdr(state));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self->state != state) {
|
|
|
|
|
if (!_state_has_lladdr(state))
|
|
|
|
|
nm_clear_l3cd(&self->l3cd);
|
|
|
|
|
self->state = state;
|
|
|
|
|
changed = TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return changed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_set_cur_lladdr_obj(NML3IPv6LL *self, NML3IPv6LLState state, const NMPlatformIP6Address *lladdr_obj)
|
|
|
|
|
{
|
|
|
|
|
nm_assert(lladdr_obj);
|
|
|
|
|
nm_assert(_state_has_lladdr(state));
|
|
|
|
|
|
|
|
|
|
nmp_object_ref_set_up_cast(&self->cur_lladdr_obj, lladdr_obj);
|
|
|
|
|
return _set_cur_lladdr(self, state, &lladdr_obj->address);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_set_cur_lladdr_bin(NML3IPv6LL *self, NML3IPv6LLState state, const struct in6_addr *lladdr)
|
|
|
|
|
{
|
|
|
|
|
nmp_object_ref_set_up_cast(&self->cur_lladdr_obj, NULL);
|
|
|
|
|
return _set_cur_lladdr(self, state, lladdr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_wait_for_addr_timeout_cb(gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
NML3IPv6LL *self = user_data;
|
|
|
|
|
|
|
|
|
|
nm_clear_g_source_inst(&self->wait_for_addr_source);
|
|
|
|
|
|
|
|
|
|
nm_assert(
|
|
|
|
|
NM_IN_SET(self->state, NM_L3_IPV6LL_STATE_DAD_FAILED, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS));
|
|
|
|
|
|
|
|
|
|
_check(self);
|
|
|
|
|
|
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
_check(NML3IPv6LL *self)
|
|
|
|
|
{
|
|
|
|
|
const NMPlatformIP6Address *pladdr;
|
|
|
|
|
char sbuf[INET6_ADDRSTRLEN];
|
|
|
|
|
gboolean cur_addr_failed;
|
|
|
|
|
struct in6_addr lladdr;
|
|
|
|
|
|
|
|
|
|
pladdr = _pladdr_find_ll(self, &cur_addr_failed);
|
|
|
|
|
|
|
|
|
|
if (pladdr) {
|
|
|
|
|
nm_clear_g_source_inst(&self->wait_for_addr_source);
|
|
|
|
|
|
|
|
|
|
if (_pladdr_is_ll_tentative(pladdr)) {
|
|
|
|
|
if (_set_cur_lladdr_obj(self, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, pladdr)) {
|
|
|
|
|
_LOGT("changed: waiting for address %s to complete DAD",
|
|
|
|
|
_nm_utils_inet6_ntop(&self->cur_lladdr, sbuf));
|
|
|
|
|
_lladdr_handle_changed(self);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_set_cur_lladdr_obj(self, NM_L3_IPV6LL_STATE_READY, pladdr)) {
|
|
|
|
|
_LOGT("changed: address %s is ready", _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf));
|
|
|
|
|
_lladdr_handle_changed(self);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self->cur_lladdr_obj || cur_addr_failed) {
|
|
|
|
|
/* we were doing DAD, but the address is no longer a suitable candidate.
|
|
|
|
|
* Prematurely abort DAD to generate a new address below. */
|
|
|
|
|
nm_assert(
|
|
|
|
|
NM_IN_SET(self->state, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, NM_L3_IPV6LL_STATE_READY));
|
|
|
|
|
if (self->state == NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS)
|
|
|
|
|
_LOGT("changed: address %s did not complete DAD",
|
|
|
|
|
_nm_utils_inet6_ntop(&self->cur_lladdr, sbuf));
|
|
|
|
|
else {
|
|
|
|
|
_LOGT("changed: address %s is gone", _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* reset the state here, so that we are sure that the following
|
|
|
|
|
* _set_cur_lladdr_bin() calls (below) will notice the change
|
|
|
|
|
* and trigger a _lladdr_handle_changed(). */
|
|
|
|
|
_set_cur_lladdr_bin(self, NM_L3_IPV6LL_STATE_STARTING, NULL);
|
|
|
|
|
nm_clear_g_source_inst(&self->wait_for_addr_source);
|
|
|
|
|
} else if (self->wait_for_addr_source) {
|
|
|
|
|
/* we are waiting. Nothing to do for now. */
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_generate_new_address(self, &lladdr)) {
|
|
|
|
|
/* our DAD counter expired. We reset it, and start a timer to retry
|
|
|
|
|
* and recover. */
|
|
|
|
|
self->addrgen.dad_counter = 0;
|
|
|
|
|
self->wait_for_addr_source =
|
|
|
|
|
nm_g_timeout_add_source(10000, _wait_for_addr_timeout_cb, self);
|
|
|
|
|
if (_set_cur_lladdr_bin(self, NM_L3_IPV6LL_STATE_DAD_FAILED, NULL)) {
|
|
|
|
|
_LOGW("changed: no IPv6 link local address to retry after Duplicate Address Detection "
|
|
|
|
|
"failures (back off)");
|
|
|
|
|
_lladdr_handle_changed(self);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* we give NML3Cfg 2 seconds to configure the address on the interface. We
|
|
|
|
|
* thus very soon expect to see this address configured (and kernel started DAD).
|
|
|
|
|
* If that does not happen within timeout, we assume that this address failed DAD. */
|
|
|
|
|
self->wait_for_addr_source = nm_g_timeout_add_source(2000, _wait_for_addr_timeout_cb, self);
|
|
|
|
|
if (_set_cur_lladdr_bin(self, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, &lladdr)) {
|
|
|
|
|
_LOGT("changed: starting DAD for address %s",
|
|
|
|
|
_nm_utils_inet6_ntop(&self->cur_lladdr, sbuf));
|
|
|
|
|
_lladdr_handle_changed(self);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
_l3cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NML3IPv6LL *self)
|
|
|
|
|
{
|
|
|
|
|
if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE) {
|
|
|
|
|
if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags,
|
|
|
|
|
nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP6_ADDRESS)))
|
|
|
|
|
_check(self);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
static gboolean
|
|
|
|
|
_starting_on_idle_cb(gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
NML3IPv6LL *self = user_data;
|
|
|
|
|
|
|
|
|
|
nm_clear_g_source_inst(&self->starting_on_idle_source);
|
|
|
|
|
|
|
|
|
|
self->l3cfg_signal_notify_id =
|
|
|
|
|
g_signal_connect(self->l3cfg, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_l3cfg_notify_cb), self);
|
|
|
|
|
|
|
|
|
|
_check(self);
|
|
|
|
|
|
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
|
|
NML3IPv6LL *
|
2021-11-09 13:28:54 +01:00
|
|
|
_nm_l3_ipv6ll_new(NML3Cfg *l3cfg,
|
2021-08-25 11:30:35 +02:00
|
|
|
gboolean assume,
|
|
|
|
|
NMUtilsStableType stable_type,
|
2021-11-09 13:28:54 +01:00
|
|
|
const char *ifname,
|
|
|
|
|
const char *network_id,
|
2021-08-25 11:30:35 +02:00
|
|
|
const NMUtilsIPv6IfaceId *token_iid,
|
2021-10-18 17:45:52 +02:00
|
|
|
guint32 route_table,
|
2021-08-25 11:30:35 +02:00
|
|
|
NML3IPv6LLNotifyFcn notify_fcn,
|
|
|
|
|
gpointer user_data)
|
|
|
|
|
{
|
|
|
|
|
NML3IPv6LL *self;
|
|
|
|
|
|
|
|
|
|
g_return_val_if_fail(NM_IS_L3CFG(l3cfg), NULL);
|
|
|
|
|
g_return_val_if_fail(notify_fcn, NULL);
|
|
|
|
|
g_return_val_if_fail(
|
|
|
|
|
(stable_type == NM_UTILS_STABLE_TYPE_NONE && !ifname && !network_id && token_iid)
|
|
|
|
|
|| (stable_type != NM_UTILS_STABLE_TYPE_NONE && ifname && network_id && !token_iid),
|
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
|
|
self = g_slice_new(NML3IPv6LL);
|
|
|
|
|
*self = (NML3IPv6LL){
|
|
|
|
|
.l3cfg = g_object_ref(l3cfg),
|
|
|
|
|
.notify_fcn = notify_fcn,
|
|
|
|
|
.user_data = user_data,
|
|
|
|
|
.state = NM_L3_IPV6LL_STATE_STARTING,
|
|
|
|
|
.starting_on_idle_source = nm_g_idle_add_source(_starting_on_idle_cb, self),
|
|
|
|
|
.l3cfg_signal_notify_id = 0,
|
|
|
|
|
.cur_lladdr_obj = NULL,
|
|
|
|
|
.cur_lladdr = IN6ADDR_ANY_INIT,
|
|
|
|
|
.assume = assume,
|
2021-10-18 17:45:52 +02:00
|
|
|
.route_table = route_table,
|
2021-08-25 11:30:35 +02:00
|
|
|
.addrgen =
|
|
|
|
|
{
|
|
|
|
|
.stable_type = stable_type,
|
|
|
|
|
.dad_counter = 0,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (self->addrgen.stable_type == NM_UTILS_STABLE_TYPE_NONE) {
|
|
|
|
|
char sbuf_token[sizeof(self->addrgen.token.iid) * 3];
|
|
|
|
|
|
|
|
|
|
self->addrgen.token.iid = *token_iid;
|
|
|
|
|
_LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT ", ifindex=%d, token=%s%s",
|
|
|
|
|
NM_HASH_OBFUSCATE_PTR(l3cfg),
|
|
|
|
|
nm_l3cfg_get_ifindex(l3cfg),
|
|
|
|
|
nm_utils_bin2hexstr_full(&self->addrgen.token.iid,
|
|
|
|
|
sizeof(self->addrgen.token.iid),
|
|
|
|
|
':',
|
|
|
|
|
FALSE,
|
|
|
|
|
sbuf_token),
|
|
|
|
|
self->assume ? ", assume" : "");
|
|
|
|
|
} else {
|
|
|
|
|
self->addrgen.stable_privacy.ifname = g_strdup(ifname);
|
|
|
|
|
self->addrgen.stable_privacy.network_id = g_strdup(network_id);
|
|
|
|
|
_LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT
|
|
|
|
|
", ifindex=%d, stable-type=%u, ifname=%s, network_id=%s%s",
|
|
|
|
|
NM_HASH_OBFUSCATE_PTR(l3cfg),
|
|
|
|
|
nm_l3cfg_get_ifindex(l3cfg),
|
|
|
|
|
(unsigned) self->addrgen.stable_type,
|
|
|
|
|
self->addrgen.stable_privacy.ifname,
|
|
|
|
|
self->addrgen.stable_privacy.network_id,
|
|
|
|
|
self->assume ? ", assume" : "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
nm_l3_ipv6ll_destroy(NML3IPv6LL *self)
|
|
|
|
|
{
|
|
|
|
|
if (!self)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
_ASSERT(self);
|
|
|
|
|
|
|
|
|
|
_LOGT("finalize");
|
|
|
|
|
|
|
|
|
|
nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle));
|
|
|
|
|
|
|
|
|
|
nm_l3cfg_remove_config_all(self->l3cfg, L3CD_TAG(self));
|
|
|
|
|
|
|
|
|
|
nm_clear_g_source_inst(&self->starting_on_idle_source);
|
|
|
|
|
nm_clear_g_source_inst(&self->wait_for_addr_source);
|
|
|
|
|
nm_clear_g_source_inst(&self->emit_changed_idle_source);
|
|
|
|
|
nm_clear_g_signal_handler(self->l3cfg, &self->l3cfg_signal_notify_id);
|
|
|
|
|
|
|
|
|
|
g_clear_object(&self->l3cfg);
|
|
|
|
|
|
|
|
|
|
nm_clear_l3cd(&self->l3cd);
|
|
|
|
|
|
|
|
|
|
nm_clear_nmp_object_up_cast(&self->cur_lladdr_obj);
|
|
|
|
|
|
|
|
|
|
if (self->addrgen.stable_type != NM_UTILS_STABLE_TYPE_NONE) {
|
|
|
|
|
g_free((char *) self->addrgen.stable_privacy.ifname);
|
|
|
|
|
g_free((char *) self->addrgen.stable_privacy.network_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nm_g_slice_free(self);
|
|
|
|
|
}
|