summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2021-09-10 13:27:15 +0200
committerThomas Haller <thaller@redhat.com>2021-09-10 13:27:15 +0200
commit3a6b3e35da3551d70f8094872d79527760210db7 (patch)
tree37467c97418b43ac80139836e620eda864eda6da
parent2d828bdbf95f4b28f6cbf40d8b8f18c14ca69391 (diff)
parentaa070fb82190cda05cb77082d5f67709010ff5d4 (diff)
downloadNetworkManager-3a6b3e35da3551d70f8094872d79527760210db7.tar.gz
l3cfg: merge branch 'th/l3cfg-ipv6ll'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/976
-rw-r--r--Makefile.am2
-rw-r--r--src/core/meson.build1
-rw-r--r--src/core/ndisc/nm-ndisc.h4
-rw-r--r--src/core/nm-core-utils.c75
-rw-r--r--src/core/nm-core-utils.h29
-rw-r--r--src/core/nm-l3-config-data.c93
-rw-r--r--src/core/nm-l3-config-data.h33
-rw-r--r--src/core/nm-l3-ipv4ll.c3
-rw-r--r--src/core/nm-l3-ipv6ll.c713
-rw-r--r--src/core/nm-l3-ipv6ll.h106
-rw-r--r--src/core/nm-l3cfg.c963
-rw-r--r--src/core/nm-l3cfg.h33
-rw-r--r--src/core/settings/nm-settings-connection.c2
-rw-r--r--src/core/settings/nm-settings.c2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c2
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c2
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-storage.c2
-rw-r--r--src/core/tests/test-ip6-config.c12
-rw-r--r--src/core/tests/test-l3cfg.c279
-rw-r--r--src/libnm-core-impl/nm-connection.c32
-rw-r--r--src/libnm-core-impl/nm-setting-private.h2
-rw-r--r--src/libnm-core-impl/nm-simple-connection.c2
-rw-r--r--src/libnm-core-impl/tests/test-setting.c2
-rw-r--r--src/libnm-core-intern/nm-core-internal.h6
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.h23
-rw-r--r--src/libnm-glib-aux/nm-test-utils.h13
-rw-r--r--src/libnm-platform/nm-platform.c68
-rw-r--r--src/libnm-platform/nm-platform.h18
-rw-r--r--src/libnm-platform/nmp-object.h15
29 files changed, 2095 insertions, 442 deletions
diff --git a/Makefile.am b/Makefile.am
index 32e88ee480..22634626d0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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 \
diff --git a/src/core/meson.build b/src/core/meson.build
index 1a3f334fd8..46b636817d 100644
--- a/src/core/meson.build
+++ b/src/core/meson.build
@@ -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',
diff --git a/src/core/ndisc/nm-ndisc.h b/src/core/ndisc/nm-ndisc.h
index 968f739ee2..5b43472f6c 100644
--- a/src/core/ndisc/nm-ndisc.h
+++ b/src/core/ndisc/nm-ndisc.h
@@ -241,7 +241,7 @@ static inline gboolean
nm_ndisc_dad_addr_is_fail_candidate_event(NMPlatformSignalChangeType change_type,
const NMPlatformIP6Address *addr)
{
- return !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TEMPORARY)
+ return !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_SECONDARY)
&& ((change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->n_ifa_flags & IFA_F_DADFAILED)
|| (change_type == NM_PLATFORM_SIGNAL_REMOVED
&& addr->n_ifa_flags & IFA_F_TENTATIVE));
@@ -255,7 +255,7 @@ nm_ndisc_dad_addr_is_fail_candidate(NMPlatform *platform, const NMPObject *obj)
addr = NMP_OBJECT_CAST_IP6_ADDRESS(
nm_platform_lookup_obj(platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj));
if (addr
- && (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TEMPORARY)
+ && (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_SECONDARY)
|| !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_DADFAILED))) {
/* the address still/again exists and is not in DADFAILED state. Skip it. */
return FALSE;
diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c
index 2e727e6a1f..747e1c0920 100644
--- a/src/core/nm-core-utils.c
+++ b/src/core/nm-core-utils.c
@@ -2877,10 +2877,11 @@ typedef struct {
bool timestamp_is_good : 1;
} HostIdData;
+static const HostIdData *volatile host_id_static;
+
static const HostIdData *
_host_id_get(void)
{
- static const HostIdData *volatile host_id_static;
const HostIdData *host_id;
again:
@@ -2941,6 +2942,78 @@ nm_utils_host_id_get_timestamp_ns(void)
return _host_id_get()->timestamp_ns;
}
+static GArray * nmtst_host_id_stack = NULL;
+static GMutex nmtst_host_id_lock;
+const HostIdData *nmtst_host_id_static_0 = NULL;
+
+void
+nmtst_utils_host_id_push(const guint8 *host_id,
+ gssize host_id_len,
+ gboolean is_good,
+ const gint64 *timestamp_ns)
+{
+ NM_G_MUTEX_LOCKED(&nmtst_host_id_lock);
+ gs_free char *str1_to_free = NULL;
+ HostIdData * h;
+
+ g_assert(host_id_len >= -1);
+
+ if (host_id_len < 0)
+ host_id_len = host_id ? strlen((const char *) host_id) : 0;
+
+ nm_log_dbg(LOGD_CORE,
+ "nmtst: host-id push: \"%s\" (%zu), is-good=%d, timestamp=%" G_GINT64_FORMAT "%s",
+ nm_utils_buf_utf8safe_escape(host_id,
+ host_id_len,
+ NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
+ &str1_to_free),
+ (gsize) host_id_len,
+ !!is_good,
+ timestamp_ns ? *timestamp_ns : 0,
+ timestamp_ns ? "" : " (not-good)");
+
+ if (!nmtst_host_id_stack) {
+ nmtst_host_id_stack = g_array_new(FALSE, FALSE, sizeof(HostIdData));
+ nmtst_host_id_static_0 = g_atomic_pointer_get(&host_id_static);
+ }
+
+ h = nm_g_array_append_new(nmtst_host_id_stack, HostIdData);
+
+ *h = (HostIdData){
+ .host_id = nm_memdup(host_id, host_id_len),
+ .host_id_len = host_id_len,
+ .timestamp_ns = timestamp_ns ? *timestamp_ns : 0,
+ .is_good = is_good,
+ .timestamp_is_good = !!timestamp_ns,
+ };
+
+ g_atomic_pointer_set(&host_id_static, h);
+}
+
+void
+nmtst_utils_host_id_pop(void)
+{
+ NM_G_MUTEX_LOCKED(&nmtst_host_id_lock);
+ HostIdData *h;
+
+ g_assert(nmtst_host_id_stack);
+ g_assert(nmtst_host_id_stack->len > 0);
+
+ nm_log_dbg(LOGD_CORE, "nmtst: host-id pop");
+
+ h = &g_array_index(nmtst_host_id_stack, HostIdData, nmtst_host_id_stack->len - 1);
+
+ g_free((char *) h->host_id);
+ g_array_set_size(nmtst_host_id_stack, nmtst_host_id_stack->len - 1u);
+
+ if (!g_atomic_pointer_compare_and_exchange(
+ &host_id_static,
+ h,
+ nmtst_host_id_stack->len == 0u ? nmtst_host_id_static_0
+ : nm_g_array_last(nmtst_host_id_stack, HostIdData)))
+ g_assert_not_reached();
+}
+
/*****************************************************************************/
static const UuidData *
diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h
index a1fa8830e9..38f56adc08 100644
--- a/src/core/nm-core-utils.h
+++ b/src/core/nm-core-utils.h
@@ -247,6 +247,33 @@ const char *const * nm_utils_proc_cmdline_split(void);
gboolean nm_utils_host_id_get(const guint8 **out_host_id, gsize *out_host_id_len);
gint64 nm_utils_host_id_get_timestamp_ns(void);
+void nmtst_utils_host_id_push(const guint8 *host_id,
+ gssize host_id_len,
+ gboolean is_good,
+ const gint64 *timestamp_ns);
+
+void nmtst_utils_host_id_pop(void);
+
+static inline void
+_nmtst_auto_utils_host_id_context_pop(const char *const *unused)
+{
+ nmtst_utils_host_id_pop();
+}
+
+#define _NMTST_UTILS_HOST_ID_CONTEXT(uniq, host_id) \
+ _nm_unused nm_auto(_nmtst_auto_utils_host_id_context_pop) \
+ const char *const NM_UNIQ_T(_host_id_context_, uniq) = ({ \
+ const gint64 _timestamp_ns = 1631000672; \
+ \
+ nmtst_utils_host_id_push((const guint8 *) "" host_id "", \
+ NM_STRLEN(host_id), \
+ TRUE, \
+ &_timestamp_ns); \
+ "" host_id ""; \
+ })
+
+#define NMTST_UTILS_HOST_ID_CONTEXT(host_id) _NMTST_UTILS_HOST_ID_CONTEXT(NM_UNIQ, host_id)
+
/*****************************************************************************/
int nm_utils_arp_type_detect_from_hwaddrlen(gsize hwaddr_len);
@@ -271,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,
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index 4af73b34e6..3ae17d5217 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -1104,6 +1104,14 @@ _l3_config_data_add_obj(NMDedupMultiIndex * multi_idx,
obj_new_stackinit.ip_address.addr_source = obj_old->ip_address.addr_source;
modified = TRUE;
}
+
+ /* OR assume_config_once flag */
+ if (obj_new->ip_address.a_assume_config_once
+ && !obj_old->ip_address.a_assume_config_once) {
+ obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new);
+ obj_new_stackinit.ip_address.a_assume_config_once = TRUE;
+ modified = TRUE;
+ }
break;
case NMP_OBJECT_TYPE_IP4_ROUTE:
case NMP_OBJECT_TYPE_IP6_ROUTE:
@@ -1113,6 +1121,14 @@ _l3_config_data_add_obj(NMDedupMultiIndex * multi_idx,
obj_new_stackinit.ip_route.rt_source = obj_old->ip_route.rt_source;
modified = TRUE;
}
+
+ /* OR assume_config_once flag */
+ if (obj_new->ip_route.r_assume_config_once
+ && !obj_old->ip_route.r_assume_config_once) {
+ obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new);
+ obj_new_stackinit.ip_route.r_assume_config_once = TRUE;
+ modified = TRUE;
+ }
break;
default:
nm_assert_not_reached();
@@ -2696,7 +2712,7 @@ nm_l3_config_data_merge(NML3ConfigData * self,
const guint32 * default_route_table_x /* length 2, for IS_IPv4 */,
const guint32 * default_route_metric_x /* length 2, for IS_IPv4 */,
const guint32 * default_route_penalty_x /* length 2, for IS_IPv4 */,
- NML3ConfigMergeHookAddObj hook_add_addr,
+ NML3ConfigMergeHookAddObj hook_add_obj,
gpointer hook_user_data)
{
static const guint32 x_default_route_table_x[2] = {RT_TABLE_MAIN, RT_TABLE_MAIN};
@@ -2733,36 +2749,67 @@ nm_l3_config_data_merge(NML3ConfigData * self,
src,
&obj,
NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
- NMPlatformIPXAddress addr_stack;
- const NMPlatformIPAddress *addr = NULL;
- NMTernary ip4acd_not_ready = NM_TERNARY_DEFAULT;
+ const NMPlatformIPAddress *a_src = NMP_OBJECT_CAST_IP_ADDRESS(obj);
+ NMPlatformIPXAddress a;
+ NML3ConfigMergeHookResult hook_result = {
+ .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT,
+ .assume_config_once = NM_OPTION_BOOL_DEFAULT,
+ };
+
+#define _ensure_a() \
+ G_STMT_START \
+ { \
+ if (a_src != &a.ax) { \
+ a_src = &a.ax; \
+ if (IS_IPv4) \
+ a.a4 = *NMP_OBJECT_CAST_IP4_ADDRESS(obj); \
+ else \
+ a.a6 = *NMP_OBJECT_CAST_IP6_ADDRESS(obj); \
+ } \
+ } \
+ G_STMT_END
- if (hook_add_addr && !hook_add_addr(src, obj, &ip4acd_not_ready, hook_user_data))
+ nm_assert(a_src->ifindex == self->ifindex);
+
+ if (hook_add_obj && !hook_add_obj(src, obj, &hook_result, hook_user_data))
continue;
- if (IS_IPv4 && ip4acd_not_ready != NM_TERNARY_DEFAULT
- && (!!ip4acd_not_ready) != NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) {
- addr_stack.a4 = *NMP_OBJECT_CAST_IP4_ADDRESS(obj);
- addr_stack.a4.ip4acd_not_ready = (!!ip4acd_not_ready);
- addr = &addr_stack.ax;
- } else
- nm_assert(IS_IPv4 || ip4acd_not_ready == NM_TERNARY_DEFAULT);
+ nm_assert(IS_IPv4 || hook_result.ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT);
+
+ if (hook_result.ip4acd_not_ready != NM_OPTION_BOOL_DEFAULT && IS_IPv4
+ && (!!hook_result.ip4acd_not_ready)
+ != ((const NMPlatformIP4Address *) a_src)->a_acd_not_ready) {
+ _ensure_a();
+ a.a4.a_acd_not_ready = (!!hook_result.ip4acd_not_ready);
+ }
+
+ if (hook_result.assume_config_once != NM_OPTION_BOOL_DEFAULT
+ && (!!hook_result.assume_config_once) != a_src->a_assume_config_once) {
+ _ensure_a();
+ a.ax.a_assume_config_once = (!!hook_result.assume_config_once);
+ }
nm_l3_config_data_add_address_full(self,
addr_family,
- addr ? NULL : obj,
- addr,
+ a_src == &a.ax ? NULL : obj,
+ a_src == &a.ax ? a_src : NULL,
NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE,
NULL);
}
+#undef _ensure_a
+
if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES)) {
nm_l3_config_data_iter_obj_for_each (&iter,
src,
&obj,
NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
- const NMPlatformIPRoute *r_src = NMP_OBJECT_CAST_IP_ROUTE(obj);
- NMPlatformIPXRoute r;
+ const NMPlatformIPRoute * r_src = NMP_OBJECT_CAST_IP_ROUTE(obj);
+ NMPlatformIPXRoute r;
+ NML3ConfigMergeHookResult hook_result = {
+ .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT,
+ .assume_config_once = NM_OPTION_BOOL_DEFAULT,
+ };
#define _ensure_r() \
G_STMT_START \
@@ -2773,11 +2820,23 @@ nm_l3_config_data_merge(NML3ConfigData * self,
r.r4 = *NMP_OBJECT_CAST_IP4_ROUTE(obj); \
else \
r.r6 = *NMP_OBJECT_CAST_IP6_ROUTE(obj); \
- r.rx.ifindex = self->ifindex; \
} \
} \
G_STMT_END
+ nm_assert(r_src->ifindex == self->ifindex);
+
+ if (hook_add_obj && !hook_add_obj(src, obj, &hook_result, hook_user_data))
+ continue;
+
+ nm_assert(hook_result.ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT);
+
+ if (hook_result.assume_config_once != NM_OPTION_BOOL_DEFAULT
+ && (!!hook_result.assume_config_once) != r_src->r_assume_config_once) {
+ _ensure_r();
+ r.rx.r_assume_config_once = (!!hook_result.assume_config_once);
+ }
+
if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) {
if (r_src->table_any) {
_ensure_r();
diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h
index 0ea5bd0e69..b2f36b3ed1 100644
--- a/src/core/nm-l3-config-data.h
+++ b/src/core/nm-l3-config-data.h
@@ -52,15 +52,6 @@ typedef enum {
/**
* NML3ConfigMergeFlags:
* @NM_L3_CONFIG_MERGE_FLAGS_NONE: no flags set
- * @NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD: if this merge flag is set,
- * the the NML3ConfigData doesn't get merged and it's information won't be
- * synced. The only purpose is to run ACD on its IPv4 addresses, but
- * regardless whether ACD succeeds/fails, the IP addresses won't be configured.
- * The point is to run ACD first (without configuring it), and only
- * commit the settings if requested. That can either happen by
- * nm_l3cfg_add_config() the same NML3Cfg again (with a different
- * tag), or by calling nm_l3cfg_add_config() again with this flag
- * cleared (and the same tag).
* @NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES: don't merge routes
* @NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES: don't merge default routes.
* Note that if the respective NML3ConfigData has NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES
@@ -71,11 +62,10 @@ typedef enum {
*/
typedef enum _nm_packed {
NM_L3_CONFIG_MERGE_FLAGS_NONE = 0,
- NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD = (1LL << 0),
- NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES = (1LL << 1),
- NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES = (1LL << 2),
- NM_L3_CONFIG_MERGE_FLAGS_NO_DNS = (1LL << 3),
- NM_L3_CONFIG_MERGE_FLAGS_CLONE = (1LL << 4),
+ NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES = (1LL << 0),
+ NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES = (1LL << 1),
+ NM_L3_CONFIG_MERGE_FLAGS_NO_DNS = (1LL << 2),
+ NM_L3_CONFIG_MERGE_FLAGS_CLONE = (1LL << 3),
} NML3ConfigMergeFlags;
/*****************************************************************************/
@@ -145,10 +135,15 @@ NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex * mu
NMPlatform * platform,
NMSettingIP6ConfigPrivacy ipv6_privacy_rfc4941);
-typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData *l3cd,
- const NMPObject * obj,
- NMTernary * out_ip4acd_not_ready,
- gpointer user_data);
+typedef struct {
+ NMOptionBool ip4acd_not_ready;
+ NMOptionBool assume_config_once;
+} NML3ConfigMergeHookResult;
+
+typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData * l3cd,
+ const NMPObject * obj,
+ NML3ConfigMergeHookResult *result,
+ gpointer user_data);
void nm_l3_config_data_merge(NML3ConfigData * self,
const NML3ConfigData *src,
@@ -156,7 +151,7 @@ void nm_l3_config_data_merge(NML3ConfigData * self,
const guint32 *default_route_table_x /* length 2, for IS_IPv4 */,
const guint32 *default_route_metric_x /* length 2, for IS_IPv4 */,
const guint32 *default_route_penalty_x /* length 2, for IS_IPv4 */,
- NML3ConfigMergeHookAddObj hook_add_addr,
+ NML3ConfigMergeHookAddObj hook_add_obj,
gpointer hook_user_data);
GPtrArray *nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self,
diff --git a/src/core/nm-l3-ipv4ll.c b/src/core/nm-l3-ipv4ll.c
index 2df87852bb..521ff5ef24 100644
--- a/src/core/nm-l3-ipv4ll.c
+++ b/src/core/nm-l3-ipv4ll.c
@@ -593,7 +593,8 @@ _l3cd_config_add(NML3IPv4LL *self)
0,
NM_L3_ACD_DEFEND_TYPE_ONCE,
self->reg_timeout_msec,
- NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD))
+ NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD,
+ NM_L3_CONFIG_MERGE_FLAGS_NONE))
nm_assert_not_reached();
self->l3cfg_commit_handle = nm_l3cfg_commit_type_register(self->l3cfg,
diff --git a/src/core/nm-l3-ipv6ll.c b/src/core/nm-l3-ipv6ll.c
new file mode 100644
index 0000000000..05149e66a6
--- /dev/null
+++ b/src/core/nm-l3-ipv6ll.c
@@ -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);
+}
diff --git a/src/core/nm-l3-ipv6ll.h b/src/core/nm-l3-ipv6ll.h
new file mode 100644
index 0000000000..8125b5d06a
--- /dev/null
+++ b/src/core/nm-l3-ipv6ll.h
@@ -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__ */
diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c
index 4f661aa060..1b9eadea32 100644
--- a/src/core/nm-l3cfg.c
+++ b/src/core/nm-l3cfg.c
@@ -18,6 +18,17 @@
/*****************************************************************************/
+#define ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC ((gint64) 20000)
+
+/* When a ObjStateData becomes a "zombie", we aim to delete it from platform
+ * on the next commit (until it disappears from platform). But we might have
+ * a bug, so that we fail to delete the platform (for example, related to
+ * IPv6 multicast routes). We thus rate limit how often we try to do this,
+ * before giving up. */
+#define ZOMBIE_COUNT_START 5
+
+/*****************************************************************************/
+
G_STATIC_ASSERT(NM_ACD_TIMEOUT_RFC5227_MSEC == N_ACD_TIMEOUT_RFC5227);
#define ACD_SUPPORTED_ETH_ALEN ETH_ALEN
@@ -97,6 +108,57 @@ typedef struct {
G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdData, info.addr) == 0);
+typedef struct {
+ const NMPObject *obj;
+
+ CList os_lst;
+
+ /* If we have a timeout pending, we link the instance to
+ * self->priv.p->obj_state_temporary_not_available_lst_head. */
+ CList os_temporary_not_available_lst;
+
+ /* If a NMPObject is no longer to be configured (but was configured
+ * during a previous commit), then we need to remember it so that the
+ * next commit can delete the address/route in kernel. It becomes a zombie. */
+ CList os_zombie_lst;
+
+ /* We might want to configure "obj" in platform, but it's currently not possible.
+ * For example, certain IPv6 routes can only be added after the IPv6 address
+ * becomes non-tentative (*sigh*). In such a case, we need to remember that, and
+ * retry later. If this timestamp is set to a non-zero value, then it means
+ * we tried to configure the obj (at that timestamp) and failed, but we are
+ * waiting to retry.
+ *
+ * See also self->priv.p->obj_state_temporary_not_available_lst_head
+ * and self->priv.p->obj_state_temporary_not_available_timeout_source. */
+ gint64 os_temporary_not_available_timestamp_msec;
+
+ /* When the obj is a zombie (that means, it was previously configured by NML3Cfg, but
+ * now no longer), it needs to be deleted from platform. This ratelimits the time
+ * how often we try that. When the counter reaches zero, we forget about it. */
+ guint8 os_zombie_count;
+
+ /* whether obj is currently in the platform cache or not.
+ * Since "obj" is the NMPObject from the merged NML3ConfigData,
+ * the object in platform has the same ID (but may otherwise not
+ * be identical). */
+ bool os_in_platform : 1;
+
+ /* whether we ever saw the object in platform. */
+ bool os_was_in_platform : 1;
+
+ /* Indicates whether NetworkManager actively tried to configure the object
+ * in platform once. */
+ bool os_nm_configured : 1;
+
+ /* This flag is only used temporarily to do a bulk update and
+ * clear all the ones that are no longer in used. */
+ bool os_dirty : 1;
+ bool os_tna_dirty : 1;
+} ObjStateData;
+
+G_STATIC_ASSERT(G_STRUCT_OFFSET(ObjStateData, obj) == 0);
+
struct _NML3CfgCommitTypeHandle {
CList commit_type_lst;
NML3CfgCommitType commit_type;
@@ -104,6 +166,7 @@ struct _NML3CfgCommitTypeHandle {
typedef struct {
const NML3ConfigData *l3cd;
+ NML3CfgConfigFlags config_flags;
NML3ConfigMergeFlags merge_flags;
union {
struct {
@@ -157,9 +220,11 @@ typedef struct _NML3CfgPrivate {
CList commit_type_lst_head;
- GHashTable *routes_temporary_not_available_hash;
+ GHashTable *obj_state_hash;
- GHashTable *externally_removed_objs_hash;
+ CList obj_state_lst_head;
+ CList obj_state_zombie_lst_head;
+ CList obj_state_temporary_not_available_lst_head;
GHashTable *acd_ipv4_addresses_on_link;
@@ -181,39 +246,7 @@ typedef struct _NML3CfgPrivate {
guint64 pseudo_timestamp_counter;
- union {
- struct {
- guint externally_removed_objs_cnt_addresses_6;
- guint externally_removed_objs_cnt_addresses_4;
- };
- guint externally_removed_objs_cnt_addresses_x[2];
- };
-
- union {
- struct {
- guint externally_removed_objs_cnt_routes_6;
- guint externally_removed_objs_cnt_routes_4;
- };
- guint externally_removed_objs_cnt_routes_x[2];
- };
-
- union {
- struct {
- GPtrArray *last_addresses_6;
- GPtrArray *last_addresses_4;
- };
- GPtrArray *last_addresses_x[2];
- };
-
- union {
- struct {
- GPtrArray *last_routes_6;
- GPtrArray *last_routes_4;
- };
- GPtrArray *last_routes_x[2];
- };
-
- guint routes_temporary_not_available_id;
+ GSource *obj_state_temporary_not_available_timeout_source;
gint8 commit_reentrant_count;
@@ -553,157 +586,436 @@ _nm_n_acd_data_probe_new(NML3Cfg *self, in_addr_t addr, guint32 timeout_msec, gp
/*****************************************************************************/
-static guint *
-_l3cfg_externally_removed_objs_counter(NML3Cfg *self, NMPObjectType obj_type)
+#define nm_assert_obj_state(self, obj_state) \
+ G_STMT_START \
+ { \
+ const NML3Cfg * _self = (self); \
+ const ObjStateData *_obj_state = (obj_state); \
+ \
+ nm_assert(_obj_state); \
+ nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj), \
+ NMP_OBJECT_TYPE_IP4_ADDRESS, \
+ NMP_OBJECT_TYPE_IP6_ADDRESS, \
+ NMP_OBJECT_TYPE_IP4_ROUTE, \
+ NMP_OBJECT_TYPE_IP6_ROUTE)); \
+ nm_assert(!_obj_state->os_in_platform || _obj_state->os_was_in_platform); \
+ nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0) \
+ == c_list_is_empty(&_obj_state->os_temporary_not_available_lst)); \
+ if (_self) { \
+ nm_assert(_self->priv.p->combined_l3cd_commited); \
+ \
+ if (NM_MORE_ASSERTS > 5) { \
+ nm_assert( \
+ c_list_contains(&_self->priv.p->obj_state_lst_head, &_obj_state->os_lst)); \
+ nm_assert( \
+ (_obj_state->os_temporary_not_available_timestamp_msec == 0) \
+ || c_list_contains(&_self->priv.p->obj_state_temporary_not_available_lst_head, \
+ &_obj_state->os_temporary_not_available_lst)); \
+ nm_assert(_obj_state->os_in_platform \
+ == (!!nm_platform_lookup_entry(_self->priv.platform, \
+ NMP_CACHE_ID_TYPE_OBJECT_TYPE, \
+ _obj_state->obj))); \
+ nm_assert( \
+ c_list_is_empty(&obj_state->os_zombie_lst) \
+ ? (_obj_state->obj \
+ == nm_dedup_multi_entry_get_obj( \
+ nm_l3_config_data_lookup_obj(_self->priv.p->combined_l3cd_commited, \
+ _obj_state->obj))) \
+ : (!nm_l3_config_data_lookup_obj(_self->priv.p->combined_l3cd_commited, \
+ _obj_state->obj))); \
+ } \
+ } \
+ } \
+ G_STMT_END
+
+static gboolean
+_obj_state_data_get_assume_config_once(const ObjStateData *obj_state)
{
- switch (obj_type) {
- case NMP_OBJECT_TYPE_IP4_ADDRESS:
- return &self->priv.p->externally_removed_objs_cnt_addresses_4;
- case NMP_OBJECT_TYPE_IP6_ADDRESS:
- return &self->priv.p->externally_removed_objs_cnt_addresses_6;
- case NMP_OBJECT_TYPE_IP4_ROUTE:
- return &self->priv.p->externally_removed_objs_cnt_routes_4;
- case NMP_OBJECT_TYPE_IP6_ROUTE:
- return &self->priv.p->externally_removed_objs_cnt_routes_6;
- default:
- return nm_assert_unreachable_val(NULL);
- }
+ nm_assert_obj_state(NULL, obj_state);
+
+ return nmp_object_get_assume_config_once(obj_state->obj);
+}
+
+static ObjStateData *
+_obj_state_data_new(const NMPObject *obj, gboolean in_platform)
+{
+ ObjStateData *obj_state;
+
+ obj_state = g_slice_new(ObjStateData);
+ *obj_state = (ObjStateData){
+ .obj = nmp_object_ref(obj),
+ .os_in_platform = in_platform,
+ .os_was_in_platform = in_platform,
+ .os_nm_configured = FALSE,
+ .os_dirty = FALSE,
+ .os_temporary_not_available_lst = C_LIST_INIT(obj_state->os_temporary_not_available_lst),
+ .os_zombie_lst = C_LIST_INIT(obj_state->os_zombie_lst),
+ };
+ return obj_state;
}
static void
-_l3cfg_externally_removed_objs_drop(NML3Cfg *self)
+_obj_state_data_free(gpointer data)
{
- nm_assert(NM_IS_L3CFG(self));
+ ObjStateData *obj_state = data;
+
+ c_list_unlink_stale(&obj_state->os_lst);
+ c_list_unlink_stale(&obj_state->os_temporary_not_available_lst);
+ nmp_object_unref(obj_state->obj);
+ nm_g_slice_free(obj_state);
+}
+
+static const char *
+_obj_state_data_to_string(const ObjStateData *obj_state, char *buf, gsize buf_size)
+{
+ const char *buf0 = buf;
+ gint64 now_msec = 0;
- self->priv.p->externally_removed_objs_cnt_addresses_4 = 0;
- self->priv.p->externally_removed_objs_cnt_addresses_6 = 0;
- self->priv.p->externally_removed_objs_cnt_routes_4 = 0;
- self->priv.p->externally_removed_objs_cnt_routes_6 = 0;
- if (nm_g_hash_table_size(self->priv.p->externally_removed_objs_hash) > 0)
- _LOGD("externally-removed: untrack all");
- nm_clear_pointer(&self->priv.p->externally_removed_objs_hash, g_hash_table_unref);
+ nm_assert(buf);
+ nm_assert(buf_size > 0);
+ nm_assert_obj_state(NULL, obj_state);
+
+ nm_strbuf_append(&buf,
+ &buf_size,
+ "[" NM_HASH_OBFUSCATE_PTR_FMT ", %s, ",
+ NM_HASH_OBFUSCATE_PTR(obj_state),
+ NMP_OBJECT_GET_CLASS(obj_state->obj)->obj_type_name);
+
+ nmp_object_to_string(obj_state->obj, NMP_OBJECT_TO_STRING_PUBLIC, buf, buf_size);
+ nm_strbuf_seek_end(&buf, &buf_size);
+ nm_strbuf_append_c(&buf, &buf_size, ']');
+
+ if (!c_list_is_empty(&obj_state->os_zombie_lst))
+ nm_strbuf_append(&buf, &buf_size, ", zombie[%u]", obj_state->os_zombie_count);
+
+ if (obj_state->os_nm_configured)
+ nm_strbuf_append_str(&buf, &buf_size, ", nm-configured");
+
+ if (obj_state->os_in_platform) {
+ nm_assert(obj_state->os_was_in_platform);
+ nm_strbuf_append_str(&buf, &buf_size, ", in-platform");
+ } else if (obj_state->os_was_in_platform)
+ nm_strbuf_append_str(&buf, &buf_size, ", was-in-platform");
+
+ if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
+ nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
+ nm_strbuf_append(
+ &buf,
+ &buf_size,
+ ", temporary-not-available-since=%" G_GINT64_FORMAT ".%03d",
+ (now_msec - obj_state->os_temporary_not_available_timestamp_msec) / 1000,
+ (int) ((now_msec - obj_state->os_temporary_not_available_timestamp_msec) % 1000));
+ }
+
+ return buf0;
}
+static gboolean
+_obj_state_data_update(ObjStateData *obj_state, const NMPObject *obj)
+{
+ gboolean changed = FALSE;
+
+ nm_assert_obj_state(NULL, obj_state);
+ nm_assert(obj);
+ nm_assert(nmp_object_id_equal(obj_state->obj, obj));
+
+ obj_state->os_dirty = FALSE;
+
+ if (obj_state->obj != obj) {
+ nm_auto_nmpobj const NMPObject *obj_old = NULL;
+
+ if (!nmp_object_equal(obj_state->obj, obj))
+ changed = TRUE;
+ obj_old = g_steal_pointer(&obj_state->obj);
+ obj_state->obj = nmp_object_ref(obj);
+ }
+
+ if (!c_list_is_empty(&obj_state->os_zombie_lst)) {
+ c_list_unlink(&obj_state->os_zombie_lst);
+ changed = TRUE;
+ }
+
+ return changed;
+}
+
+/*****************************************************************************/
+
static void
-_l3cfg_externally_removed_objs_drop_unused(NML3Cfg *self)
+_obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboolean in_platform)
{
- GHashTableIter h_iter;
- const NMPObject *obj;
- char sbuf[sizeof(_nm_utils_to_string_buffer)];
+ char sbuf[sizeof(_nm_utils_to_string_buffer)];
+ ObjStateData *obj_state;
nm_assert(NM_IS_L3CFG(self));
+ nm_assert_is_bool(in_platform);
+
+ nm_assert(
+ in_platform
+ == (!!nm_platform_lookup_entry(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj)));
- if (!self->priv.p->externally_removed_objs_hash)
+ obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &obj);
+ if (!obj_state)
return;
- if (!self->priv.p->combined_l3cd_commited) {
- _l3cfg_externally_removed_objs_drop(self);
+ if (obj_state->os_in_platform == (!!in_platform))
+ goto out;
+
+ if (!in_platform && !c_list_is_empty(&obj_state->os_zombie_lst)) {
+ /* this is a zombie. We can forget about it.*/
+ obj_state->os_in_platform = FALSE;
+ c_list_unlink(&obj_state->os_zombie_lst);
+ _LOGD("obj-state: zombie gone (untrack): %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ g_hash_table_remove(self->priv.p->obj_state_hash, obj_state);
return;
}
- g_hash_table_iter_init(&h_iter, self->priv.p->externally_removed_objs_hash);
- while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj, NULL)) {
- if (!nm_l3_config_data_lookup_obj(self->priv.p->combined_l3cd_commited, obj)) {
- /* The object is no longer tracked in the configuration.
- * The externally_removed_objs_hash is to prevent adding entires that were
- * removed externally, so if we don't plan to add the entry, we no longer need to track
- * it. */
- _LOGD("externally-removed: untrack %s",
- nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
- (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))--;
- g_hash_table_iter_remove(&h_iter);
- }
+ nm_assert(c_list_is_empty(&obj_state->os_zombie_lst));
+
+ if (in_platform) {
+ obj_state->os_in_platform = TRUE;
+ obj_state->os_was_in_platform = TRUE;
+ _LOGD("obj-state: appeared in platform: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ goto out;
}
+
+ obj_state->os_in_platform = FALSE;
+ _LOGD("obj-state: remove from platform: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+
+out:
+ nm_assert_obj_state(self, obj_state);
}
static void
-_l3cfg_externally_removed_objs_track(NML3Cfg *self, const NMPObject *obj, gboolean is_removed)
+_obj_states_update_all(NML3Cfg *self)
{
- char sbuf[1000];
+ static const NMPObjectType obj_types[] = {
+ NMP_OBJECT_TYPE_IP4_ADDRESS,
+ NMP_OBJECT_TYPE_IP6_ADDRESS,
+ NMP_OBJECT_TYPE_IP4_ROUTE,
+ NMP_OBJECT_TYPE_IP6_ROUTE,
+ };
+ char sbuf[sizeof(_nm_utils_to_string_buffer)];
+ ObjStateData *obj_state;
+ int i;
+ gboolean any_dirty = FALSE;
nm_assert(NM_IS_L3CFG(self));
- if (!self->priv.p->combined_l3cd_commited)
- return;
+ c_list_for_each_entry (obj_state, &self->priv.p->obj_state_lst_head, os_lst) {
+ if (!c_list_is_empty(&obj_state->os_zombie_lst)) {
+ /* we can ignore zombies. */
+ continue;
+ }
+ any_dirty = TRUE;
+ obj_state->os_dirty = TRUE;
+ }
+
+ for (i = 0; i < (int) G_N_ELEMENTS(obj_types); i++) {
+ const NMPObjectType obj_type = obj_types[i];
+ NMDedupMultiIter o_iter;
+ const NMPObject * obj;
+
+ if (!self->priv.p->combined_l3cd_commited)
+ continue;
- if (!is_removed) {
- /* the object is still (or again) present. It no longer gets hidden. */
- if (self->priv.p->externally_removed_objs_hash) {
- const NMPObject *obj2;
- gpointer x_val;
-
- if (g_hash_table_steal_extended(self->priv.p->externally_removed_objs_hash,
- obj,
- (gpointer *) &obj2,
- &x_val)) {
- (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj2))))--;
- _LOGD("externally-removed: untrack %s",
- nmp_object_to_string(obj2, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
- nmp_object_unref(obj2);
+ nm_l3_config_data_iter_obj_for_each (&o_iter,
+ self->priv.p->combined_l3cd_commited,
+ &obj,
+ obj_type) {
+ obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &obj);
+ if (!obj_state) {
+ obj_state =
+ _obj_state_data_new(obj,
+ !!nm_platform_lookup_entry(self->priv.platform,
+ NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+ obj));
+ c_list_link_tail(&self->priv.p->obj_state_lst_head, &obj_state->os_lst);
+ g_hash_table_add(self->priv.p->obj_state_hash, obj_state);
+ _LOGD("obj-state: track: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ nm_assert_obj_state(self, obj_state);
+ continue;
+ }
+
+ if (_obj_state_data_update(obj_state, obj)) {
+ _LOGD("obj-state: update: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
}
+
+ nm_assert_obj_state(self, obj_state);
}
- return;
}
- if (!nm_l3_config_data_lookup_obj(self->priv.p->combined_l3cd_commited, obj)) {
- /* we don't care about this object, so there is nothing to hide hide */
- return;
+ if (any_dirty) {
+ GHashTableIter h_iter;
+
+ g_hash_table_iter_init(&h_iter, self->priv.p->obj_state_hash);
+ while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj_state, NULL)) {
+ if (!c_list_is_empty(&obj_state->os_zombie_lst))
+ continue;
+ if (!obj_state->os_dirty)
+ continue;
+
+ if (obj_state->os_in_platform && obj_state->os_nm_configured) {
+ c_list_link_tail(&self->priv.p->obj_state_zombie_lst_head,
+ &obj_state->os_zombie_lst);
+ obj_state->os_zombie_count = ZOMBIE_COUNT_START;
+ _LOGD("obj-state: now zombie: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ continue;
+ }
+
+ _LOGD("obj-state: untrack: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ g_hash_table_iter_remove(&h_iter);
+ }
}
+}
- if (G_UNLIKELY(!self->priv.p->externally_removed_objs_hash)) {
- self->priv.p->externally_removed_objs_hash =
- g_hash_table_new_full((GHashFunc) nmp_object_id_hash,
- (GEqualFunc) nmp_object_id_equal,
- (GDestroyNotify) nmp_object_unref,
- NULL);
+typedef struct {
+ NML3Cfg * self;
+ NML3CfgCommitType commit_type;
+} ObjStatesSyncFilterData;
+
+static gboolean
+_obj_states_sync_filter(/* const NMDedupMultiObj * */ gconstpointer o, gpointer user_data)
+{
+ char sbuf[sizeof(_nm_utils_to_string_buffer)];
+ const NMPObject * obj = o;
+ const ObjStatesSyncFilterData *sync_filter_data = user_data;
+ NMPObjectType obj_type;
+ ObjStateData * obj_state;
+
+ nm_assert(sync_filter_data);
+ nm_assert(NM_IS_L3CFG(sync_filter_data->self));
+
+ obj_type = NMP_OBJECT_GET_TYPE(obj);
+
+ if (obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS
+ && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->a_acd_not_ready)
+ return FALSE;
+
+ obj_state = g_hash_table_lookup(sync_filter_data->self->priv.p->obj_state_hash, &obj);
+
+ nm_assert_obj_state(sync_filter_data->self, obj_state);
+ nm_assert(obj_state->obj == obj);
+ nm_assert(c_list_is_empty(&obj_state->os_zombie_lst));
+
+ if (!obj_state->os_nm_configured) {
+ NML3Cfg *self;
+
+ if (sync_filter_data->commit_type == NM_L3_CFG_COMMIT_TYPE_ASSUME
+ && !_obj_state_data_get_assume_config_once(obj_state))
+ return FALSE;
+
+ obj_state->os_nm_configured = TRUE;
+
+ self = sync_filter_data->self;
+ _LOGD("obj-state: configure-first-time: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ return TRUE;
}
- if (g_hash_table_add(self->priv.p->externally_removed_objs_hash,
- (gpointer) nmp_object_ref(obj))) {
- (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))++;
- _LOGD("externally-removed: track %s",
- nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+ if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
+ /* we currently try to configure this address (but failed earlier).
+ * Definitely retry. */
+ return TRUE;
}
+
+ if (!obj_state->os_in_platform
+ && sync_filter_data->commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY)
+ return FALSE;
+
+ return TRUE;
}
static void
-_l3cfg_externally_removed_objs_pickup(NML3Cfg *self, int addr_family)
+_obj_state_zombie_lst_get_prune_lists(NML3Cfg * self,
+ int addr_family,
+ GPtrArray **out_addresses_prune,
+ GPtrArray **out_routes_prune)
{
- const int IS_IPv4 = NM_IS_IPv4(addr_family);
- NMDedupMultiIter iter;
- const NMPObject *obj;
+ const int IS_IPv4 = NM_IS_IPv4(addr_family);
+ const NMPObjectType obj_type_route = NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4);
+ const NMPObjectType obj_type_address = NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4);
+ char sbuf[sizeof(_nm_utils_to_string_buffer)];
+ ObjStateData * obj_state;
+ ObjStateData * obj_state_safe;
- if (!self->priv.p->combined_l3cd_commited)
- return;
+ nm_assert(NM_IS_L3CFG(self));
+ nm_assert(out_addresses_prune && !*out_addresses_prune);
+ nm_assert(out_routes_prune && !*out_routes_prune);
- nm_l3_config_data_iter_obj_for_each (&iter,
- self->priv.p->combined_l3cd_commited,
- &obj,
- NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
- if (!nm_platform_lookup_entry(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj))
- _l3cfg_externally_removed_objs_track(self, obj, TRUE);
- }
- nm_l3_config_data_iter_obj_for_each (&iter,
- self->priv.p->combined_l3cd_commited,
- &obj,
- NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
- if (!nm_platform_lookup_entry(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj))
- _l3cfg_externally_removed_objs_track(self, obj, TRUE);
+ c_list_for_each_entry_safe (obj_state,
+ obj_state_safe,
+ &self->priv.p->obj_state_zombie_lst_head,
+ os_zombie_lst) {
+ NMPObjectType obj_type;
+ GPtrArray ** p_a;
+
+ nm_assert_obj_state(self, obj_state);
+ nm_assert(obj_state->os_zombie_count > 0);
+
+ obj_type = NMP_OBJECT_GET_TYPE(obj_state->obj);
+
+ if (obj_type == obj_type_route)
+ p_a = out_routes_prune;
+ else if (obj_type == obj_type_address)
+ p_a = out_addresses_prune;
+ else
+ continue;
+
+ if (!*p_a)
+ *p_a = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+
+ g_ptr_array_add(*p_a, (gpointer) nmp_object_ref(obj_state->obj));
+
+ if (--obj_state->os_zombie_count == 0) {
+ _LOGD("obj-state: prune zombie (untrack): %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ g_hash_table_remove(self->priv.p->obj_state_hash, obj_state);
+ continue;
+ }
+ _LOGD("obj-state: prune zombie: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
}
}
-static gboolean
-_l3cfg_externally_removed_objs_filter(/* const NMDedupMultiObj * */ gconstpointer o,
- gpointer user_data)
+static void
+_obj_state_zombie_lst_prune_all(NML3Cfg *self, int addr_family)
{
- const NMPObject *obj = o;
- GHashTable * externally_removed_objs_hash = user_data;
+ char sbuf[sizeof(_nm_utils_to_string_buffer)];
+ ObjStateData *obj_state;
+ ObjStateData *obj_state_safe;
- if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ADDRESS
- && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready)
- return FALSE;
+ /* we call this during reapply. Then we delete all the routes/addresses
+ * that are configured, and not only the zombies.
+ *
+ * Still, we need to adjust the os_zombie_count and assume that we
+ * are going to drop them. */
+
+ c_list_for_each_entry_safe (obj_state,
+ obj_state_safe,
+ &self->priv.p->obj_state_zombie_lst_head,
+ os_zombie_lst) {
+ nm_assert_obj_state(self, obj_state);
+ nm_assert(obj_state->os_zombie_count > 0);
+
+ if (NMP_OBJECT_GET_ADDR_FAMILY(obj_state->obj) != addr_family)
+ continue;
- return !nm_g_hash_table_contains(externally_removed_objs_hash, obj);
+ if (--obj_state->os_zombie_count == 0) {
+ _LOGD("obj-state: zombie pruned during reapply (untrack): %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ g_hash_table_remove(self->priv.p->obj_state_hash, obj_state);
+ continue;
+ }
+ _LOGD("obj-state: zombie pruned during reapply: %s",
+ _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+ }
}
/*****************************************************************************/
@@ -845,7 +1157,7 @@ _nm_l3cfg_notify_platform_change(NML3Cfg * self,
case NMP_OBJECT_TYPE_IP6_ADDRESS:
case NMP_OBJECT_TYPE_IP4_ROUTE:
case NMP_OBJECT_TYPE_IP6_ROUTE:
- _l3cfg_externally_removed_objs_track(self, obj, change_type == NM_PLATFORM_SIGNAL_REMOVED);
+ _obj_states_externally_removed_track(self, obj, change_type != NM_PLATFORM_SIGNAL_REMOVED);
default:
break;
}
@@ -2710,6 +3022,7 @@ nm_l3cfg_add_config(NML3Cfg * self,
guint32 default_route_penalty_6,
NML3AcdDefendType acd_defend_type,
guint32 acd_timeout_msec,
+ NML3CfgConfigFlags config_flags,
NML3ConfigMergeFlags merge_flags)
{
L3ConfigData *l3_config_data;
@@ -2774,6 +3087,7 @@ nm_l3cfg_add_config(NML3Cfg * self,
*l3_config_data = (L3ConfigData){
.tag_confdata = tag,
.l3cd = nm_l3_config_data_ref_and_seal(l3cd),
+ .config_flags = config_flags,
.merge_flags = merge_flags,
.default_route_table_4 = default_route_table_4,
.default_route_table_6 = default_route_table_6,
@@ -2797,6 +3111,10 @@ nm_l3cfg_add_config(NML3Cfg * self,
l3_config_data->priority_confdata = priority;
changed = TRUE;
}
+ if (l3_config_data->config_flags != config_flags) {
+ l3_config_data->config_flags = config_flags;
+ changed = TRUE;
+ }
if (l3_config_data->merge_flags != merge_flags) {
l3_config_data->merge_flags = merge_flags;
changed = TRUE;
@@ -2916,13 +3234,14 @@ nm_l3cfg_remove_config_all_dirty(NML3Cfg *self, gconstpointer tag)
typedef struct {
NML3Cfg * self;
gconstpointer tag;
+ bool assume_config_once;
} L3ConfigMergeHookAddObjData;
static gboolean
-_l3_hook_add_addr_cb(const NML3ConfigData *l3cd,
- const NMPObject * obj,
- NMTernary * out_ip4acd_not_ready,
- gpointer user_data)
+_l3_hook_add_obj_cb(const NML3ConfigData * l3cd,
+ const NMPObject * obj,
+ NML3ConfigMergeHookResult *hook_result,
+ gpointer user_data)
{
const L3ConfigMergeHookAddObjData *hook_data = user_data;
NML3Cfg * self = hook_data->self;
@@ -2930,40 +3249,54 @@ _l3_hook_add_addr_cb(const NML3ConfigData *l3cd,
in_addr_t addr;
gboolean acd_bad = FALSE;
- nm_assert(out_ip4acd_not_ready && *out_ip4acd_not_ready == NM_TERNARY_DEFAULT);
+ nm_assert(obj);
+ nm_assert(hook_result);
+ nm_assert(hook_result->ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT);
+ nm_assert(hook_result->assume_config_once == NM_OPTION_BOOL_DEFAULT);
- if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP4_ADDRESS)
- return TRUE;
+ hook_result->assume_config_once = hook_data->assume_config_once;
- addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
+ switch (NMP_OBJECT_GET_TYPE(obj)) {
+ case NMP_OBJECT_TYPE_IP4_ADDRESS:
- if (ACD_ADDR_SKIP(addr))
- goto out;
+ addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
- acd_data = _l3_acd_data_find(self, addr);
+ if (ACD_ADDR_SKIP(addr))
+ goto out_ip4_address;
- if (!acd_data) {
- /* we don't yet track an ACD state for this address. That can only
- * happend during _l3cfg_update_combined_config() with !to_commit,
- * where we didn't update the ACD state.
- *
- * This means, unless you actually commit, nm_l3cfg_get_combined_l3cd(self, get_commited = FALSE)
- * won't consider IPv4 addresses ready, that have no known ACD state yet. */
- nm_assert(self->priv.p->changed_configs_acd_state);
- acd_bad = TRUE;
- goto out;
- }
+ acd_data = _l3_acd_data_find(self, addr);
- nm_assert(
- _acd_track_data_is_not_dirty(_acd_data_find_track(acd_data, l3cd, obj, hook_data->tag)));
- if (!NM_IN_SET(acd_data->info.state,
- NM_L3_ACD_ADDR_STATE_READY,
- NM_L3_ACD_ADDR_STATE_DEFENDING))
- acd_bad = TRUE;
+ if (!acd_data) {
+ /* we don't yet track an ACD state for this address. That can only
+ * happened during _l3cfg_update_combined_config() with !to_commit,
+ * where we didn't update the ACD state.
+ *
+ * This means, unless you actually commit, nm_l3cfg_get_combined_l3cd(self, get_commited = FALSE)
+ * won't consider IPv4 addresses ready, that have no known ACD state yet. */
+ nm_assert(self->priv.p->changed_configs_acd_state);
+ acd_bad = TRUE;
+ goto out_ip4_address;
+ }
-out:
- *out_ip4acd_not_ready = acd_bad ? NM_TERNARY_TRUE : NM_TERNARY_FALSE;
- return TRUE;
+ nm_assert(_acd_track_data_is_not_dirty(
+ _acd_data_find_track(acd_data, l3cd, obj, hook_data->tag)));
+ if (!NM_IN_SET(acd_data->info.state,
+ NM_L3_ACD_ADDR_STATE_READY,
+ NM_L3_ACD_ADDR_STATE_DEFENDING))
+ acd_bad = TRUE;
+
+out_ip4_address:
+ hook_result->ip4acd_not_ready = acd_bad ? NM_OPTION_BOOL_TRUE : NM_OPTION_BOOL_FALSE;
+ return TRUE;
+
+ default:
+ nm_assert_not_reached();
+ /* fall-through */
+ case NMP_OBJECT_TYPE_IP6_ADDRESS:
+ case NMP_OBJECT_TYPE_IP4_ROUTE:
+ case NMP_OBJECT_TYPE_IP6_ROUTE:
+ return TRUE;
+ }
}
static void
@@ -3036,17 +3369,20 @@ _l3cfg_update_combined_config(NML3Cfg * self,
for (i = 0; i < l3_config_datas_len; i++) {
const L3ConfigData *l3cd_data = l3_config_datas_arr[i];
- if (NM_FLAGS_HAS(l3cd_data->merge_flags, NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD))
+ if (NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD))
continue;
hook_data.tag = l3cd_data->tag_confdata;
+ hook_data.assume_config_once =
+ NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE);
+
nm_l3_config_data_merge(l3cd,
l3cd_data->l3cd,
l3cd_data->merge_flags,
l3cd_data->default_route_table_x,
l3cd_data->default_route_metric_x,
l3cd_data->default_route_penalty_x,
- _l3_hook_add_addr_cb,
+ _l3_hook_add_obj_cb,
&hook_data);
}
@@ -3080,6 +3416,8 @@ out:
nm_l3_config_data_ref(self->priv.p->combined_l3cd_merged);
commited_changed = TRUE;
+ _obj_states_update_all(self);
+
_nm_l3cfg_emit_signal_notify_l3cd_changed(self,
l3cd_commited_old,
self->priv.p->combined_l3cd_commited,
@@ -3115,75 +3453,45 @@ out:
/*****************************************************************************/
-typedef struct {
- const NMPObject *obj;
- gint64 timestamp_msec;
- bool dirty;
-} RoutesTemporaryNotAvailableData;
-
-static void
-_routes_temporary_not_available_data_free(gpointer user_data)
-{
- RoutesTemporaryNotAvailableData *data = user_data;
-
- nmp_object_unref(data->obj);
- nm_g_slice_free(data);
-}
-
-#define ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC ((gint64) 20000)
-
static gboolean
_routes_temporary_not_available_timeout(gpointer user_data)
{
- RoutesTemporaryNotAvailableData *data;
- NML3Cfg * self = NM_L3CFG(user_data);
- GHashTableIter iter;
- gint64 expiry_threshold_msec;
- gboolean any_expired = FALSE;
- gint64 now_msec;
- gint64 oldest_msec;
+ NML3Cfg * self = NM_L3CFG(user_data);
+ ObjStateData *obj_state;
+ gint64 now_msec;
+ gint64 expiry_msec;
- self->priv.p->routes_temporary_not_available_id = 0;
+ nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
- if (!self->priv.p->routes_temporary_not_available_hash)
- return G_SOURCE_REMOVE;
+ obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head,
+ ObjStateData,
+ os_temporary_not_available_lst);
- /* we check the timeouts again. That is, because we allow to remove
- * entries from routes_temporary_not_available_hash, without rescheduling
- * out timeouts. */
+ if (!obj_state)
+ return G_SOURCE_CONTINUE;
now_msec = nm_utils_get_monotonic_timestamp_msec();
- expiry_threshold_msec = now_msec - ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC;
- oldest_msec = G_MAXINT64;
+ expiry_msec = obj_state->os_temporary_not_available_timestamp_msec
+ + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC;
- g_hash_table_iter_init(&iter, self->priv.p->routes_temporary_not_available_hash);
- while (g_hash_table_iter_next(&iter, (gpointer *) &data, NULL)) {
- if (data->timestamp_msec >= expiry_threshold_msec) {
- any_expired = TRUE;
- break;
- }
- if (data->timestamp_msec < oldest_msec)
- oldest_msec = data->timestamp_msec;
+ if (now_msec < expiry_msec) {
+ /* the timeout is not yet reached. Restart the timer... */
+ self->priv.p->obj_state_temporary_not_available_timeout_source =
+ nm_g_timeout_add_source(expiry_msec - now_msec,
+ _routes_temporary_not_available_timeout,
+ self);
+ return G_SOURCE_CONTINUE;
}
- if (any_expired) {
- /* a route expired. We emit a signal, but we don't schedule it again. That will
- * only happen if the user calls nm_l3cfg_commit() again. */
- _nm_l3cfg_emit_signal_notify_simple(
- self,
- NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED);
- return G_SOURCE_REMOVE;
- }
-
- if (oldest_msec != G_MAXINT64) {
- /* we have a timeout still. Reschedule. */
- self->priv.p->routes_temporary_not_available_id =
- g_timeout_add(oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec,
- _routes_temporary_not_available_timeout,
- self);
- }
- return G_SOURCE_REMOVE;
+ /* One (or several) routes expired. We emit a signal, but we don't schedule it again.
+ * We expect the callers to commit again, which will one last time try to configure
+ * the route. If that again fails, we detect the timeout, log a warning and don't
+ * track the object as not temporary-not-available anymore. */
+ _nm_l3cfg_emit_signal_notify_simple(
+ self,
+ NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED);
+ return G_SOURCE_CONTINUE;
}
static gboolean
@@ -3191,13 +3499,12 @@ _routes_temporary_not_available_update(NML3Cfg * self,
int addr_family,
GPtrArray *routes_temporary_not_available_arr)
{
- RoutesTemporaryNotAvailableData *data;
- GHashTableIter iter;
- gint64 oldest_msec;
- gint64 now_msec;
- gboolean prune_all = FALSE;
- gboolean success = TRUE;
- guint i;
+ ObjStateData *obj_state;
+ ObjStateData *obj_state_safe;
+ gint64 now_msec;
+ gboolean prune_all = FALSE;
+ gboolean success = TRUE;
+ guint i;
now_msec = nm_utils_get_monotonic_timestamp_msec();
@@ -3206,36 +3513,43 @@ _routes_temporary_not_available_update(NML3Cfg * self,
goto out_prune;
}
- if (self->priv.p->routes_temporary_not_available_hash) {
- g_hash_table_iter_init(&iter, self->priv.p->routes_temporary_not_available_hash);
- while (g_hash_table_iter_next(&iter, (gpointer *) &data, NULL)) {
- if (NMP_OBJECT_GET_ADDR_FAMILY(data->obj) == addr_family)
- data->dirty = TRUE;
- }
- } else {
- self->priv.p->routes_temporary_not_available_hash =
- g_hash_table_new_full(nmp_object_indirect_id_hash,
- nmp_object_indirect_id_equal,
- _routes_temporary_not_available_data_free,
- NULL);
+ c_list_for_each_entry (obj_state,
+ &self->priv.p->obj_state_temporary_not_available_lst_head,
+ os_temporary_not_available_lst) {
+ nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0);
+ obj_state->os_tna_dirty = TRUE;
}
for (i = 0; i < routes_temporary_not_available_arr->len; i++) {
const NMPObject *o = routes_temporary_not_available_arr->pdata[i];
- char sbuf[1024];
+ char sbuf[sizeof(_nm_utils_to_string_buffer)];
nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)));
- data = g_hash_table_lookup(self->priv.p->routes_temporary_not_available_hash, &o);
+ obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &o);
- if (data) {
- if (!data->dirty)
- continue;
+ if (!obj_state) {
+ /* Hm? We don't track this object? Very odd, a bug? */
+ nm_assert_not_reached();
+ continue;
+ }
- nm_assert(data->timestamp_msec > 0 && data->timestamp_msec <= now_msec);
+ if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
+ nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0
+ && obj_state->os_temporary_not_available_timestamp_msec <= now_msec);
- if (now_msec > data->timestamp_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) {
- /* timeout. Could not add this address. */
+ if (!obj_state->os_tna_dirty) {
+ /* Odd, this only can happen if routes_temporary_not_available_arr contains duplicates.
+ * It should not. */
+ nm_assert_not_reached();
+ continue;
+ }
+
+ if (now_msec > obj_state->os_temporary_not_available_timestamp_msec
+ + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) {
+ /* Timeout. Could not add this address.
+ *
+ * For now, keep it obj_state->os_tna_dirty and prune it below. */
_LOGW("failure to add IPv%c route: %s",
nm_utils_addr_family_to_char(addr_family),
nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
@@ -3243,7 +3557,7 @@ _routes_temporary_not_available_update(NML3Cfg * self,
continue;
}
- data->dirty = FALSE;
+ obj_state->os_tna_dirty = FALSE;
continue;
}
@@ -3251,41 +3565,34 @@ _routes_temporary_not_available_update(NML3Cfg * self,
nm_utils_addr_family_to_char(addr_family),
nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
- data = g_slice_new(RoutesTemporaryNotAvailableData);
- *data = (RoutesTemporaryNotAvailableData){
- .obj = nmp_object_ref(o),
- .timestamp_msec = now_msec,
- .dirty = FALSE,
- };
- g_hash_table_add(self->priv.p->routes_temporary_not_available_hash, data);
+ obj_state->os_tna_dirty = FALSE;
+ obj_state->os_temporary_not_available_timestamp_msec = now_msec;
+ c_list_link_tail(&self->priv.p->obj_state_temporary_not_available_lst_head,
+ &obj_state->os_temporary_not_available_lst);
}
out_prune:
- oldest_msec = G_MAXINT64;
-
- if (self->priv.p->routes_temporary_not_available_hash) {
- g_hash_table_iter_init(&iter, self->priv.p->routes_temporary_not_available_hash);
- while (g_hash_table_iter_next(&iter, (gpointer *) &data, NULL)) {
- nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(data->obj) == addr_family || !data->dirty);
- if (!prune_all && !data->dirty) {
- if (data->timestamp_msec < oldest_msec)
- oldest_msec = data->timestamp_msec;
- continue;
- }
- g_hash_table_iter_remove(&iter);
+ c_list_for_each_entry_safe (obj_state,
+ obj_state_safe,
+ &self->priv.p->obj_state_temporary_not_available_lst_head,
+ os_temporary_not_available_lst) {
+ if (prune_all || obj_state->os_tna_dirty) {
+ obj_state->os_temporary_not_available_timestamp_msec = 0;
+ c_list_unlink(&obj_state->os_temporary_not_available_lst);
}
- if (oldest_msec != G_MAXINT64)
- nm_clear_pointer(&self->priv.p->routes_temporary_not_available_hash,
- g_hash_table_unref);
}
- nm_clear_g_source(&self->priv.p->routes_temporary_not_available_id);
- if (oldest_msec != G_MAXINT64) {
- nm_assert(oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC < now_msec);
- self->priv.p->routes_temporary_not_available_id =
- g_timeout_add(oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec,
- _routes_temporary_not_available_timeout,
- self);
+ nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
+
+ obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head,
+ ObjStateData,
+ os_temporary_not_available_lst);
+ if (obj_state) {
+ self->priv.p->obj_state_temporary_not_available_timeout_source =
+ nm_g_timeout_add_source((obj_state->os_temporary_not_available_timestamp_msec
+ + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec),
+ _routes_temporary_not_available_timeout,
+ self);
}
return success;
@@ -3323,52 +3630,24 @@ _l3_commit_one(NML3Cfg * self,
nm_utils_addr_family_to_char(addr_family),
_l3_cfg_commit_type_to_string(commit_type, sbuf_commit_type, sizeof(sbuf_commit_type)));
- if (changed_combined_l3cd) {
- /* our combined configuration changed. We may track entries in externally_removed_objs_hash,
- * which are not longer to be considered by our configuration. We need to forget about them. */
- _l3cfg_externally_removed_objs_drop_unused(self);
- }
-
- if (commit_type == NM_L3_CFG_COMMIT_TYPE_ASSUME) {
- /* we need to artificially pre-populate the externally remove hash. */
- _l3cfg_externally_removed_objs_pickup(self, addr_family);
- }
-
if (self->priv.p->combined_l3cd_commited) {
- GHashTable * externally_removed_objs_hash;
- NMDedupMultiFcnSelectPredicate predicate;
- const NMDedupMultiHeadEntry * head_entry;
-
- if (commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
- && self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] > 0) {
- predicate = _l3cfg_externally_removed_objs_filter;
- externally_removed_objs_hash = self->priv.p->externally_removed_objs_hash;
- } else {
- if (IS_IPv4)
- predicate = _l3cfg_externally_removed_objs_filter;
- else
- predicate = NULL;
- externally_removed_objs_hash = NULL;
- }
+ const NMDedupMultiHeadEntry * head_entry;
+ const ObjStatesSyncFilterData sync_filter_data = {
+ .self = self,
+ .commit_type = commit_type,
+ };
+
head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4));
addresses = nm_dedup_multi_objs_to_ptr_array_head(head_entry,
- predicate,
- externally_removed_objs_hash);
+ _obj_states_sync_filter,
+ (gpointer) &sync_filter_data);
- if (commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
- && self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] > 0) {
- predicate = _l3cfg_externally_removed_objs_filter;
- externally_removed_objs_hash = self->priv.p->externally_removed_objs_hash;
- } else {
- predicate = NULL;
- externally_removed_objs_hash = NULL;
- }
head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4));
routes = nm_dedup_multi_objs_to_ptr_array_head(head_entry,
- predicate,
- externally_removed_objs_hash);
+ _obj_states_sync_filter,
+ (gpointer) &sync_filter_data);
route_table_sync =
nm_l3_config_data_get_route_table_sync(self->priv.p->combined_l3cd_commited,
@@ -3387,13 +3666,9 @@ _l3_commit_one(NML3Cfg * self,
addr_family,
self->priv.ifindex,
route_table_sync);
- } else if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) {
- addresses_prune = nm_g_ptr_array_ref(self->priv.p->last_addresses_x[IS_IPv4]);
- routes_prune = nm_g_ptr_array_ref(self->priv.p->last_routes_x[IS_IPv4]);
- }
-
- nm_g_ptr_array_set(&self->priv.p->last_addresses_x[IS_IPv4], addresses);
- nm_g_ptr_array_set(&self->priv.p->last_routes_x[IS_IPv4], routes);
+ _obj_state_zombie_lst_prune_all(self, addr_family);
+ } else
+ _obj_state_zombie_lst_get_prune_lists(self, addr_family, &addresses_prune, &routes_prune);
/* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ip6_privacy(). */
/* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ndisc_*(). */
@@ -3479,9 +3754,6 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source);
- if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY)
- _l3cfg_externally_removed_objs_drop(self);
-
_l3cfg_update_combined_config(self,
TRUE,
commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY,
@@ -3534,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 *
@@ -3767,6 +4039,14 @@ nm_l3cfg_init(NML3Cfg *self)
c_list_init(&self->priv.p->acd_lst_head);
c_list_init(&self->priv.p->acd_event_notify_lst_head);
c_list_init(&self->priv.p->commit_type_lst_head);
+ c_list_init(&self->priv.p->obj_state_lst_head);
+ c_list_init(&self->priv.p->obj_state_temporary_not_available_lst_head);
+ c_list_init(&self->priv.p->obj_state_zombie_lst_head);
+
+ self->priv.p->obj_state_hash = g_hash_table_new_full(nmp_object_indirect_id_hash,
+ nmp_object_indirect_id_equal,
+ _obj_state_data_free,
+ NULL);
}
static void
@@ -3821,15 +4101,12 @@ finalize(GObject *object)
nm_clear_g_source_inst(&self->priv.p->nacd_source);
nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry);
- nm_clear_pointer(&self->priv.p->last_addresses_4, g_ptr_array_unref);
- nm_clear_pointer(&self->priv.p->last_addresses_6, g_ptr_array_unref);
- nm_clear_pointer(&self->priv.p->last_routes_4, g_ptr_array_unref);
- nm_clear_pointer(&self->priv.p->last_routes_6, g_ptr_array_unref);
-
- nm_clear_g_source(&self->priv.p->routes_temporary_not_available_id);
- nm_clear_pointer(&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref);
+ nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
- nm_clear_pointer(&self->priv.p->externally_removed_objs_hash, g_hash_table_unref);
+ nm_clear_pointer(&self->priv.p->obj_state_hash, g_hash_table_destroy);
+ nm_assert(c_list_is_empty(&self->priv.p->obj_state_lst_head));
+ nm_assert(c_list_is_empty(&self->priv.p->obj_state_temporary_not_available_lst_head));
+ nm_assert(c_list_is_empty(&self->priv.p->obj_state_zombie_lst_head));
g_clear_object(&self->priv.netns);
g_clear_object(&self->priv.platform);
diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h
index 63f786f99a..193ff72a7c 100644
--- a/src/core/nm-l3cfg.h
+++ b/src/core/nm-l3cfg.h
@@ -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())
@@ -28,6 +29,37 @@ typedef enum _nm_packed {
NM_L3_ACD_DEFEND_TYPE_ALWAYS,
} NML3AcdDefendType;
+/**
+ * NML3CfgConfigFlags:
+ * @NM_L3CFG_CONFIG_FLAGS_NONE: no flags, the default.
+ * @NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD: if this merge flag is set,
+ * the the NML3ConfigData doesn't get merged and it's information won't be
+ * synced. The only purpose is to run ACD on its IPv4 addresses, but
+ * regardless whether ACD succeeds/fails, the IP addresses won't be configured.
+ * The point is to run ACD first (without configuring it), and only
+ * commit the settings if requested. That can either happen by
+ * nm_l3cfg_add_config() the same NML3Cfg again (with a different
+ * tag), or by calling nm_l3cfg_add_config() again with this flag
+ * cleared (and the same tag).
+ * @NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE: a commit with
+ * %NM_L3_CFG_COMMIT_TYPE_ASSUME, means to not remove/add
+ * addresses that are missing/already exist. The assume mode
+ * is for taking over a device gracefully after restart, so
+ * it aims to preserve whatever was configured (or not configured).
+ * With this flag enabled, the first commit in assume mode will still
+ * add the addresses/routes. This is necessary for example with IPv6LL.
+ * Also while assuming a device, we want to configure things
+ * (like an IPv6 address), so we need to bypass the common
+ * "don't change" behavior. At least once. If the address/route
+ * is still not (no longer) configured on the subsequent
+ * commit, it's not getting added again.
+ */
+typedef enum _nm_packed {
+ NM_L3CFG_CONFIG_FLAGS_NONE = 0,
+ NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD = (1LL << 0),
+ NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE = (1LL << 1),
+} NML3CfgConfigFlags;
+
typedef enum _nm_packed {
NM_L3_ACD_ADDR_STATE_INIT,
NM_L3_ACD_ADDR_STATE_PROBING,
@@ -291,6 +323,7 @@ gboolean nm_l3cfg_add_config(NML3Cfg * self,
guint32 default_route_penalty_6,
NML3AcdDefendType acd_defend_type,
guint32 acd_timeout_msec,
+ NML3CfgConfigFlags config_flags,
NML3ConfigMergeFlags merge_flags);
gboolean nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *ifcfg);
diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c
index d4bfca0c40..71da97d0f7 100644
--- a/src/core/settings/nm-settings-connection.c
+++ b/src/core/settings/nm-settings-connection.c
@@ -382,7 +382,7 @@ _nm_settings_connection_set_connection(NMSettingsConnection * self,
NM_SETTING_COMPARE_FLAG_EXACT)) {
connection_old = priv->connection;
priv->connection = g_object_ref(new_connection);
- nmtst_connection_assert_unchanging(priv->connection);
+ nm_assert_connection_unchanging(priv->connection);
_getsettings_cached_clear(priv);
_nm_settings_notify_sorted_by_autoconnect_priority_maybe_changed(priv->settings);
diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c
index 32374c5940..b71b9d189d 100644
--- a/src/core/settings/nm-settings.c
+++ b/src/core/settings/nm-settings.c
@@ -1352,7 +1352,7 @@ _connection_changed_track(NMSettings * self,
|| (_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
nm_assert(!connection || nm_streq0(uuid, nm_connection_get_uuid(connection)));
- nmtst_connection_assert_unchanging(connection);
+ nm_assert_connection_unchanging(connection);
sett_conn_entry =
_sett_conn_entries_get(self, uuid) ?: _sett_conn_entries_create_and_add(self, uuid);
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
index 134bdf6844..3feb444026 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
@@ -112,7 +112,7 @@ nms_ifcfg_rh_storage_new_connection(NMSIfcfgRHPlugin * plugin,
nm_assert(NM_IS_CONNECTION(connection_take));
nm_assert(_nm_connection_verify(connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS);
- nmtst_connection_assert_unchanging(connection_take);
+ nm_assert_connection_unchanging(connection_take);
self = _storage_new(plugin, nm_connection_get_uuid(connection_take), filename);
self->connection = connection_take;
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index b66139e3a4..602a7327d2 100644
--- a/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -323,7 +323,7 @@ load_eni_ifaces(NMSIfupdownPlugin *self)
NM_PRINT_FMT_QUOTED(local, " (", local->message, ")", ""));
sd = NULL;
} else {
- nmtst_connection_assert_unchanging(connection);
+ nm_assert_connection_unchanging(connection);
uuid = nm_connection_get_uuid(connection);
if (!storage)
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-storage.c b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
index 8c526c81b4..ec0634c43d 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
@@ -168,7 +168,7 @@ nms_keyfile_storage_new_connection(NMSKeyfilePlugin * plugin,
nm_assert(filename && filename[0] == '/');
nm_assert(storage_type >= NMS_KEYFILE_STORAGE_TYPE_RUN
&& storage_type <= _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST);
- nmtst_connection_assert_unchanging(connection_take);
+ nm_assert_connection_unchanging(connection_take);
self = _storage_new(plugin,
nm_connection_get_uuid(connection_take),
diff --git a/src/core/tests/test-ip6-config.c b/src/core/tests/test-ip6-config.c
index ddf4c789c8..2e6d8aaa19 100644
--- a/src/core/tests/test-ip6-config.c
+++ b/src/core/tests/test-ip6-config.c
@@ -327,7 +327,7 @@ test_nm_ip6_config_addresses_sort(void)
0,
0,
0,
- IFA_F_TEMPORARY);
+ IFA_F_SECONDARY);
ADDR_ADD("2607:f0d0:1002:51::8",
NULL,
64,
@@ -336,7 +336,7 @@ test_nm_ip6_config_addresses_sort(void)
0,
0,
0,
- IFA_F_TEMPORARY);
+ IFA_F_SECONDARY);
ADDR_ADD("2607:f0d0:1002:51::0",
NULL,
64,
@@ -345,7 +345,7 @@ test_nm_ip6_config_addresses_sort(void)
0,
0,
0,
- IFA_F_TEMPORARY);
+ IFA_F_SECONDARY);
ADDR_ADD("fec0::1", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
ADDR_ADD("fe80::208:74ff:feda:625c", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
ADDR_ADD("fe80::208:74ff:feda:625d", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
@@ -374,7 +374,7 @@ test_nm_ip6_config_addresses_sort(void)
0,
0,
0,
- IFA_F_TEMPORARY);
+ IFA_F_SECONDARY);
ADDR_ADD("2607:f0d0:1002:51::4", NULL, 64, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0);
ADDR_ADD("2607:f0d0:1002:51::5", NULL, 64, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0);
ADDR_ADD("2607:f0d0:1002:51::8",
@@ -385,7 +385,7 @@ test_nm_ip6_config_addresses_sort(void)
0,
0,
0,
- IFA_F_TEMPORARY);
+ IFA_F_SECONDARY);
ADDR_ADD("2607:f0d0:1002:51::0",
NULL,
64,
@@ -394,7 +394,7 @@ test_nm_ip6_config_addresses_sort(void)
0,
0,
0,
- IFA_F_TEMPORARY);
+ IFA_F_SECONDARY);
ADDR_ADD("2607:f0d0:1002:51::6",
NULL,
64,
diff --git a/src/core/tests/test-l3cfg.c b/src/core/tests/test-l3cfg.c
index 2ef9f36eb8..e4c6beb4b9 100644
--- a/src/core/tests/test-l3cfg.c
+++ b/src/core/tests/test-l3cfg.c
@@ -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"
@@ -434,6 +437,7 @@ test_l3cfg(gconstpointer test_data)
0,
tdata->acd_defend_type_a,
tdata->acd_timeout_msec_a,
+ NM_L3CFG_CONFIG_FLAGS_NONE,
NM_L3_CONFIG_MERGE_FLAGS_NONE);
}
@@ -595,6 +599,7 @@ _test_l3_ipv4ll_signal_notify(NML3Cfg * l3cfg,
0,
NM_L3_ACD_DEFEND_TYPE_ONCE,
nmtst_get_rand_bool() ? tdata->acd_timeout_msec : 0u,
+ NM_L3CFG_CONFIG_FLAGS_NONE,
NM_L3_CONFIG_MERGE_FLAGS_NONE))
g_assert_not_reached();
nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll));
@@ -778,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
@@ -795,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);
}
diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c
index 9bc8df3f9b..dc85ea0c03 100644
--- a/src/libnm-core-impl/nm-connection.c
+++ b/src/libnm-core-impl/nm-connection.c
@@ -2115,23 +2115,23 @@ _nm_connection_ensure_normalized(NMConnection * connection,
#if NM_MORE_ASSERTS
static void
-_nmtst_connection_unchanging_changed_cb(NMConnection *connection, gpointer user_data)
+_nm_assert_connection_unchanging_changed_cb(NMConnection *connection, gpointer user_data)
{
nm_assert_not_reached();
}
static void
-_nmtst_connection_unchanging_secrets_updated_cb(NMConnection *connection,
- const char * setting_name,
- gpointer user_data)
+_nm_assert_connection_unchanging_secrets_updated_cb(NMConnection *connection,
+ const char * setting_name,
+ gpointer user_data)
{
nm_assert_not_reached();
}
-const char _nmtst_connection_unchanging_user_data = 0;
+const char _nm_assert_connection_unchanging_user_data = 0;
void
-nmtst_connection_assert_unchanging(NMConnection *connection)
+nm_assert_connection_unchanging(NMConnection *connection)
{
if (!connection)
return;
@@ -2144,7 +2144,7 @@ nmtst_connection_assert_unchanging(NMConnection *connection)
0,
NULL,
NULL,
- (gpointer) &_nmtst_connection_unchanging_user_data)
+ (gpointer) &_nm_assert_connection_unchanging_user_data)
!= 0) {
/* avoid connecting the assertion handler multiple times. */
return;
@@ -2152,16 +2152,16 @@ nmtst_connection_assert_unchanging(NMConnection *connection)
g_signal_connect(connection,
NM_CONNECTION_CHANGED,
- G_CALLBACK(_nmtst_connection_unchanging_changed_cb),
- (gpointer) &_nmtst_connection_unchanging_user_data);
+ G_CALLBACK(_nm_assert_connection_unchanging_changed_cb),
+ (gpointer) &_nm_assert_connection_unchanging_user_data);
g_signal_connect(connection,
NM_CONNECTION_SECRETS_CLEARED,
- G_CALLBACK(_nmtst_connection_unchanging_changed_cb),
- (gpointer) &_nmtst_connection_unchanging_user_data);
+ G_CALLBACK(_nm_assert_connection_unchanging_changed_cb),
+ (gpointer) &_nm_assert_connection_unchanging_user_data);
g_signal_connect(connection,
NM_CONNECTION_SECRETS_UPDATED,
- G_CALLBACK(_nmtst_connection_unchanging_secrets_updated_cb),
- (gpointer) &_nmtst_connection_unchanging_user_data);
+ G_CALLBACK(_nm_assert_connection_unchanging_secrets_updated_cb),
+ (gpointer) &_nm_assert_connection_unchanging_user_data);
}
#endif
@@ -2338,7 +2338,7 @@ nm_connection_need_secrets(NMConnection *connection, GPtrArray **hints)
if (!setting)
continue;
- nm_assert(!setting_before || _nmtst_nm_setting_sort(setting_before, setting) < 0);
+ nm_assert(!setting_before || _nm_setting_sort_for_nm_assert(setting_before, setting) < 0);
nm_assert(!setting_before || _nm_setting_compare_priority(setting_before, setting) <= 0);
setting_before = setting;
@@ -2660,7 +2660,7 @@ nm_connection_is_type(NMConnection *connection, const char *type)
}
int
-_nmtst_nm_setting_sort(NMSetting *a, NMSetting *b)
+_nm_setting_sort_for_nm_assert(NMSetting *a, NMSetting *b)
{
g_assert(NM_IS_SETTING(a));
g_assert(NM_IS_SETTING(b));
@@ -2727,7 +2727,7 @@ nm_connection_get_settings(NMConnection *connection, guint *out_length)
NMSetting *setting = priv->settings[nm_meta_setting_types_by_priority[i]];
if (setting) {
- nm_assert(j == 0 || _nmtst_nm_setting_sort(arr[j - 1], setting) < 0);
+ nm_assert(j == 0 || _nm_setting_sort_for_nm_assert(arr[j - 1], setting) < 0);
arr[j++] = setting;
}
}
diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h
index 843fdb6453..b7c60536a0 100644
--- a/src/libnm-core-impl/nm-setting-private.h
+++ b/src/libnm-core-impl/nm-setting-private.h
@@ -175,7 +175,7 @@ void _nm_setting_ip_config_private_init(gpointer self, NMSettingIPConfigPrivate
NMSettingPriority _nm_setting_get_base_type_priority(NMSetting *setting);
int _nm_setting_compare_priority(gconstpointer a, gconstpointer b);
-int _nmtst_nm_setting_sort(NMSetting *a, NMSetting *b);
+int _nm_setting_sort_for_nm_assert(NMSetting *a, NMSetting *b);
/*****************************************************************************/
diff --git a/src/libnm-core-impl/nm-simple-connection.c b/src/libnm-core-impl/nm-simple-connection.c
index 6b5b118413..f16d9d59c6 100644
--- a/src/libnm-core-impl/nm-simple-connection.c
+++ b/src/libnm-core-impl/nm-simple-connection.c
@@ -161,7 +161,7 @@ dispose(GObject *object)
#if NM_MORE_ASSERTS
g_signal_handlers_disconnect_by_data(object,
- (gpointer) &_nmtst_connection_unchanging_user_data);
+ (gpointer) &_nm_assert_connection_unchanging_user_data);
#endif
nm_connection_clear_secrets(connection);
diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c
index c84bee19d2..7b8a5ba494 100644
--- a/src/libnm-core-impl/tests/test-setting.c
+++ b/src/libnm-core-impl/tests/test-setting.c
@@ -152,7 +152,7 @@ test_nm_meta_setting_types_by_priority(void)
for (j = 0; j < i; j++) {
NMSetting *other = arr->pdata[j];
- if (_nmtst_nm_setting_sort(other, setting) >= 0) {
+ if (_nm_setting_sort_for_nm_assert(other, setting) >= 0) {
g_error("sort order for nm_meta_setting_types_by_priority[%d vs %d] is wrong: %s "
"should be before %s",
j,
diff --git a/src/libnm-core-intern/nm-core-internal.h b/src/libnm-core-intern/nm-core-internal.h
index c50eaf41ee..f3db626a3f 100644
--- a/src/libnm-core-intern/nm-core-internal.h
+++ b/src/libnm-core-intern/nm-core-internal.h
@@ -272,11 +272,11 @@ gboolean _nm_connection_ensure_normalized(NMConnection * connection,
gboolean _nm_connection_remove_setting(NMConnection *connection, GType setting_type);
#if NM_MORE_ASSERTS
-extern const char _nmtst_connection_unchanging_user_data;
-void nmtst_connection_assert_unchanging(NMConnection *connection);
+extern const char _nm_assert_connection_unchanging_user_data;
+void nm_assert_connection_unchanging(NMConnection *connection);
#else
static inline void
-nmtst_connection_assert_unchanging(NMConnection *connection)
+nm_assert_connection_unchanging(NMConnection *connection)
{}
#endif
diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h
index 83240d2b94..eb7eb08dc5 100644
--- a/src/libnm-glib-aux/nm-shared-utils.h
+++ b/src/libnm-glib-aux/nm-shared-utils.h
@@ -18,6 +18,7 @@ typedef enum _nm_packed {
NM_OPTION_BOOL_TRUE = 1,
} NMOptionBool;
+#define nm_assert_is_bool(value) nm_assert(NM_IN_SET((value), 0, 1))
#define nm_assert_is_ternary(value) nm_assert(NM_IN_SET((value), -1, 0, 1))
/*****************************************************************************/
@@ -2096,6 +2097,28 @@ nm_g_array_unref(GArray *arr)
g_array_unref(arr);
}
+#define nm_g_array_first(arr, type) \
+ ({ \
+ GArray *const _arr = (arr); \
+ guint _len; \
+ \
+ nm_assert(_arr); \
+ _len = _arr->len; \
+ nm_assert(_len > 0); \
+ &g_array_index(arr, type, 0); \
+ })
+
+#define nm_g_array_last(arr, type) \
+ ({ \
+ GArray *const _arr = (arr); \
+ guint _len; \
+ \
+ nm_assert(_arr); \
+ _len = _arr->len; \
+ nm_assert(_len > 0); \
+ &g_array_index(arr, type, _len - 1u); \
+ })
+
#define nm_g_array_append_new(arr, type) \
({ \
GArray *const _arr = (arr); \
diff --git a/src/libnm-glib-aux/nm-test-utils.h b/src/libnm-glib-aux/nm-test-utils.h
index 2e01f3e810..253aaf0e86 100644
--- a/src/libnm-glib-aux/nm-test-utils.h
+++ b/src/libnm-glib-aux/nm-test-utils.h
@@ -1458,6 +1458,19 @@ next:;
/*****************************************************************************/
+/* uses an expression statement to copy and return @arg. The use is for functions
+ * like nmtst_inet4_from_string(), which return a static (thread-local) variable.
+ * If you use such a statement twice, the result will be overwritten. The macro
+ * prevents that. */
+#define NMTST_COPY(arg) \
+ ({ \
+ typeof(arg) _arg = (arg); \
+ \
+ _arg; \
+ })
+
+/*****************************************************************************/
+
#define __define_nmtst_static(NUM, SIZE) \
static inline const char *nmtst_static_##SIZE##_##NUM(const char *str) \
{ \
diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c
index 80cfe97b13..e65e47041e 100644
--- a/src/libnm-platform/nm-platform.c
+++ b/src/libnm-platform/nm-platform.c
@@ -3657,7 +3657,7 @@ _addr_array_clean_expired(int addr_family,
}
#endif
- if (!NM_IS_IPv4(addr_family) && NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TEMPORARY)) {
+ if (!NM_IS_IPv4(addr_family) && NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_SECONDARY)) {
/* temporary addresses are never added explicitly by NetworkManager but
* kernel adds them via mngtempaddr flag.
*
@@ -4262,7 +4262,7 @@ nm_platform_ip_address_get_prune_list(NMPlatform *self,
if (!IS_IPv4) {
if (exclude_ipv6_temporary_addrs
- && NM_FLAGS_HAS(NMP_OBJECT_CAST_IP_ADDRESS(obj)->n_ifa_flags, IFA_F_TEMPORARY))
+ && NM_FLAGS_HAS(NMP_OBJECT_CAST_IP_ADDRESS(obj)->n_ifa_flags, IFA_F_SECONDARY))
continue;
}
@@ -6303,7 +6303,8 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf
"%s" /* label */
" src %s"
"%s" /* external */
- "%s" /* ip4acd_not_ready */
+ "%s" /* a_acd_not_ready */
+ "%s" /* a_assume_config_once */
"",
s_address,
address->plen,
@@ -6322,7 +6323,8 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf
str_label,
nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)),
address->external ? " ext" : "",
- address->ip4acd_not_ready ? " ip4acd-not-ready" : "");
+ address->a_acd_not_ready ? " ip4acd-not-ready" : "",
+ address->a_assume_config_once ? " assume-config-once" : "");
g_free(str_peer);
return buf;
}
@@ -6356,17 +6358,22 @@ NM_UTILS_ENUM2STR_DEFINE(nm_platform_link_inet6_addrgenmode2str,
NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY, "stable-privacy"),
NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_RANDOM, "random"), );
+G_STATIC_ASSERT(IFA_F_SECONDARY == IFA_F_TEMPORARY);
+
NM_UTILS_FLAGS2STR_DEFINE(nm_platform_addr_flags2str,
unsigned,
NM_UTILS_FLAGS2STR(IFA_F_SECONDARY, "secondary"),
NM_UTILS_FLAGS2STR(IFA_F_NODAD, "nodad"),
NM_UTILS_FLAGS2STR(IFA_F_OPTIMISTIC, "optimistic"),
+ NM_UTILS_FLAGS2STR(IFA_F_DADFAILED, "dadfailed"),
NM_UTILS_FLAGS2STR(IFA_F_HOMEADDRESS, "homeaddress"),
NM_UTILS_FLAGS2STR(IFA_F_DEPRECATED, "deprecated"),
+ NM_UTILS_FLAGS2STR(IFA_F_TENTATIVE, "tentative"),
NM_UTILS_FLAGS2STR(IFA_F_PERMANENT, "permanent"),
NM_UTILS_FLAGS2STR(IFA_F_MANAGETEMPADDR, "mngtmpaddr"),
NM_UTILS_FLAGS2STR(IFA_F_NOPREFIXROUTE, "noprefixroute"),
- NM_UTILS_FLAGS2STR(IFA_F_TENTATIVE, "tentative"), );
+ NM_UTILS_FLAGS2STR(IFA_F_MCAUTOJOIN, "mcautojoin"),
+ NM_UTILS_FLAGS2STR(IFA_F_STABLE_PRIVACY, "stable-privacy"), );
NM_UTILS_ENUM2STR_DEFINE(nm_platform_route_scope2str,
int,
@@ -6436,7 +6443,10 @@ nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf
g_snprintf(
buf,
len,
- "%s/%d lft %s pref %s%s%s%s%s src %s%s",
+ "%s/%d lft %s pref %s%s%s%s%s src %s"
+ "%s" /* external */
+ "%s" /* a_assume_config_once */
+ "",
s_address,
address->plen,
str_lft_p,
@@ -6446,7 +6456,8 @@ nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf
str_dev,
_to_string_ifa_flags(address->n_ifa_flags, s_flags, sizeof(s_flags)),
nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)),
- address->external ? " ext" : "");
+ address->external ? " external" : "",
+ address->a_assume_config_once ? " assume-config-once" : "");
g_free(str_peer);
return buf;
}
@@ -6538,6 +6549,7 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz
"%s" /* initrwnd */
"%s" /* mtu */
"%s" /* is_external */
+ "%s" /* r_assume_config_once */
"",
nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced),
str_type),
@@ -6594,7 +6606,8 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz
route->lock_mtu ? "lock " : "",
route->mtu)
: "",
- route->is_external ? " (E)" : "");
+ route->is_external ? " is-external" : "",
+ route->r_assume_config_once ? " assume-config-once" : "");
return buf;
}
@@ -6665,6 +6678,7 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
"%s" /* mtu */
"%s" /* pref */
"%s" /* is_external */
+ "%s" /* r_assume_config_once */
"",
nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced),
str_type),
@@ -6725,7 +6739,8 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
" pref %s",
nm_icmpv6_router_pref_to_string(route->rt_pref, str_pref2, sizeof(str_pref2)))
: "",
- route->is_external ? " (E)" : "");
+ route->is_external ? " is-external" : "",
+ route->r_assume_config_once ? " assume-config-once" : "");
return buf;
}
@@ -7809,20 +7824,20 @@ nm_platform_ip6_address_pretty_sort_cmp(const NMPlatformIP6Address *a1,
NM_CMP_DIRECT(_address_pretty_sort_get_prio_6(&a2->address),
_address_pretty_sort_get_prio_6(&a1->address));
- ipv6_privacy1 = NM_FLAGS_ANY(a1->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY);
- ipv6_privacy2 = NM_FLAGS_ANY(a2->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY);
+ ipv6_privacy1 = NM_FLAGS_ANY(a1->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_SECONDARY);
+ ipv6_privacy2 = NM_FLAGS_ANY(a2->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_SECONDARY);
if (ipv6_privacy1 || ipv6_privacy2) {
gboolean public1 = TRUE;
gboolean public2 = TRUE;
if (ipv6_privacy1) {
- if (a1->n_ifa_flags & IFA_F_TEMPORARY)
+ if (a1->n_ifa_flags & IFA_F_SECONDARY)
public1 = prefer_temp;
else
public1 = !prefer_temp;
}
if (ipv6_privacy2) {
- if (a2->n_ifa_flags & IFA_F_TEMPORARY)
+ if (a2->n_ifa_flags & IFA_F_SECONDARY)
public2 = prefer_temp;
else
public2 = !prefer_temp;
@@ -7861,7 +7876,8 @@ nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState
NM_HASH_COMBINE_BOOLS(guint8,
obj->external,
obj->use_ip4_broadcast_address,
- obj->ip4acd_not_ready));
+ obj->a_acd_not_ready,
+ obj->a_assume_config_once));
nm_hash_update_strarr(h, obj->label);
}
@@ -7883,7 +7899,8 @@ nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Ad
NM_CMP_FIELD(a, b, n_ifa_flags);
NM_CMP_FIELD_STR(a, b, label);
NM_CMP_FIELD_UNSAFE(a, b, external);
- NM_CMP_FIELD_UNSAFE(a, b, ip4acd_not_ready);
+ NM_CMP_FIELD_UNSAFE(a, b, a_acd_not_ready);
+ NM_CMP_FIELD_UNSAFE(a, b, a_assume_config_once);
return 0;
}
@@ -7900,7 +7917,7 @@ nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState
obj->plen,
obj->address,
obj->peer_address,
- NM_HASH_COMBINE_BOOLS(guint8, obj->external));
+ NM_HASH_COMBINE_BOOLS(guint8, obj->external, obj->a_assume_config_once));
}
int
@@ -7921,6 +7938,7 @@ nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a, const NMPlatformIP6Ad
NM_CMP_FIELD(a, b, preferred);
NM_CMP_FIELD(a, b, n_ifa_flags);
NM_CMP_FIELD_UNSAFE(a, b, external);
+ NM_CMP_FIELD_UNSAFE(a, b, a_assume_config_once);
return 0;
}
@@ -8021,7 +8039,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
obj->initrwnd,
obj->mtu,
obj->r_rtm_flags,
- NM_HASH_COMBINE_BOOLS(guint8,
+ NM_HASH_COMBINE_BOOLS(guint16,
obj->metric_any,
obj->table_any,
obj->lock_window,
@@ -8029,7 +8047,8 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
obj->lock_initcwnd,
obj->lock_initrwnd,
obj->lock_mtu,
- obj->is_external));
+ obj->is_external,
+ obj->r_assume_config_once));
break;
}
}
@@ -8119,8 +8138,10 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
NM_CMP_FIELD(a, b, initcwnd);
NM_CMP_FIELD(a, b, initrwnd);
NM_CMP_FIELD(a, b, mtu);
- if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL)
+ if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) {
NM_CMP_FIELD_UNSAFE(a, b, is_external);
+ NM_CMP_FIELD_UNSAFE(a, b, r_assume_config_once);
+ }
break;
}
return 0;
@@ -8205,7 +8226,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
obj->rt_source,
obj->mss,
obj->r_rtm_flags,
- NM_HASH_COMBINE_BOOLS(guint8,
+ NM_HASH_COMBINE_BOOLS(guint16,
obj->metric_any,
obj->table_any,
obj->lock_window,
@@ -8213,7 +8234,8 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
obj->lock_initcwnd,
obj->lock_initrwnd,
obj->lock_mtu,
- obj->is_external),
+ obj->is_external,
+ obj->r_assume_config_once),
obj->window,
obj->cwnd,
obj->initcwnd,
@@ -8296,8 +8318,10 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
NM_CMP_DIRECT(_route_pref_normalize(a->rt_pref), _route_pref_normalize(b->rt_pref));
else
NM_CMP_FIELD(a, b, rt_pref);
- if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL)
+ if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) {
NM_CMP_FIELD_UNSAFE(a, b, is_external);
+ NM_CMP_FIELD_UNSAFE(a, b, r_assume_config_once);
+ }
break;
}
return 0;
diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h
index 22cfb092be..e3f6d0433e 100644
--- a/src/libnm-platform/nm-platform.h
+++ b/src/libnm-platform/nm-platform.h
@@ -325,10 +325,10 @@ typedef enum {
\
bool use_ip4_broadcast_address : 1; \
\
- /* Whether the address is ready to be configured. By default, an address is, but this
- * flag may indicate that the address is just for tracking purpose only, but the ACD
- * state is not yet ready for the address to be configured. */ \
- bool ip4acd_not_ready : 1; \
+ /* Whether the address is should be configured once during assume. This is a meta flag
+ * that is not honored by NMPlatform (netlink code). Instead, it can be used by the upper
+ * layers which use NMPlatformIPAddress to track addresses that should be configured. */ \
+ bool a_assume_config_once : 1; \
;
/**
@@ -351,6 +351,11 @@ typedef struct {
struct _NMPlatformIP4Address {
__NMPlatformIPAddress_COMMON;
+ /* Whether the address is ready to be configured. By default, an address is, but this
+ * flag may indicate that the address is just for tracking purpose only, but the ACD
+ * state is not yet ready for the address to be configured. */
+ bool a_acd_not_ready : 1;
+
/* The local address IFA_LOCAL. */
in_addr_t address;
@@ -473,6 +478,11 @@ typedef union {
* and is not reflected on netlink. */ \
bool is_external : 1; \
\
+ /* Whether the route is should be configured once during assume. This is a meta flag
+ * that is not honored by NMPlatform (netlink code). Instead, it can be used by the upper
+ * layers which use NMPlatformIPRoute to track routes that should be configured. */ \
+ bool r_assume_config_once : 1; \
+ \
/* rtnh_flags
*
* Routes with rtm_flags RTM_F_CLONED are hidden by platform and
diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h
index bf789fb077..6d9060cba4 100644
--- a/src/libnm-platform/nmp-object.h
+++ b/src/libnm-platform/nmp-object.h
@@ -1082,6 +1082,21 @@ nm_platform_lookup_object_by_addr_family(NMPlatform * platform,
/*****************************************************************************/
+static inline gboolean
+nmp_object_get_assume_config_once(const NMPObject *obj)
+{
+ switch (NMP_OBJECT_GET_TYPE(obj)) {
+ case NMP_OBJECT_TYPE_IP4_ADDRESS:
+ case NMP_OBJECT_TYPE_IP6_ADDRESS:
+ return NMP_OBJECT_CAST_IP_ADDRESS(obj)->a_assume_config_once;
+ case NMP_OBJECT_TYPE_IP4_ROUTE:
+ case NMP_OBJECT_TYPE_IP6_ROUTE:
+ return NMP_OBJECT_CAST_IP_ROUTE(obj)->r_assume_config_once;
+ default:
+ return nm_assert_unreachable_val(FALSE);
+ }
+}
+
static inline const char *
nmp_object_link_get_ifname(const NMPObject *obj)
{