core: add NML3IPv6LL helper

This helper class is supposed to encapsulate most logic about
configuring IPv6 link local addresses and exposes a simpler API in order
to simplify NMDevice. Currently this logic is spread out in NMDevice.

Also, NML3IPv6LL directly uses NML3Cfg, thereby freeing NMDevice to care
about that too much.

For several reasons, NML3IPv6LL works different than NML3IPv4LL.
For one, with IPv6 we need to configure the address in kernel, which does
DAD for us. So, NML3IPv6LL will tell NML3Cfg to configure those
addresses that it wants to probe. For IPv4, it only tells NML3Cfg to do
ACD, without configuring anything yet. That is left to the caller.
This commit is contained in:
Thomas Haller 2021-08-25 11:30:35 +02:00
parent 929eae245d
commit aa070fb821
No known key found for this signature in database
GPG Key ID: 29C2366E4DFC5728
8 changed files with 1103 additions and 1 deletions

View File

@ -2382,6 +2382,8 @@ src_core_libNetworkManagerBase_la_SOURCES = \
src/core/nm-l3-config-data.h \
src/core/nm-l3-ipv4ll.c \
src/core/nm-l3-ipv4ll.h \
src/core/nm-l3-ipv6ll.c \
src/core/nm-l3-ipv6ll.h \
src/core/nm-l3cfg.c \
src/core/nm-l3cfg.h \
src/core/nm-ip-config.c \

View File

@ -51,6 +51,7 @@ libNetworkManagerBase = static_library(
'nm-netns.c',
'nm-l3-config-data.c',
'nm-l3-ipv4ll.c',
'nm-l3-ipv6ll.c',
'nm-l3cfg.c',
'nm-ip-config.c',
'nm-ip4-config.c',

View File

@ -298,6 +298,8 @@ typedef enum {
NM_UTILS_STABLE_TYPE_RANDOM = 3,
} NMUtilsStableType;
#define NM_UTILS_STABLE_TYPE_NONE ((NMUtilsStableType) -1)
NMUtilsStableType nm_utils_stable_id_parse(const char *stable_id,
const char *deviceid,
const char *hwaddr,

713
src/core/nm-l3-ipv6ll.c Normal file
View File

@ -0,0 +1,713 @@
/* 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"
/*****************************************************************************/
/* FIXME(next): ensure that NML3IPv6LL generates the same stable privacy addresses
* 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"),
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 {
NML3Cfg * l3cfg;
NML3CfgCommitTypeHandle *l3cfg_commit_handle;
NML3IPv6LLNotifyFcn notify_fcn;
gpointer user_data;
GSource * starting_on_idle_source;
GSource * wait_for_addr_source;
GSource * emit_changed_idle_source;
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;
/* "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)
{
NML3IPv6LL * self = user_data;
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;
const NMPObject * obj;
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,
0,
NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4,
NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
0,
0,
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,
self->l3cfg_commit_handle);
if (changed)
nm_l3cfg_commit_on_idle_schedule(self->l3cfg);
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 *
_nm_l3_ipv6ll_new(NML3Cfg * l3cfg,
gboolean assume,
NMUtilsStableType stable_type,
const char * ifname,
const char * network_id,
const NMUtilsIPv6IfaceId *token_iid,
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,
.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);
}

106
src/core/nm-l3-ipv6ll.h Normal file
View File

@ -0,0 +1,106 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef __NM_L3_IPV6LL_H__
#define __NM_L3_IPV6LL_H__
#include "nm-l3cfg.h"
#include "nm-core-utils.h"
/*****************************************************************************/
typedef struct _NML3IPv6LL NML3IPv6LL;
typedef enum _nm_packed {
/* NONE is not actually used by NML3IPv6LL. This is a bogus placeholder
* state for external users. */
NM_L3_IPV6LL_STATE_NONE,
NM_L3_IPV6LL_STATE_STARTING,
NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS,
NM_L3_IPV6LL_STATE_READY,
NM_L3_IPV6LL_STATE_DAD_FAILED,
} NML3IPv6LLState;
const char *nm_l3_ipv6ll_state_to_string(NML3IPv6LLState state);
typedef void (*NML3IPv6LLNotifyFcn)(NML3IPv6LL * ipv6ll,
NML3IPv6LLState state,
const struct in6_addr *lladdr,
gpointer user_data);
static inline gboolean
NM_IS_L3_IPV6LL(const NML3IPv6LL *self)
{
nm_assert(!self || (NM_IS_L3CFG(*((NML3Cfg **) self))));
return !!self;
}
NML3IPv6LL *_nm_l3_ipv6ll_new(NML3Cfg * l3cfg,
gboolean assume,
NMUtilsStableType stable_type,
const char * ifname,
const char * network_id,
const NMUtilsIPv6IfaceId *token_iid,
NML3IPv6LLNotifyFcn notify_fcn,
gpointer user_data);
static inline NML3IPv6LL *
nm_l3_ipv6ll_new_stable_privacy(NML3Cfg * l3cfg,
gboolean assume,
NMUtilsStableType stable_type,
const char * ifname,
const char * network_id,
NML3IPv6LLNotifyFcn notify_fcn,
gpointer user_data)
{
nm_assert(stable_type != NM_UTILS_STABLE_TYPE_NONE);
return _nm_l3_ipv6ll_new(l3cfg,
assume,
stable_type,
ifname,
network_id,
NULL,
notify_fcn,
user_data);
}
static inline NML3IPv6LL *
nm_l3_ipv6ll_new_token(NML3Cfg * l3cfg,
gboolean assume,
const NMUtilsIPv6IfaceId *token_iid,
NML3IPv6LLNotifyFcn notify_fcn,
gpointer user_data)
{
return _nm_l3_ipv6ll_new(l3cfg,
assume,
NM_UTILS_STABLE_TYPE_NONE,
NULL,
NULL,
token_iid,
notify_fcn,
user_data);
}
void nm_l3_ipv6ll_destroy(NML3IPv6LL *self);
NM_AUTO_DEFINE_FCN0(NML3IPv6LL *, _nm_auto_destroy_l3ipv6ll, nm_l3_ipv6ll_destroy);
#define nm_auto_destroy_l3ipv6ll nm_auto(_nm_auto_destroy_l3ipv6ll)
/*****************************************************************************/
NML3Cfg *nm_l3_ipv6ll_get_l3cfg(NML3IPv6LL *self);
int nm_l3_ipv6ll_get_ifindex(NML3IPv6LL *self);
NMPlatform *nm_l3_ipv6ll_get_platform(NML3IPv6LL *self);
/*****************************************************************************/
NML3IPv6LLState nm_l3_ipv6ll_get_state(NML3IPv6LL *self, const struct in6_addr **out_lladdr);
const NML3ConfigData *nm_l3_ipv6ll_get_l3cd(NML3IPv6LL *self);
/*****************************************************************************/
#endif /* __NM_L3_IPV6LL_H__ */

View File

@ -3806,7 +3806,7 @@ nm_l3cfg_commit_type_get(NML3Cfg *self)
* a certain @commit_type. The "higher" commit type is the used one when calling
* nm_l3cfg_commit() with %NM_L3_CFG_COMMIT_TYPE_AUTO.
*
* Returns: a handle tracking the registration, or %NULL of @commit_type
* Returns: a handle tracking the registration, or %NULL if @commit_type
* is %NM_L3_CFG_COMMIT_TYPE_NONE.
*/
NML3CfgCommitTypeHandle *

View File

@ -7,6 +7,7 @@
#include "nm-l3-config-data.h"
#define NM_L3CFG_CONFIG_PRIORITY_IPV4LL 0
#define NM_L3CFG_CONFIG_PRIORITY_IPV6LL 0
#define NM_ACD_TIMEOUT_RFC5227_MSEC 9000u
#define NM_TYPE_L3CFG (nm_l3cfg_get_type())

View File

@ -2,8 +2,11 @@
#include "src/core/nm-default-daemon.h"
#include <linux/if_addr.h>
#include "nm-l3cfg.h"
#include "nm-l3-ipv4ll.h"
#include "nm-l3-ipv6ll.h"
#include "nm-netns.h"
#include "libnm-platform/nm-platform.h"
@ -780,6 +783,276 @@ test_l3_ipv4ll(gconstpointer test_data)
/*****************************************************************************/
#define _LLADDR_TEST1 "fe80::dd5a:8a44:48bc:3ad"
#define _LLADDR_TEST2 "fe80::878b:938e:46f9:4807"
typedef struct {
const TestFixture1 *f;
NML3Cfg * l3cfg0;
NML3IPv6LL * l3ipv6ll;
int step;
int ipv6ll_callback_step;
bool steps_done : 1;
const NMPObject * lladdr0;
} TestL3IPv6LLData;
static const NMPlatformIP6Address *
_test_l3_ipv6ll_find_lladdr(TestL3IPv6LLData *tdata, int ifindex)
{
const NMPlatformIP6Address *found = NULL;
NMDedupMultiIter iter;
const NMPObject * obj;
NMPLookup lookup;
g_assert(tdata);
nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex);
nm_platform_iter_obj_for_each (&iter, tdata->f->platform, &lookup, &obj) {
const NMPlatformIP6Address *a = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
if (!IN6_IS_ADDR_LINKLOCAL(&a->address))
continue;
if (!found)
found = a;
else
g_assert_not_reached();
}
return found;
}
static const NMPObject *
_test_l3_ipv6ll_find_lladdr_wait(TestL3IPv6LLData *tdata, int ifindex)
{
const NMPObject *obj = NULL;
nmtst_main_context_iterate_until_assert(NULL, 3000, ({
const NMPlatformIP6Address *a;
a = _test_l3_ipv6ll_find_lladdr(tdata, ifindex);
if (a
&& !NM_FLAGS_HAS(a->n_ifa_flags,
IFA_F_TENTATIVE))
obj = NMP_OBJECT_UP_CAST(a);
obj;
}));
return obj;
}
static const NMPlatformIP6Address *
_test_l3_ipv6ll_find_inet6(TestL3IPv6LLData *tdata, const struct in6_addr *addr)
{
const NMPlatformIP6Address *a;
a = nmtstp_platform_ip6_address_find(nm_l3cfg_get_platform(tdata->l3cfg0),
nmtst_get_rand_bool() ? 0 : tdata->f->ifindex0,
addr);
if (a) {
g_assert_cmpint(a->ifindex, ==, tdata->f->ifindex0);
g_assert_cmpmem(addr, sizeof(*addr), &a->address, sizeof(a->address));
}
g_assert(a
== nm_platform_ip6_address_get(nm_l3cfg_get_platform(tdata->l3cfg0),
tdata->f->ifindex0,
addr));
return a;
}
static void
_test_l3_ipv6ll_signal_notify(NML3Cfg * l3cfg,
const NML3ConfigNotifyData *notify_data,
TestL3IPv6LLData * tdata)
{
g_assert_cmpint(tdata->step, >=, 1);
g_assert_cmpint(tdata->step, <=, 2);
}
static void
_test_l3_ipv6ll_callback_changed(NML3IPv6LL * ipv6ll,
NML3IPv6LLState state,
const struct in6_addr *lladdr,
gpointer user_data)
{
TestL3IPv6LLData * tdata = user_data;
int step = tdata->ipv6ll_callback_step++;
const NMPlatformIP6Address *a1;
g_assert_cmpint(tdata->step, ==, 1);
g_assert(!tdata->steps_done);
switch (step) {
case 0:
if (NM_IN_SET(tdata->f->test_idx, 1, 2, 4)) {
g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS);
g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST1);
} else if (NM_IN_SET(tdata->f->test_idx, 3)) {
g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY);
g_assert(
IN6_ARE_ADDR_EQUAL(lladdr, &NMP_OBJECT_CAST_IP6_ADDRESS(tdata->lladdr0)->address));
tdata->steps_done = TRUE;
} else
g_assert_not_reached();
break;
case 1:
if (NM_IN_SET(tdata->f->test_idx, 1, 2)) {
g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY);
g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST1);
a1 = _test_l3_ipv6ll_find_inet6(tdata, lladdr);
g_assert(a1);
g_assert(!NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE));
tdata->steps_done = TRUE;
} else if (NM_IN_SET(tdata->f->test_idx, 4)) {
g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS);
g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST2);
} else
g_assert_not_reached();
break;
case 2:
if (NM_IN_SET(tdata->f->test_idx, 4)) {
g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY);
g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST2);
a1 = _test_l3_ipv6ll_find_inet6(tdata, lladdr);
g_assert(a1);
g_assert(!NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE));
tdata->steps_done = TRUE;
} else
g_assert_not_reached();
break;
default:
g_assert_not_reached();
}
}
static void
test_l3_ipv6ll(gconstpointer test_data)
{
NMTST_UTILS_HOST_ID_CONTEXT("l3-ipv6ll");
const int TEST_IDX = GPOINTER_TO_INT(test_data);
nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {};
gs_unref_object NML3Cfg *l3cfg0 = NULL;
TestL3IPv6LLData tdata_stack = {
.step = 0,
.steps_done = FALSE,
};
TestL3IPv6LLData *const tdata = &tdata_stack;
char sbuf1[sizeof(_nm_utils_to_string_buffer)];
int r;
_LOGD("test start (/l3-ipv6ll/%d)", TEST_IDX);
if (nmtst_test_quick()) {
gs_free char *msg =
g_strdup_printf("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n",
g_get_prgname() ?: "test-l3-ipv6ll");
g_test_skip(msg);
return;
}
tdata->f = _test_fixture_1_setup(&test_fixture, TEST_IDX);
if (NM_IN_SET(tdata->f->test_idx, 4)) {
_LOGD("add conflicting IPv6LL on other interface...");
r = nm_platform_link_change_flags(tdata->f->platform, tdata->f->ifindex1, IFF_UP, FALSE);
g_assert_cmpint(r, >=, 0);
r = nm_platform_link_set_inet6_addr_gen_mode(tdata->f->platform,
tdata->f->ifindex1,
NM_IN6_ADDR_GEN_MODE_NONE);
g_assert_cmpint(r, >=, 0);
r = nm_platform_link_change_flags(tdata->f->platform, tdata->f->ifindex1, IFF_UP, TRUE);
g_assert_cmpint(r, >=, 0);
nmtstp_ip6_address_add(tdata->f->platform,
-1,
tdata->f->ifindex1,
*nmtst_inet6_from_string(_LLADDR_TEST1),
64,
in6addr_any,
NM_PLATFORM_LIFETIME_PERMANENT,
NM_PLATFORM_LIFETIME_PERMANENT,
0);
_LOGD("wait for IPv6 LL address...");
tdata->lladdr0 =
nmp_object_ref(_test_l3_ipv6ll_find_lladdr_wait(tdata, tdata->f->ifindex1));
} else if (NM_IN_SET(tdata->f->test_idx, 2, 3)) {
_LOGD("wait for IPv6 LL address...");
tdata->lladdr0 =
nmp_object_ref(_test_l3_ipv6ll_find_lladdr_wait(tdata, tdata->f->ifindex0));
}
if (tdata->lladdr0) {
_LOGD("got IPv6 LL address %s",
nmp_object_to_string(tdata->lladdr0,
NMP_OBJECT_TO_STRING_PUBLIC,
sbuf1,
sizeof(sbuf1)));
}
l3cfg0 = _netns_access_l3cfg(tdata->f->netns, tdata->f->ifindex0);
tdata->l3cfg0 = l3cfg0;
g_signal_connect(tdata->l3cfg0,
NM_L3CFG_SIGNAL_NOTIFY,
G_CALLBACK(_test_l3_ipv6ll_signal_notify),
tdata);
tdata->l3ipv6ll = nm_l3_ipv6ll_new_stable_privacy(tdata->l3cfg0,
NM_IN_SET(tdata->f->test_idx, 3),
NM_UTILS_STABLE_TYPE_UUID,
tdata->f->ifname0,
"b6a5b934-c649-43dc-a524-3dfdb74f9419",
_test_l3_ipv6ll_callback_changed,
tdata);
g_assert(nm_l3_ipv6ll_get_l3cfg(tdata->l3ipv6ll) == tdata->l3cfg0);
g_assert_cmpint(nm_l3_ipv6ll_get_ifindex(tdata->l3ipv6ll), ==, tdata->f->ifindex0);
tdata->step = 1;
nmtst_main_context_iterate_until_assert(NULL, 7000, tdata->steps_done);
g_assert_cmpint(tdata->step, ==, 1);
if (NM_IN_SET(tdata->f->test_idx, 3))
g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 1);
else if (NM_IN_SET(tdata->f->test_idx, 4))
g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 3);
else
g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 2);
g_assert(tdata->steps_done);
tdata->step = 2;
nmtst_main_context_iterate_until(NULL, nmtst_get_rand_uint32() % 1000, FALSE);
g_assert_cmpint(tdata->step, ==, 2);
if (NM_IN_SET(tdata->f->test_idx, 3))
g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 1);
else if (NM_IN_SET(tdata->f->test_idx, 4))
g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 3);
else
g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 2);
g_assert(tdata->steps_done);
g_assert(tdata->steps_done);
tdata->step = 0;
tdata->steps_done = FALSE;
g_signal_handlers_disconnect_by_func(tdata->l3cfg0,
G_CALLBACK(_test_l3_ipv6ll_signal_notify),
tdata);
nm_l3_ipv6ll_destroy(tdata->l3ipv6ll);
nm_clear_nmp_object(&tdata->lladdr0);
}
/*****************************************************************************/
NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup;
void
@ -797,4 +1070,8 @@ _nmtstp_setup_tests(void)
g_test_add_data_func("/l3cfg/4", GINT_TO_POINTER(4), test_l3cfg);
g_test_add_data_func("/l3-ipv4ll/1", GINT_TO_POINTER(1), test_l3_ipv4ll);
g_test_add_data_func("/l3-ipv4ll/2", GINT_TO_POINTER(2), test_l3_ipv4ll);
g_test_add_data_func("/l3-ipv6ll/1", GINT_TO_POINTER(1), test_l3_ipv6ll);
g_test_add_data_func("/l3-ipv6ll/2", GINT_TO_POINTER(2), test_l3_ipv6ll);
g_test_add_data_func("/l3-ipv6ll/3", GINT_TO_POINTER(3), test_l3_ipv6ll);
g_test_add_data_func("/l3-ipv6ll/4", GINT_TO_POINTER(4), test_l3_ipv6ll);
}