summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-10-23 17:12:42 +0200
committerThomas Haller <thaller@redhat.com>2020-10-23 17:12:42 +0200
commitb4ced8b91106e0506a4e27d916c4164287337a8b (patch)
tree79ef8b1dabdc2fef73201dd89c4d60883aab60f1
parentad18612c3644e7797bc173077bfc8731b0b5fd97 (diff)
parent17269b05200aa565b5de7054a224e8ecd5b6aeca (diff)
l3cfg: merge branch 'th/l3cfg-13'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/657
-rw-r--r--Makefile.am2
-rw-r--r--shared/nm-glib-aux/nm-glib.h2
-rw-r--r--shared/nm-std-aux/nm-std-aux.h30
-rw-r--r--shared/nm-utils/nm-test-utils.h25
-rw-r--r--src/meson.build1
-rw-r--r--src/nm-l3-config-data.c2
-rw-r--r--src/nm-l3-ipv4ll.c1001
-rw-r--r--src/nm-l3-ipv4ll.h103
-rw-r--r--src/nm-l3cfg.c184
-rw-r--r--src/nm-l3cfg.h49
-rw-r--r--src/nm-netns.c13
-rw-r--r--src/nm-netns.h2
-rw-r--r--src/platform/nm-platform.c36
-rw-r--r--src/platform/nm-platform.h125
-rw-r--r--src/platform/tests/test-common.c146
-rw-r--r--src/platform/tests/test-common.h9
-rw-r--r--src/tests/test-l3cfg.c336
17 files changed, 1874 insertions, 192 deletions
diff --git a/Makefile.am b/Makefile.am
index 0d02d3ff5..61437e4cb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2124,6 +2124,8 @@ src_libNetworkManagerBase_la_SOURCES = \
src/nm-netns.h \
src/nm-l3-config-data.c \
src/nm-l3-config-data.h \
+ src/nm-l3-ipv4ll.c \
+ src/nm-l3-ipv4ll.h \
src/nm-l3cfg.c \
src/nm-l3cfg.h \
src/nm-ip-config.c \
diff --git a/shared/nm-glib-aux/nm-glib.h b/shared/nm-glib-aux/nm-glib.h
index 5125617d7..7330b007a 100644
--- a/shared/nm-glib-aux/nm-glib.h
+++ b/shared/nm-glib-aux/nm-glib.h
@@ -639,7 +639,7 @@ g_hash_table_steal_extended(GHashTable * hash_table,
gpointer *_stolen_value = (stolen_value); \
\
/* we cannot allow NULL arguments, because then we would leak the values in
- * the compat implementation. */ \
+ * the compat implementation. */ \
g_assert(_stolen_key); \
g_assert(_stolen_value); \
\
diff --git a/shared/nm-std-aux/nm-std-aux.h b/shared/nm-std-aux/nm-std-aux.h
index 762f104be..44d0dd273 100644
--- a/shared/nm-std-aux/nm-std-aux.h
+++ b/shared/nm-std-aux/nm-std-aux.h
@@ -189,6 +189,19 @@ typedef uint64_t _nm_bitwise nm_be64_t;
/*****************************************************************************/
+static inline uint32_t
+nm_add_u32_clamped(uint32_t a, uint32_t b)
+{
+ uint32_t c;
+
+ /* returns the sum of a+b, or UINT32_MAX if the result would overflow. */
+
+ c = a + b;
+ if (c < a)
+ return UINT32_MAX;
+ return c;
+}
+
/* glib's MIN()/MAX() macros don't have function-like behavior, in that they evaluate
* the argument possibly twice.
*
@@ -767,6 +780,21 @@ nm_steal_fd(int *p_fd)
/*****************************************************************************/
+static inline uintptr_t
+nm_ptr_to_uintptr(const void *p)
+{
+ /* in C, pointers can only be compared (with less-than or greater-than) under certain
+ * circumstances. Since uintptr_t is supposed to be able to represent the pointer
+ * as a plain integer and also support to convert the integer back to the pointer,
+ * it should be safer to compare the pointers directly.
+ *
+ * Of course, this function isn't very useful beyond that its use makes it clear
+ * that we want to compare pointers by value, which otherwise may not be valid. */
+ return (uintptr_t) p;
+}
+
+/*****************************************************************************/
+
#define NM_CMP_RETURN(c) \
do { \
const int _cc = (c); \
@@ -813,7 +841,7 @@ nm_steal_fd(int *p_fd)
* Avoid that by casting pointers to void* and then to uintptr_t. This comparison
* is not really meaningful, except that it provides some kind of stable sort order
* between pointers (that can otherwise not be compared). */
-#define NM_CMP_DIRECT_PTR(a, b) NM_CMP_DIRECT((uintptr_t)((void *) (a)), (uintptr_t)((void *) (b)))
+#define NM_CMP_DIRECT_PTR(a, b) NM_CMP_DIRECT(nm_ptr_to_uintptr(a), nm_ptr_to_uintptr(b))
#define NM_CMP_DIRECT_MEMCMP(a, b, size) NM_CMP_RETURN(memcmp((a), (b), (size)))
diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h
index 9d4848829..6c945b192 100644
--- a/shared/nm-utils/nm-test-utils.h
+++ b/shared/nm-utils/nm-test-utils.h
@@ -900,14 +900,16 @@ nmtst_get_rand_uint64(void)
static inline guint
nmtst_get_rand_uint(void)
{
- G_STATIC_ASSERT_EXPR(sizeof(guint32) == sizeof(guint));
- return nmtst_get_rand_uint32();
+ G_STATIC_ASSERT_EXPR((sizeof(guint) == sizeof(guint32) || (sizeof(guint) == sizeof(guint64))));
+ if (sizeof(guint32) == sizeof(guint))
+ return nmtst_get_rand_uint32();
+ return nmtst_get_rand_uint64();
}
static inline gsize
nmtst_get_rand_size(void)
{
- G_STATIC_ASSERT_EXPR(sizeof(gsize) == sizeof(guint32) || sizeof(gsize) == sizeof(guint64));
+ G_STATIC_ASSERT_EXPR((sizeof(gsize) == sizeof(guint32) || (sizeof(gsize) == sizeof(guint64))));
if (sizeof(gsize) == sizeof(guint32))
return nmtst_get_rand_uint32();
return nmtst_get_rand_uint64();
@@ -919,6 +921,17 @@ nmtst_get_rand_bool(void)
return nmtst_get_rand_uint32() % 2;
}
+static inline gboolean
+nmtst_get_rand_one_case_in(guint32 num)
+{
+ /* num=1 doesn't make much sense, because it will always return %TRUE.
+ * Still accept it, it might be that @num is calculated, so 1 might be
+ * a valid edge case. */
+ g_assert(num > 0);
+
+ return (nmtst_get_rand_uint32() % num) == 0;
+}
+
static inline gpointer
nmtst_rand_buf(GRand *rand, gpointer buffer, gsize buffer_length)
{
@@ -1191,8 +1204,12 @@ _nmtst_main_loop_quit_on_notify(GObject *object, GParamSpec *pspec, gpointer use
nm_auto_destroy_and_unref_gsource GSource *_source = NULL; \
GMainContext * _context = (context); \
gboolean _had_timeout = FALSE; \
+ typeof(timeout_msec) _timeout_msec0 = (timeout_msec); \
+ gint64 _timeout_msec = _timeout_msec0; \
+ \
+ g_assert_cmpint(_timeout_msec0, ==, _timeout_msec); \
\
- _source = g_timeout_source_new(timeout_msec); \
+ _source = g_timeout_source_new(NM_CLAMP(_timeout_msec, 0, (gint64) G_MAXUINT)); \
g_source_set_callback(_source, nmtst_g_source_set_boolean_true, &_had_timeout, NULL); \
g_source_attach(_source, _context); \
\
diff --git a/src/meson.build b/src/meson.build
index 667a6a2ce..b3eddadcd 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -45,6 +45,7 @@ sources = files(
'nm-dbus-utils.c',
'nm-netns.c',
'nm-l3-config-data.c',
+ 'nm-l3-ipv4ll.c',
'nm-l3cfg.c',
'nm-ip-config.c',
'nm-ip4-config.c',
diff --git a/src/nm-l3-config-data.c b/src/nm-l3-config-data.c
index 864924e88..9b1e017b1 100644
--- a/src/nm-l3-config-data.c
+++ b/src/nm-l3-config-data.c
@@ -2600,7 +2600,7 @@ nm_l3_config_data_merge(NML3ConfigData * self,
if (r_src->metric_any) {
_ensure_r();
r.rx.metric_any = FALSE;
- r.rx.metric_any = default_route_metric_x[IS_IPv4];
+ r.rx.metric = nm_add_u32_clamped(r.rx.metric, default_route_metric_x[IS_IPv4]);
}
if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r_src)) {
diff --git a/src/nm-l3-ipv4ll.c b/src/nm-l3-ipv4ll.c
new file mode 100644
index 000000000..c5dd7ec88
--- /dev/null
+++ b/src/nm-l3-ipv4ll.c
@@ -0,0 +1,1001 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "nm-default.h"
+
+#include "nm-l3-ipv4ll.h"
+
+#include <net/if.h>
+
+#include "n-acd/src/n-acd.h"
+#include "nm-core-utils.h"
+
+#define ADDR_IPV4LL_PREFIX_LEN 16
+
+/*****************************************************************************/
+
+struct _NML3IPv4LLRegistration {
+ NML3IPv4LL *self;
+ CList reg_lst;
+ guint timeout_msec;
+};
+
+G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3IPv4LLRegistration, self) == 0);
+
+struct _NML3IPv4LL {
+ NML3Cfg * l3cfg;
+ int ref_count;
+ in_addr_t addr;
+ guint reg_timeout_msec;
+ CList reg_lst_head;
+ NML3CfgCommitTypeHandle *l3cfg_commit_handle;
+ GSource * state_change_on_idle_source;
+ const NML3ConfigData * l3cd;
+ const NMPObject * plobj;
+ struct {
+ nm_le64_t value;
+ nm_le64_t generation;
+ } seed;
+ gulong l3cfg_signal_notify_id;
+ gint64 last_good_at_msec : 1;
+ NML3IPv4LLState state;
+ NMEtherAddr seed_mac;
+ NMEtherAddr mac;
+ bool seed_set : 1;
+ bool seed_reset_generation : 1;
+ bool mac_set : 1;
+ bool link_seen_not_ready : 1;
+ bool notify_on_idle : 1;
+ bool reg_changed : 1;
+ bool l3cd_timeout_msec_changed : 1;
+};
+
+G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3IPv4LL, ref_count) == sizeof(gpointer));
+
+#define L3CD_TAG(self) (&(((const char *) self)[1]))
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN LOGD_IP4
+#define _NMLOG_PREFIX_NAME "ipv4ll"
+#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
+
+/*****************************************************************************/
+
+static void _ipv4ll_state_change_on_idle(NML3IPv4LL *self);
+
+static void _ipv4ll_state_change(NML3IPv4LL *self, gboolean is_on_idle_handler);
+
+/*****************************************************************************/
+
+NM_UTILS_ENUM2STR_DEFINE(nm_l3_ipv4ll_state_to_string,
+ NML3IPv4LLState,
+ NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_UNKNOWN, "unknown"),
+ NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_DISABLED, "disabled"),
+ NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_WAIT_FOR_LINK, "wait-for-link"),
+ NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_EXTERNAL, "external"),
+ NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_PROBING, "probing"),
+ NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_DEFENDING, "defending"),
+ NM_UTILS_ENUM2STR(NM_L3_IPV4LL_STATE_READY, "ready"), );
+
+/*****************************************************************************/
+
+#define _ASSERT(self) \
+ G_STMT_START \
+ { \
+ NML3IPv4LL *const _self = (self); \
+ \
+ nm_assert(NM_IS_L3_IPV4LL(_self)); \
+ if (NM_MORE_ASSERTS > 5) { \
+ nm_assert(_self->addr == 0u || nm_utils_ip4_address_is_link_local(_self->addr)); \
+ nm_assert(!_self->l3cd || NM_IS_L3_CONFIG_DATA(_self->l3cd)); \
+ } \
+ } \
+ G_STMT_END
+
+/*****************************************************************************/
+
+NML3Cfg *
+nm_l3_ipv4ll_get_l3cfg(NML3IPv4LL *self)
+{
+ nm_assert(NM_IS_L3_IPV4LL(self));
+
+ return self->l3cfg;
+}
+
+int
+nm_l3_ipv4ll_get_ifindex(NML3IPv4LL *self)
+{
+ nm_assert(NM_IS_L3_IPV4LL(self));
+
+ return nm_l3cfg_get_ifindex(self->l3cfg);
+}
+
+NMPlatform *
+nm_l3_ipv4ll_get_platform(NML3IPv4LL *self)
+{
+ nm_assert(NM_IS_L3_IPV4LL(self));
+
+ return nm_l3cfg_get_platform(self->l3cfg);
+}
+
+/*****************************************************************************/
+
+NML3IPv4LLState
+nm_l3_ipv4ll_get_state(NML3IPv4LL *self)
+{
+ nm_assert(NM_IS_L3_IPV4LL(self));
+
+ return self->state;
+}
+
+in_addr_t
+nm_l3_ipv4ll_get_addr(NML3IPv4LL *self)
+{
+ nm_assert(NM_IS_L3_IPV4LL(self));
+
+ return self->addr;
+}
+
+const NML3ConfigData *
+nm_l3_ipv4ll_get_l3cd(NML3IPv4LL *self)
+{
+ nm_assert(NM_IS_L3_IPV4LL(self));
+
+ return self->l3cd;
+}
+
+/*****************************************************************************/
+
+static NML3IPv4LLRegistration *
+_registration_update(NML3IPv4LL * self,
+ NML3IPv4LLRegistration *reg,
+ gboolean add,
+ guint timeout_msec)
+{
+ nm_auto_unref_l3ipv4ll NML3IPv4LL *self_unref_on_exit = NULL;
+ gboolean changed = FALSE;
+
+ if (reg) {
+ nm_assert(!self);
+ _ASSERT(reg->self);
+ self = reg->self;
+ nm_assert(c_list_contains(&self->reg_lst_head, &reg->reg_lst));
+ nm_assert(self == nm_l3_ipv4ll_register_get_instance(reg));
+ } else {
+ nm_assert(add);
+ _ASSERT(self);
+ }
+
+ if (!add) {
+ _LOGT("registration[" NM_HASH_OBFUSCATE_PTR_FMT "]: remove", NM_HASH_OBFUSCATE_PTR(reg));
+ c_list_unlink_stale(&reg->reg_lst);
+ if (c_list_is_empty(&self->reg_lst_head))
+ self_unref_on_exit = self;
+ nm_g_slice_free(reg);
+ reg = NULL;
+ goto out;
+ }
+
+ if (!reg) {
+ reg = g_slice_new(NML3IPv4LLRegistration);
+ *reg = (NML3IPv4LLRegistration){
+ .self = self,
+ .timeout_msec = timeout_msec,
+ };
+
+ if (c_list_is_empty(&self->reg_lst_head))
+ nm_l3_ipv4ll_ref(self);
+ c_list_link_tail(&self->reg_lst_head, &reg->reg_lst);
+ changed = TRUE;
+ _LOGT("registration[" NM_HASH_OBFUSCATE_PTR_FMT "]: add (timeout_msec=%u)",
+ NM_HASH_OBFUSCATE_PTR(reg),
+ timeout_msec);
+ } else {
+ if (reg->timeout_msec != timeout_msec) {
+ reg->timeout_msec = timeout_msec;
+ changed = TRUE;
+ }
+ if (changed) {
+ _LOGT("registration[" NM_HASH_OBFUSCATE_PTR_FMT "]: update (timeout_msec=%u)",
+ NM_HASH_OBFUSCATE_PTR(reg),
+ timeout_msec);
+ }
+ }
+
+out:
+ if (changed) {
+ self->reg_changed = TRUE;
+ _ipv4ll_state_change(self, FALSE);
+ }
+ return reg;
+}
+
+NML3IPv4LLRegistration *
+nm_l3_ipv4ll_register_new(NML3IPv4LL *self, guint timeout_msec)
+{
+ return _registration_update(self, NULL, TRUE, timeout_msec);
+}
+
+NML3IPv4LLRegistration *
+nm_l3_ipv4ll_register_update(NML3IPv4LLRegistration *reg, guint timeout_msec)
+{
+ return _registration_update(NULL, reg, TRUE, timeout_msec);
+}
+
+NML3IPv4LLRegistration *
+nm_l3_ipv4ll_register_remove(NML3IPv4LLRegistration *reg)
+{
+ return _registration_update(NULL, reg, FALSE, 0);
+}
+
+/*****************************************************************************/
+
+static gboolean
+_ip4_address_is_link_local(const NMPlatformIP4Address *a)
+{
+ nm_assert(a);
+
+ return nm_utils_ip4_address_is_link_local(a->address) && a->plen == ADDR_IPV4LL_PREFIX_LEN
+ && a->address == a->peer_address;
+}
+
+static gboolean
+_acd_info_is_good(const NML3AcdAddrInfo *acd_info)
+{
+ if (!acd_info)
+ return TRUE;
+
+ switch (acd_info->state) {
+ case NM_L3_ACD_ADDR_STATE_INIT:
+ case NM_L3_ACD_ADDR_STATE_PROBING:
+ case NM_L3_ACD_ADDR_STATE_READY:
+ case NM_L3_ACD_ADDR_STATE_DEFENDING:
+ case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+ return TRUE;
+ case NM_L3_ACD_ADDR_STATE_USED:
+ case NM_L3_ACD_ADDR_STATE_CONFLICT:
+ return FALSE;
+ }
+ nm_assert_not_reached();
+ return FALSE;
+}
+
+static gboolean
+_plobj_link_is_ready(const NMPObject *plobj)
+{
+ const NMPlatformLink *pllink;
+
+ if (!plobj)
+ return FALSE;
+
+ pllink = NMP_OBJECT_CAST_LINK(plobj);
+ if (!NM_FLAGS_HAS(pllink->n_ifi_flags, IFF_UP))
+ return FALSE;
+ if (pllink->l_address.len != ETH_ALEN)
+ return FALSE;
+
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+static NMPlatformIP4Address *
+_l3cd_config_plat_init_addr(NMPlatformIP4Address *a, int ifindex, in_addr_t addr)
+{
+ nm_assert(nm_utils_ip4_address_is_link_local(addr));
+
+ *a = (NMPlatformIP4Address){
+ .ifindex = ifindex,
+ .address = addr,
+ .peer_address = addr,
+ .plen = ADDR_IPV4LL_PREFIX_LEN,
+ .addr_source = NM_IP_CONFIG_SOURCE_IP4LL,
+ };
+ return a;
+}
+
+static NMPlatformIP4Route *
+_l3cd_config_plat_init_route(NMPlatformIP4Route *r, int ifindex)
+{
+ *r = (NMPlatformIP4Route){
+ .ifindex = ifindex,
+ .network = htonl(0xE0000000u),
+ .plen = 4,
+ .rt_source = NM_IP_CONFIG_SOURCE_IP4LL,
+ .table_any = TRUE,
+ .metric_any = TRUE,
+ };
+ return r;
+}
+
+static const NML3ConfigData *
+_l3cd_config_create(int ifindex, in_addr_t addr, NMDedupMultiIndex *multi_idx)
+{
+ nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
+ NMPlatformIP4Address a;
+ NMPlatformIP4Route r;
+
+ nm_assert(nm_utils_ip4_address_is_link_local(addr));
+ nm_assert(ifindex > 0);
+ nm_assert(multi_idx);
+
+ l3cd = nm_l3_config_data_new(multi_idx, ifindex);
+ nm_l3_config_data_set_source(l3cd, NM_IP_CONFIG_SOURCE_IP4LL);
+
+ nm_l3_config_data_add_address_4(l3cd, _l3cd_config_plat_init_addr(&a, ifindex, addr));
+ nm_l3_config_data_add_route_4(l3cd, _l3cd_config_plat_init_route(&r, ifindex));
+
+ return nm_l3_config_data_seal(g_steal_pointer(&l3cd));
+}
+
+static in_addr_t
+_l3cd_config_get_addr(const NML3ConfigData *l3cd)
+{
+ NMDedupMultiIter iter;
+ const NMPlatformIP4Address *pladdr;
+
+ if (!l3cd)
+ return 0u;
+
+ nm_l3_config_data_iter_ip4_address_for_each (&iter, l3cd, &pladdr) {
+ const in_addr_t addr = pladdr->address;
+
+ nm_assert(_ip4_address_is_link_local(pladdr));
+#if NM_MORE_ASSERTS > 10
+ {
+ nm_auto_unref_l3cd const NML3ConfigData *l3cd2 = NULL;
+
+ l3cd2 = _l3cd_config_create(nm_l3_config_data_get_ifindex(l3cd),
+ addr,
+ nm_l3_config_data_get_multi_idx(l3cd));
+ nm_assert(nm_l3_config_data_equal(l3cd2, l3cd));
+ }
+#endif
+ return addr;
+ }
+
+ return nm_assert_unreachable_val(0u);
+}
+
+/*****************************************************************************/
+
+static void
+_ipv4ll_addrgen(NML3IPv4LL *self, gboolean generate_new_addr)
+{
+ CSipHash state;
+ char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+ gboolean seed_changed = FALSE;
+ in_addr_t addr_new;
+ guint64 h;
+
+ _ASSERT(self);
+
+ /* MAC_HASH_KEY is the same as used by systemd. */
+#define MAC_HASH_KEY \
+ ((const guint8[16]){0xdf, \
+ 0x04, \
+ 0x22, \
+ 0x98, \
+ 0x3f, \
+ 0xad, \
+ 0x14, \
+ 0x52, \
+ 0xf9, \
+ 0x87, \
+ 0x2e, \
+ 0xd1, \
+ 0x9c, \
+ 0x70, \
+ 0xe2, \
+ 0xf2})
+
+ if (self->mac_set && (!self->seed_set || !nm_ether_addr_equal(&self->mac, &self->seed_mac))) {
+ /* systemd's ipv4ll library by default only hashes the MAC address (as we do here).
+ * This is also what previous versions of NetworkManager did (whenn using sd_ipv4ll).
+ *
+ * On the other hand, systemd-networkd uses net_get_name_persistent() of the device
+ * mixed with /etc/machine-id.
+ *
+ * See also: https://tools.ietf.org/html/rfc3927#section-2.1
+ *
+ * FIXME(l3cfg): At this point, maybe we should also mix it with nm_utils_host_id_get().
+ * This would get the behavior closer to what systemd-networkd does.
+ * Don't do that for now, because it would be a change in behavior compared
+ * to earlier versions of NetworkManager. */
+
+ c_siphash_init(&state, MAC_HASH_KEY);
+ c_siphash_append(&state, self->mac.ether_addr_octet, ETH_ALEN);
+ h = c_siphash_finalize(&state);
+
+ _LOGT("addr-gen: %sset seed (for " NM_ETHER_ADDR_FORMAT_STR ")",
+ self->seed_set ? "re" : "",
+ NM_ETHER_ADDR_FORMAT_VAL(&self->mac));
+
+ self->seed_set = TRUE;
+ self->seed_mac = self->mac;
+ self->seed.generation = htole64(0);
+ self->seed.value = htole64(h);
+ self->seed_reset_generation = FALSE;
+ self->addr = 0u;
+
+ seed_changed = TRUE;
+ }
+
+ if (!self->seed_set) {
+ /* we have no seed set (and consequently no MAC address set either).
+ * We cannot generate an address. */
+ nm_assert(self->addr == 0u);
+ return;
+ }
+
+ nm_assert(seed_changed || self->seed.generation != htole64(0u));
+
+ if (self->seed_reset_generation) {
+ _LOGT("addr-gen: reset seed (generation only)");
+ self->seed.generation = htole64(0);
+ self->addr = 0u;
+ seed_changed = TRUE;
+ }
+
+ if (!seed_changed && !generate_new_addr) {
+ /* neither did the caller request a new address, nor was the seed changed. The current
+ * address is still to be used. */
+ nm_assert(nm_utils_ip4_address_is_link_local(self->addr));
+ return;
+ }
+
+gen_addr:
+
+#define PICK_HASH_KEY \
+ ((const guint8[16]){0x15, \
+ 0xac, \
+ 0x82, \
+ 0xa6, \
+ 0xd6, \
+ 0x3f, \
+ 0x49, \
+ 0x78, \
+ 0x98, \
+ 0x77, \
+ 0x5d, \
+ 0x0c, \
+ 0x69, \
+ 0x02, \
+ 0x94, \
+ 0x0b})
+
+ h = c_siphash_hash(PICK_HASH_KEY, (const guint8 *) &self->seed, sizeof(self->seed));
+
+ self->seed.generation = htole64(le64toh(self->seed.generation) + 1u);
+
+ addr_new = htonl(h & UINT32_C(0x0000FFFF)) | NM_IPV4LL_NETWORK;
+
+ if (self->addr == addr_new || NM_IN_SET(ntohl(addr_new) & 0x0000FF00u, 0x0000u, 0xFF00u))
+ goto gen_addr;
+
+ nm_assert(nm_utils_ip4_address_is_link_local(addr_new));
+
+ _LOGT("addr-gen: set address %s", _nm_utils_inet4_ntop(addr_new, sbuf_addr));
+ self->addr = addr_new;
+}
+
+/*****************************************************************************/
+
+static void
+_ipv4ll_update_link(NML3IPv4LL *self, const NMPObject *plobj)
+{
+ char sbuf[ETH_ALEN * 3];
+ nm_auto_nmpobj const NMPObject *pllink_old = NULL;
+ const NMEtherAddr * mac_new;
+ gboolean changed;
+
+ if (self->plobj == plobj)
+ return;
+
+ pllink_old = g_steal_pointer(&self->plobj);
+ self->plobj = nmp_object_ref(plobj);
+
+ mac_new = NULL;
+ if (plobj) {
+ const NMPlatformLink *pllink = NMP_OBJECT_CAST_LINK(plobj);
+
+ if (pllink->l_address.len == ETH_ALEN)
+ mac_new = &pllink->l_address.ether_addr;
+ }
+
+ changed = FALSE;
+ if (!mac_new) {
+ if (self->mac_set) {
+ changed = TRUE;
+ self->mac_set = FALSE;
+ }
+ } else {
+ if (!self->mac_set || !nm_ether_addr_equal(mac_new, &self->mac)) {
+ changed = TRUE;
+ self->mac_set = TRUE;
+ self->mac = *mac_new;
+ }
+ }
+
+ if (changed) {
+ _LOGT("mac changed: %s",
+ self->mac_set ? _nm_utils_hwaddr_ntoa(&self->mac, ETH_ALEN, TRUE, sbuf, sizeof(sbuf))
+ : "unset");
+ }
+}
+
+/*****************************************************************************/
+
+static void
+_l3cd_config_add(NML3IPv4LL *self)
+{
+ nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
+ char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+ gboolean changed;
+
+ _ASSERT(self);
+ nm_assert(self->addr != 0u);
+ nm_assert(self->reg_timeout_msec > 0u);
+
+ if (_l3cd_config_get_addr(self->l3cd) != self->addr) {
+ l3cd = _l3cd_config_create(nm_l3_ipv4ll_get_ifindex(self),
+ self->addr,
+ nm_l3cfg_get_multi_idx(self->l3cfg));
+ nm_assert(!nm_l3_config_data_equal(l3cd, self->l3cd));
+ changed = TRUE;
+ } else
+ changed = FALSE;
+
+ if (!changed && !self->l3cd_timeout_msec_changed)
+ return;
+
+ self->l3cd_timeout_msec_changed = FALSE;
+
+ _LOGT("add l3cd config with %s (acd-timeout %u msec%s)",
+ _nm_utils_inet4_ntop(self->addr, sbuf_addr),
+ self->reg_timeout_msec,
+ changed ? "" : " changed");
+
+ if (changed) {
+ NM_SWAP(&l3cd, &self->l3cd);
+ self->notify_on_idle = TRUE;
+ }
+
+ if (!nm_l3cfg_add_config(self->l3cfg,
+ L3CD_TAG(self),
+ TRUE,
+ self->l3cd,
+ NM_L3CFG_CONFIG_PRIORITY_IPV4LL,
+ 0,
+ 0,
+ NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4,
+ NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
+ 0,
+ 0,
+ NM_L3_ACD_DEFEND_TYPE_ONCE,
+ self->reg_timeout_msec,
+ NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD))
+ nm_assert_not_reached();
+
+ self->l3cfg_commit_handle = nm_l3cfg_commit_type_register(self->l3cfg,
+ NM_L3_CFG_COMMIT_TYPE_ASSUME,
+ self->l3cfg_commit_handle);
+ nm_l3cfg_commit_on_idle_schedule(self->l3cfg);
+}
+
+static gboolean
+_l3cd_config_remove(NML3IPv4LL *self)
+{
+ nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
+
+ nm_assert(NM_IS_L3_IPV4LL(self));
+
+ if (!self->l3cd)
+ return FALSE;
+
+ _LOGT("remove l3cd config");
+
+ self->notify_on_idle = TRUE;
+
+ l3cd = g_steal_pointer(&self->l3cd);
+ if (!nm_l3cfg_remove_config(self->l3cfg, L3CD_TAG(self), l3cd))
+ nm_assert_not_reached();
+
+ nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle));
+ nm_l3cfg_commit_on_idle_schedule(self->l3cfg);
+ return TRUE;
+}
+
+/*****************************************************************************/
+
+static const NMPlatformIP4Address *
+_ipv4ll_platform_ip4_address_lookup(NML3IPv4LL *self, in_addr_t addr)
+{
+ const NMPlatformIP4Address *pladdr;
+
+ if (addr == 0u)
+ return NULL;
+
+ nm_assert(nm_utils_ip4_address_is_link_local(addr));
+
+ pladdr = nm_platform_ip4_address_get(nm_l3_ipv4ll_get_platform(self),
+ nm_l3_ipv4ll_get_ifindex(self),
+ addr,
+ ADDR_IPV4LL_PREFIX_LEN,
+ addr);
+
+ nm_assert(!pladdr || pladdr->address == addr);
+ nm_assert(!pladdr || _ip4_address_is_link_local(pladdr));
+ return pladdr;
+}
+
+static const NML3AcdAddrInfo *
+_ipv4ll_l3cfg_get_acd_addr_info(NML3IPv4LL *self, in_addr_t addr)
+{
+ if (addr == 0u)
+ return NULL;
+
+ nm_assert(nm_utils_ip4_address_is_link_local(addr));
+ return nm_l3cfg_get_acd_addr_info(self->l3cfg, addr);
+}
+
+static const NMPlatformIP4Address *
+_ipv4ll_platform_find_addr(NML3IPv4LL *self, const NML3AcdAddrInfo **out_acd_info)
+{
+ const NMPlatformIP4Address *addr_without_acd_info = NULL;
+ NMDedupMultiIter iter;
+ NMPLookup lookup;
+ const NMPObject * obj;
+ const NML3AcdAddrInfo * acd_info;
+ const NMPlatformIP4Address *addr;
+
+ nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, nm_l3_ipv4ll_get_ifindex(self));
+ nm_platform_iter_obj_for_each (&iter, nm_l3_ipv4ll_get_platform(self), &lookup, &obj) {
+ addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj);
+ if (!_ip4_address_is_link_local(addr))
+ continue;
+
+ acd_info = _ipv4ll_l3cfg_get_acd_addr_info(self, addr->address);
+ if (!_acd_info_is_good(acd_info))
+ continue;
+
+ if (acd_info) {
+ /* We have a good acd_info. We won't find a better one. Return it. */
+ NM_SET_OUT(out_acd_info, acd_info);
+ return addr;
+ }
+
+ if (!addr_without_acd_info) {
+ /* remember a potential candidate address that has no acd_info. */
+ addr_without_acd_info = addr;
+ }
+ }
+
+ if (addr_without_acd_info) {
+ NM_SET_OUT(out_acd_info, NULL);
+ return addr_without_acd_info;
+ }
+
+ return NULL;
+}
+
+/*****************************************************************************/
+
+static gboolean
+_ipv4ll_set_state(NML3IPv4LL *self, NML3IPv4LLState state)
+{
+ char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+ char sbuf100[100];
+
+ if (self->state == state)
+ return FALSE;
+ self->state = state;
+ self->notify_on_idle = TRUE;
+ _LOGT("state: set state %s (addr=%s)",
+ nm_l3_ipv4ll_state_to_string(state, sbuf100, sizeof(sbuf100)),
+ _nm_utils_inet4_ntop(self->addr, sbuf_addr));
+ return TRUE;
+}
+
+static void
+_ipv4ll_state_change(NML3IPv4LL *self, gboolean is_on_idle_handler)
+{
+ nm_auto_unref_l3ipv4ll NML3IPv4LL *self_keep_alive = NULL;
+ const NMPlatformIP4Address * pladdr;
+ const NML3AcdAddrInfo * acd_info;
+ gboolean generate_new_addr;
+ NML3IPv4LLState new_state;
+ in_addr_t addr0;
+ NML3IPv4LLRegistration * reg;
+
+ _ASSERT(self);
+
+ self_keep_alive = nm_l3_ipv4ll_ref(self);
+
+ nm_clear_g_source_inst(&self->state_change_on_idle_source);
+
+ addr0 = self->addr;
+
+ if (self->reg_changed) {
+ guint timeout_msec = self->reg_timeout_msec;
+
+ if (c_list_is_empty(&self->reg_lst_head))
+ timeout_msec = 0;
+ else {
+ timeout_msec = G_MAXUINT;
+ c_list_for_each_entry (reg, &self->reg_lst_head, reg_lst) {
+ if (reg->timeout_msec < timeout_msec)
+ timeout_msec = reg->timeout_msec;
+ if (reg->timeout_msec == 0)
+ break;
+ }
+ }
+ if (self->reg_timeout_msec != timeout_msec) {
+ self->reg_timeout_msec = timeout_msec;
+ self->l3cd_timeout_msec_changed = TRUE;
+ }
+ }
+
+ if (self->reg_timeout_msec == 0) {
+ if (_ipv4ll_set_state(self, NM_L3_IPV4LL_STATE_DISABLED))
+ _l3cd_config_remove(self);
+ goto out_notify;
+ }
+
+ if (!self->mac_set) {
+ if (_ipv4ll_set_state(self, NM_L3_IPV4LL_STATE_WAIT_FOR_LINK))
+ _l3cd_config_remove(self);
+ else
+ nm_assert(!self->l3cd);
+ goto out_notify;
+ }
+
+ if (self->state <= NM_L3_IPV4LL_STATE_EXTERNAL) {
+ pladdr = _ipv4ll_platform_ip4_address_lookup(self, self->addr);
+ if (pladdr) {
+ if (!_acd_info_is_good(_ipv4ll_l3cfg_get_acd_addr_info(self, self->addr)))
+ pladdr = NULL;
+ }
+ if (!pladdr)
+ pladdr = _ipv4ll_platform_find_addr(self, NULL);
+
+ if (pladdr) {
+ /* we have an externally configured address. Check whether we can use it. */
+ goto out_set_external_pladdr;
+ }
+ }
+
+ generate_new_addr = FALSE;
+ while (TRUE) {
+ _ipv4ll_addrgen(self, generate_new_addr);
+ acd_info = _ipv4ll_l3cfg_get_acd_addr_info(self, self->addr);
+ if (_acd_info_is_good(acd_info))
+ goto out_is_good;
+ generate_new_addr = TRUE;
+ }
+
+out_set_external_pladdr:
+ self->addr = pladdr->address;
+ _ipv4ll_set_state(self, NM_L3_IPV4LL_STATE_EXTERNAL);
+ _l3cd_config_add(self);
+ self->notify_on_idle = TRUE;
+ goto out_notify;
+
+out_is_good:
+ nm_assert(_acd_info_is_good(acd_info));
+ switch (acd_info ? acd_info->state : NM_L3_ACD_ADDR_STATE_INIT) {
+ case NM_L3_ACD_ADDR_STATE_INIT:
+ case NM_L3_ACD_ADDR_STATE_PROBING:
+ new_state = NM_L3_IPV4LL_STATE_PROBING;
+ goto out_is_good_1;
+ case NM_L3_ACD_ADDR_STATE_READY:
+ new_state = NM_L3_IPV4LL_STATE_READY;
+ goto out_is_good_1;
+ case NM_L3_ACD_ADDR_STATE_DEFENDING:
+ new_state = NM_L3_IPV4LL_STATE_DEFENDING;
+ goto out_is_good_1;
+ case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+ case NM_L3_ACD_ADDR_STATE_USED:
+ case NM_L3_ACD_ADDR_STATE_CONFLICT:
+ nm_assert_not_reached();
+ goto out_notify;
+ }
+ nm_assert_not_reached();
+ goto out_notify;
+out_is_good_1:
+ _ipv4ll_set_state(self, new_state);
+ _l3cd_config_add(self);
+ if (self->addr != addr0)
+ self->notify_on_idle = TRUE;
+ goto out_notify;
+
+out_notify:
+ if (self->notify_on_idle) {
+ if (is_on_idle_handler) {
+ NML3ConfigNotifyData notify_data;
+
+ self->notify_on_idle = FALSE;
+
+ notify_data.notify_type = NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT;
+ notify_data.ipv4ll_event = (typeof(notify_data.ipv4ll_event)){
+ .ipv4ll = self,
+ };
+ _nm_l3cfg_emit_signal_notify(self->l3cfg, &notify_data);
+ } else
+ _ipv4ll_state_change_on_idle(self);
+ }
+}
+
+static gboolean
+_ipv4ll_state_change_on_idle_cb(gpointer user_data)
+{
+ NML3IPv4LL *self = user_data;
+
+ _ipv4ll_state_change(self, TRUE);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+_ipv4ll_state_change_on_idle(NML3IPv4LL *self)
+{
+ nm_assert(NM_IS_L3_IPV4LL(self));
+
+ if (!self->state_change_on_idle_source) {
+ self->state_change_on_idle_source =
+ nm_g_idle_source_new(G_PRIORITY_DEFAULT, _ipv4ll_state_change_on_idle_cb, self, NULL);
+ g_source_attach(self->state_change_on_idle_source, NULL);
+ }
+}
+
+/*****************************************************************************/
+
+void
+nm_l3_ipv4ll_restart(NML3IPv4LL *self)
+{
+ nm_assert(NM_IS_L3_IPV4LL(self));
+
+ self->seed_reset_generation = TRUE;
+ _ipv4ll_state_change(self, FALSE);
+}
+
+/*****************************************************************************/
+
+static void
+_l3cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NML3IPv4LL *self)
+{
+ if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE) {
+ const NMPObject *obj = notify_data->platform_change.obj;
+
+ /* we only process the link changes on the idle handler. That means, we may miss
+ * events. If we saw the link down for a moment, remember it. Note that netlink
+ * anyway can loose signals, so we might still miss to see the link down. This
+ * is as good as we get it. */
+ if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LINK) {
+ if (notify_data->platform_change.change_type == NM_PLATFORM_SIGNAL_REMOVED)
+ self->link_seen_not_ready = TRUE;
+ else if (!_plobj_link_is_ready(obj))
+ self->link_seen_not_ready = TRUE;
+ }
+ return;
+ }
+
+ if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE) {
+ /* NMl3Cfg only reloads the platform link during the idle handler. Pick it up now. */
+ _ipv4ll_update_link(self, nm_l3cfg_get_plobj(l3cfg, FALSE));
+
+ /* theoretically, this even is already on an idle handler. However, we share
+ * the call with other signal handlers, so at this point we don't want to
+ * emit additional signals. Thus pass %FALSE to _ipv4ll_state_change(). */
+ _ipv4ll_state_change(self, FALSE);
+ return;
+ }
+
+ if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) {
+ if (self->l3cd
+ && nm_l3_acd_addr_info_find_track_info(&notify_data->acd_event.info,
+ L3CD_TAG(self),
+ self->l3cd,
+ NULL)) {
+ _ipv4ll_state_change(self, FALSE);
+ }
+ return;
+ }
+}
+
+/*****************************************************************************/
+
+NML3IPv4LL *
+nm_l3_ipv4ll_new(NML3Cfg *l3cfg)
+{
+ NML3IPv4LL *self;
+
+ g_return_val_if_fail(NM_IS_L3CFG(l3cfg), NULL);
+
+ self = g_slice_new(NML3IPv4LL);
+ *self = (NML3IPv4LL){
+ .l3cfg = g_object_ref(l3cfg),
+ .ref_count = 1,
+ .reg_lst_head = C_LIST_INIT(self->reg_lst_head),
+ .l3cfg_commit_handle = NULL,
+ .state_change_on_idle_source = NULL,
+ .l3cd = NULL,
+ .plobj = NULL,
+ .addr = 0u,
+ .state = NM_L3_IPV4LL_STATE_DISABLED,
+ .reg_timeout_msec = 0,
+ .notify_on_idle = TRUE,
+ .l3cfg_signal_notify_id =
+ g_signal_connect(l3cfg, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_l3cfg_notify_cb), self),
+ .last_good_at_msec = 0,
+ .link_seen_not_ready = FALSE,
+ .seed_set = FALSE,
+ .seed_reset_generation = FALSE,
+ };
+
+ _LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(l3cfg));
+
+ _ipv4ll_update_link(self, nm_l3cfg_get_plobj(l3cfg, FALSE));
+ _ipv4ll_state_change(self, FALSE);
+ return self;
+}
+
+NML3IPv4LL *
+nm_l3_ipv4ll_ref(NML3IPv4LL *self)
+{
+ if (!self)
+ return NULL;
+
+ _ASSERT(self);
+
+ nm_assert(self->ref_count < G_MAXINT);
+ self->ref_count++;
+ return self;
+}
+
+void
+nm_l3_ipv4ll_unref(NML3IPv4LL *self)
+{
+ if (!self)
+ return;
+
+ _ASSERT(self);
+
+ if (--self->ref_count > 0)
+ return;
+
+ _LOGT("finalize");
+
+ nm_assert(c_list_is_empty(&self->reg_lst_head));
+
+ if (self->l3cd) {
+ nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
+
+ l3cd = g_steal_pointer(&self->l3cd);
+ if (!nm_l3cfg_remove_config(self->l3cfg, L3CD_TAG(self), l3cd))
+ nm_assert_not_reached();
+
+ nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle));
+ nm_l3cfg_commit_on_idle_schedule(self->l3cfg);
+ } else
+ nm_assert(!self->l3cfg_commit_handle);
+
+ nm_clear_g_source_inst(&self->state_change_on_idle_source);
+
+ nm_clear_g_signal_handler(self->l3cfg, &self->l3cfg_signal_notify_id);
+
+ g_clear_object(&self->l3cfg);
+ nmp_object_unref(self->plobj);
+ nm_g_slice_free(self);
+}
diff --git a/src/nm-l3-ipv4ll.h b/src/nm-l3-ipv4ll.h
new file mode 100644
index 000000000..1d45b0c51
--- /dev/null
+++ b/src/nm-l3-ipv4ll.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#ifndef __NM_L3_IPV4LL_H__
+#define __NM_L3_IPV4LL_H__
+
+#include "nm-l3cfg.h"
+
+/*****************************************************************************/
+
+typedef enum _nm_packed {
+ NM_L3_IPV4LL_STATE_UNKNOWN,
+ NM_L3_IPV4LL_STATE_DISABLED,
+ NM_L3_IPV4LL_STATE_WAIT_FOR_LINK,
+ NM_L3_IPV4LL_STATE_EXTERNAL,
+ NM_L3_IPV4LL_STATE_PROBING,
+ NM_L3_IPV4LL_STATE_READY,
+ NM_L3_IPV4LL_STATE_DEFENDING,
+} NML3IPv4LLState;
+
+const char *nm_l3_ipv4ll_state_to_string(NML3IPv4LLState val, char *buf, gsize len);
+
+/*****************************************************************************/
+
+typedef struct _NML3IPv4LL NML3IPv4LL;
+
+static inline gboolean
+NM_IS_L3_IPV4LL(const NML3IPv4LL *self)
+{
+ nm_assert(!self
+ || (NM_IS_L3CFG(*((NML3Cfg **) self))
+ && (*((int *) (((char *) self) + sizeof(gpointer)))) > 0));
+ return !!self;
+}
+
+NML3IPv4LL *nm_l3_ipv4ll_new(NML3Cfg *self);
+
+NML3IPv4LL *nm_l3_ipv4ll_ref(NML3IPv4LL *self);
+void nm_l3_ipv4ll_unref(NML3IPv4LL *self);
+
+NM_AUTO_DEFINE_FCN0(NML3IPv4LL *, _nm_auto_unref_l3ipv4ll, nm_l3_ipv4ll_unref);
+#define nm_auto_unref_l3ipv4ll nm_auto(_nm_auto_unref_l3ipv4ll)
+
+/*****************************************************************************/
+
+NML3Cfg *nm_l3_ipv4ll_get_l3cfg(NML3IPv4LL *self);
+
+int nm_l3_ipv4ll_get_ifindex(NML3IPv4LL *self);
+
+NMPlatform *nm_l3_ipv4ll_get_platform(NML3IPv4LL *self);
+
+/*****************************************************************************/
+
+/* By default, NML3IPv4LL is disabled. You also need to register (enable) it.
+ * The intent of this API is that multiple users can enable/register their own
+ * settings, and NML3IPv4LL will mediate the different requests.
+ *
+ * Also, by setting timeout_msec to zero, NML3IPv4LL is disabled again (zero
+ * wins over all timeouts). This is useful if you do DHCP and IPv4LL on the
+ * same interface. You possibly want to disable IPv4LL if you have a valid
+ * DHCP lease. By registering a timeout_msec to zero, you can disable IPv4LL.
+ *
+ * Also, a registration keeps the NML3IPv4LL instance alive (it also takes
+ * a reference). */
+
+typedef struct _NML3IPv4LLRegistration NML3IPv4LLRegistration;
+
+NML3IPv4LLRegistration *nm_l3_ipv4ll_register_new(NML3IPv4LL *self, guint timeout_msec);
+
+NML3IPv4LLRegistration *nm_l3_ipv4ll_register_update(NML3IPv4LLRegistration *reg,
+ guint timeout_msec);
+
+NML3IPv4LLRegistration *nm_l3_ipv4ll_register_remove(NML3IPv4LLRegistration *reg);
+
+NM_AUTO_DEFINE_FCN0(NML3IPv4LLRegistration *,
+ _nm_auto_remove_l3ipv4ll_registration,
+ nm_l3_ipv4ll_register_remove);
+#define nm_auto_remove_l3ipv4ll_registration nm_auto(_nm_auto_remove_l3ipv4ll_registration)
+
+static inline NML3IPv4LL *
+nm_l3_ipv4ll_register_get_instance(NML3IPv4LLRegistration *reg)
+{
+ NML3IPv4LL *ipv4ll;
+
+ if (!reg)
+ return NULL;
+ ipv4ll = *((NML3IPv4LL **) reg);
+ nm_assert(NM_IS_L3_IPV4LL(ipv4ll));
+ return ipv4ll;
+}
+
+/*****************************************************************************/
+
+NML3IPv4LLState nm_l3_ipv4ll_get_state(NML3IPv4LL *self);
+
+in_addr_t nm_l3_ipv4ll_get_addr(NML3IPv4LL *self);
+
+const NML3ConfigData *nm_l3_ipv4ll_get_l3cd(NML3IPv4LL *self);
+
+/*****************************************************************************/
+
+void nm_l3_ipv4ll_restart(NML3IPv4LL *self);
+
+#endif /* __NM_L3_IPV4LL_H__ */
diff --git a/src/nm-l3cfg.c b/src/nm-l3cfg.c
index b68920699..c6ad7f100 100644
--- a/src/nm-l3cfg.c
+++ b/src/nm-l3cfg.c
@@ -12,6 +12,7 @@
#include "platform/nmp-object.h"
#include "nm-netns.h"
#include "n-acd/src/n-acd.h"
+#include "nm-l3-ipv4ll.h"
/*****************************************************************************/
@@ -57,32 +58,6 @@ typedef enum {
ACD_STATE_CHANGE_MODE_TIMEOUT,
} AcdStateChangeMode;
-typedef struct {
- union {
- NML3AcdAddrTrackInfo track_info;
- struct {
- const NMPObject * obj;
- const NML3ConfigData *l3cd;
- gconstpointer tag;
-
- guint32 acd_timeout_msec_track;
- NML3AcdDefendType acd_defend_type_track;
- bool acd_dirty_track : 1;
- bool acd_failed_notified_track : 1;
- };
- };
-} AcdTrackData;
-
-G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, track_info) == 0);
-G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, obj) == G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, obj));
-G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, l3cd) == G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, l3cd));
-G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, tag) == G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, tag));
-G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, acd_timeout_msec_track)
- >= G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, _padding));
-G_STATIC_ASSERT(sizeof(AcdTrackData) == sizeof(NML3AcdAddrTrackInfo));
-
-#define ACD_TRACK_DATA(arg) NM_CONSTCAST(AcdTrackData, arg, NML3AcdAddrTrackInfo)
-
G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3AcdAddrInfo, addr) == 0);
typedef struct {
@@ -320,6 +295,7 @@ static NM_UTILS_ENUM2STR_DEFINE(
_l3_config_notify_type_to_string,
NML3ConfigNotifyType,
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, "acd-event"),
+ NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT, "ipv4ll-event"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, "platform-change"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"),
@@ -364,9 +340,11 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
char * sbuf,
gsize sbuf_size)
{
- char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
- char *s = sbuf;
- gsize l = sbuf_size;
+ char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+ char sbuf100[100];
+ char * s = sbuf;
+ gsize l = sbuf_size;
+ in_addr_t addr4;
nm_assert(sbuf);
nm_assert(sbuf_size > 0);
@@ -397,6 +375,19 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
", obj-type-flags=0x%x",
notify_data->platform_change_on_idle.obj_type_flags);
break;
+ case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT:
+ nm_assert(NM_IS_L3_IPV4LL(notify_data->ipv4ll_event.ipv4ll));
+ addr4 = nm_l3_ipv4ll_get_addr(notify_data->ipv4ll_event.ipv4ll);
+ nm_utils_strbuf_append(
+ &s,
+ &l,
+ ", ipv4ll=" NM_HASH_OBFUSCATE_PTR_FMT "%s%s, state=%s",
+ NM_HASH_OBFUSCATE_PTR(notify_data->ipv4ll_event.ipv4ll),
+ NM_PRINT_FMT_QUOTED2(addr4 != 0, ", addr=", _nm_utils_inet4_ntop(addr4, sbuf_addr), ""),
+ nm_l3_ipv4ll_state_to_string(nm_l3_ipv4ll_get_state(notify_data->ipv4ll_event.ipv4ll),
+ sbuf100,
+ sizeof(sbuf100)));
+ break;
default:
break;
}
@@ -576,15 +567,15 @@ _l3cfg_externally_removed_objs_drop_unused(NML3Cfg *self)
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_route_obj(self->priv.p->combined_l3cd_commited, obj)) {
+ 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. */
- (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))--;
- g_hash_table_iter_remove(&h_iter);
_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);
}
}
}
@@ -602,10 +593,17 @@ _l3cfg_externally_removed_objs_track(NML3Cfg *self, const NMPObject *obj, gboole
if (!is_removed) {
/* the object is still (or again) present. It no longer gets hidden. */
if (self->priv.p->externally_removed_objs_hash) {
- if (g_hash_table_remove(self->priv.p->externally_removed_objs_hash, obj)) {
- (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))--;
+ 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(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+ nmp_object_to_string(obj2, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+ nmp_object_unref(obj2);
}
}
return;
@@ -977,13 +975,13 @@ nm_l3cfg_get_acd_is_pending(NML3Cfg *self)
}
static gboolean
-_acd_track_data_is_not_dirty(const AcdTrackData *acd_track)
+_acd_track_data_is_not_dirty(const NML3AcdAddrTrackInfo *acd_track)
{
- return acd_track && !acd_track->acd_dirty_track;
+ return acd_track && !acd_track->_priv.acd_dirty_track;
}
static void
-_acd_track_data_clear(AcdTrackData *acd_track)
+_acd_track_data_clear(NML3AcdAddrTrackInfo *acd_track)
{
nm_l3_config_data_unref(acd_track->l3cd);
nmp_object_unref(acd_track->obj);
@@ -998,7 +996,7 @@ _acd_data_free(AcdData *acd_data)
nm_clear_g_source_inst(&acd_data->acd_data_timeout_source);
c_list_unlink_stale(&acd_data->acd_lst);
c_list_unlink_stale(&acd_data->acd_event_notify_lst);
- g_free((AcdTrackData *) acd_data->info.track_infos);
+ g_free((NML3AcdAddrTrackInfo *) acd_data->info.track_infos);
nm_g_slice_free(acd_data);
}
@@ -1014,17 +1012,17 @@ _acd_data_collect_tracks_data(const AcdData * acd_data,
guint i;
for (i = 0; i < acd_data->info.n_track_infos; i++) {
- const AcdTrackData *acd_track = ACD_TRACK_DATA(&acd_data->info.track_infos[i]);
+ const NML3AcdAddrTrackInfo *acd_track = &acd_data->info.track_infos[i];
if (dirty_selector != NM_TERNARY_DEFAULT) {
- if ((!!dirty_selector) != (!!acd_track->acd_dirty_track))
+ if ((!!dirty_selector) != (!!acd_track->_priv.acd_dirty_track))
continue;
}
n++;
- if (best_acd_timeout_msec > acd_track->acd_timeout_msec_track)
- best_acd_timeout_msec = acd_track->acd_timeout_msec_track;
- if (best_acd_defend_type < acd_track->acd_defend_type_track)
- best_acd_defend_type = acd_track->acd_defend_type_track;
+ if (best_acd_timeout_msec > acd_track->_priv.acd_timeout_msec_track)
+ best_acd_timeout_msec = acd_track->_priv.acd_timeout_msec_track;
+ if (best_acd_defend_type < acd_track->_priv.acd_defend_type_track)
+ best_acd_defend_type = acd_track->_priv.acd_defend_type_track;
}
nm_assert(n == 0 || best_acd_defend_type > NM_L3_ACD_DEFEND_TYPE_NONE);
@@ -1035,7 +1033,7 @@ _acd_data_collect_tracks_data(const AcdData * acd_data,
return n;
}
-static AcdTrackData *
+static NML3AcdAddrTrackInfo *
_acd_data_find_track(const AcdData * acd_data,
const NML3ConfigData *l3cd,
const NMPObject * obj,
@@ -1044,10 +1042,10 @@ _acd_data_find_track(const AcdData * acd_data,
guint i;
for (i = 0; i < acd_data->info.n_track_infos; i++) {
- const AcdTrackData *acd_track = ACD_TRACK_DATA(&acd_data->info.track_infos[i]);
+ const NML3AcdAddrTrackInfo *acd_track = &acd_data->info.track_infos[i];
if (acd_track->obj == obj && acd_track->l3cd == l3cd && acd_track->tag == tag)
- return (AcdTrackData *) acd_track;
+ return (NML3AcdAddrTrackInfo *) acd_track;
}
return NULL;
@@ -1383,19 +1381,19 @@ _l3_acd_nacd_instance_create_probe(NML3Cfg * self,
static void
_l3_acd_data_prune_one(NML3Cfg *self, AcdData *acd_data, gboolean all /* or only dirty */)
{
- AcdTrackData *acd_tracks;
- guint i;
- guint j;
+ NML3AcdAddrTrackInfo *acd_tracks;
+ guint i;
+ guint j;
- acd_tracks = (AcdTrackData *) acd_data->info.track_infos;
+ acd_tracks = (NML3AcdAddrTrackInfo *) acd_data->info.track_infos;
j = 0;
for (i = 0; i < acd_data->info.n_track_infos; i++) {
- AcdTrackData *acd_track = &acd_tracks[i];
+ NML3AcdAddrTrackInfo *acd_track = &acd_tracks[i];
/* If not "all" is requested, we only delete the dirty ones
* (and mark the survivors as dirty right away). */
- if (!all && !acd_track->acd_dirty_track) {
- acd_track->acd_dirty_track = TRUE;
+ if (!all && !acd_track->_priv.acd_dirty_track) {
+ acd_track->_priv.acd_dirty_track = TRUE;
if (j != i)
acd_tracks[j] = *acd_track;
j++;
@@ -1455,11 +1453,11 @@ _l3_acd_data_add(NML3Cfg * self,
NML3AcdDefendType acd_defend_type,
guint32 acd_timeout_msec)
{
- in_addr_t addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
- AcdTrackData *acd_track;
- AcdData * acd_data;
- const char * track_mode;
- char sbuf100[100];
+ in_addr_t addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
+ NML3AcdAddrTrackInfo *acd_track;
+ AcdData * acd_data;
+ const char * track_mode;
+ char sbuf100[100];
if (ACD_ADDR_SKIP(addr))
return;
@@ -1509,36 +1507,38 @@ _l3_acd_data_add(NML3Cfg * self,
g_realloc((gpointer) acd_data->info.track_infos,
acd_data->n_track_infos_alloc * sizeof(acd_data->info.track_infos[0]));
}
- acd_track = (AcdTrackData *) &acd_data->info.track_infos[acd_data->info.n_track_infos++];
- *acd_track = (AcdTrackData){
- .l3cd = nm_l3_config_data_ref(l3cd),
- .obj = nmp_object_ref(obj),
- .tag = tag,
- .acd_dirty_track = FALSE,
- .acd_defend_type_track = acd_defend_type,
- .acd_timeout_msec_track = acd_timeout_msec,
+ acd_track =
+ (NML3AcdAddrTrackInfo *) &acd_data->info.track_infos[acd_data->info.n_track_infos++];
+ *acd_track = (NML3AcdAddrTrackInfo){
+ .l3cd = nm_l3_config_data_ref(l3cd),
+ .obj = nmp_object_ref(obj),
+ .tag = tag,
+ ._priv.acd_dirty_track = FALSE,
+ ._priv.acd_defend_type_track = acd_defend_type,
+ ._priv.acd_timeout_msec_track = acd_timeout_msec,
};
track_mode = "new";
} else {
- nm_assert(acd_track->acd_dirty_track);
- acd_track->acd_dirty_track = FALSE;
- if (acd_track->acd_timeout_msec_track != acd_timeout_msec
- || acd_track->acd_defend_type_track != acd_defend_type) {
- acd_track->acd_defend_type_track = acd_defend_type;
- acd_track->acd_timeout_msec_track = acd_timeout_msec;
- track_mode = "update";
+ nm_assert(acd_track->_priv.acd_dirty_track);
+ acd_track->_priv.acd_dirty_track = FALSE;
+ if (acd_track->_priv.acd_timeout_msec_track != acd_timeout_msec
+ || acd_track->_priv.acd_defend_type_track != acd_defend_type) {
+ acd_track->_priv.acd_defend_type_track = acd_defend_type;
+ acd_track->_priv.acd_timeout_msec_track = acd_timeout_msec;
+ track_mode = "update";
} else
return;
}
acd_data->track_infos_changed = TRUE;
- _LOGT_acd(
- acd_data,
- "track " ACD_TRACK_FMT " with timeout %u msec, defend=%s (%s)",
- ACD_TRACK_PTR(acd_track),
- acd_timeout_msec,
- _l3_acd_defend_type_to_string(acd_track->acd_defend_type_track, sbuf100, sizeof(sbuf100)),
- track_mode);
+ _LOGT_acd(acd_data,
+ "track " ACD_TRACK_FMT " with timeout %u msec, defend=%s (%s)",
+ ACD_TRACK_PTR(acd_track),
+ acd_timeout_msec,
+ _l3_acd_defend_type_to_string(acd_track->_priv.acd_defend_type_track,
+ sbuf100,
+ sizeof(sbuf100)),
+ track_mode);
}
static void
@@ -1556,7 +1556,7 @@ _l3_acd_data_add_all(NML3Cfg * self,
c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
nm_assert(acd_data->info.n_track_infos > 0u);
for (i = 0; i < acd_data->info.n_track_infos; i++)
- nm_assert(((const AcdTrackData *) &acd_data->info.track_infos[i])->acd_dirty_track);
+ nm_assert(acd_data->info.track_infos[i]._priv.acd_dirty_track);
}
#endif
@@ -1794,7 +1794,7 @@ _l3_acd_data_state_change(NML3Cfg * self,
*
* Here, all the state for one address that we probe/announce is tracked in AcdData/acd_data.
*
- * The acd_data has a list of AcdTrackData/acd_track_lst_head, which are configuration items
+ * The acd_data has a list of NML3AcdAddrTrackInfo/acd_track_lst_head, which are configuration items
* that are interested in configuring this address. The "owners" of the ACD check for a certain
* address.
*
@@ -2668,7 +2668,10 @@ nm_l3cfg_add_config(NML3Cfg * self,
nm_assert(tag);
nm_assert(l3cd);
nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex);
- nm_assert(acd_timeout_msec < ACD_MAX_TIMEOUT_MSEC);
+
+ if (acd_timeout_msec > ACD_MAX_TIMEOUT_MSEC)
+ acd_timeout_msec = ACD_MAX_TIMEOUT_MSEC;
+
nm_assert(NM_IN_SET(acd_defend_type,
NM_L3_ACD_DEFEND_TYPE_NEVER,
NM_L3_ACD_DEFEND_TYPE_ONCE,
@@ -2703,12 +2706,11 @@ nm_l3cfg_add_config(NML3Cfg * self,
if (l3_config_data->l3cd == l3cd) {
nm_assert(idx == -1);
idx = idx2;
- continue;
+ idx2++;
+ } else {
+ changed = TRUE;
+ _l3_config_datas_remove_index_fast(self->priv.p->l3_config_datas, idx2);
}
-
- changed = TRUE;
- _l3_config_datas_remove_index_fast(self->priv.p->l3_config_datas, idx2);
-
idx2 = _l3_config_datas_find_next(self->priv.p->l3_config_datas, idx2, tag, NULL);
if (idx2 < 0)
break;
@@ -3503,7 +3505,7 @@ nm_l3cfg_commit_type_register(NML3Cfg * self,
linked = FALSE;
c_list_for_each_entry (h, &self->priv.p->commit_type_lst_head, commit_type_lst) {
if (handle->commit_type >= h->commit_type) {
- c_list_link_before(&self->priv.p->commit_type_lst_head, &handle->commit_type_lst);
+ c_list_link_before(&h->commit_type_lst, &handle->commit_type_lst);
linked = TRUE;
break;
}
diff --git a/src/nm-l3cfg.h b/src/nm-l3cfg.h
index d89e2fa28..d6aa1172a 100644
--- a/src/nm-l3cfg.h
+++ b/src/nm-l3cfg.h
@@ -6,6 +6,8 @@
#include "platform/nmp-object.h"
#include "nm-l3-config-data.h"
+#define NM_L3CFG_CONFIG_PRIORITY_IPV4LL 0
+
#define NM_TYPE_L3CFG (nm_l3cfg_get_type())
#define NM_L3CFG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_L3CFG, NML3Cfg))
#define NM_L3CFG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_L3CFG, NML3CfgClass))
@@ -40,11 +42,13 @@ typedef struct {
const NML3ConfigData *l3cd;
gconstpointer tag;
- char _padding[sizeof(struct {
- guint32 a;
- NML3AcdDefendType b;
- guint8 c;
- })];
+ struct {
+ guint32 acd_timeout_msec_track;
+ NML3AcdDefendType acd_defend_type_track;
+ bool acd_dirty_track : 1;
+ bool acd_failed_notified_track : 1;
+ } _priv;
+
} NML3AcdAddrTrackInfo;
typedef struct {
@@ -55,6 +59,33 @@ typedef struct {
const NML3AcdAddrTrackInfo *track_infos;
} NML3AcdAddrInfo;
+static inline const NML3AcdAddrTrackInfo *
+nm_l3_acd_addr_info_find_track_info(const NML3AcdAddrInfo *addr_info,
+ gconstpointer tag,
+ const NML3ConfigData * l3cd,
+ const NMPObject * obj)
+{
+ guint i;
+ const NML3AcdAddrTrackInfo *ti;
+
+ nm_assert(addr_info);
+
+ /* we always expect that the number n_track_infos is reasonably small. Hence,
+ * a naive linear search is simplest and fastest (e.g. we don't have a hash table). */
+
+ for (i = 0, ti = addr_info->track_infos; i < addr_info->n_track_infos; i++, ti++) {
+ if (l3cd && ti->l3cd != l3cd)
+ continue;
+ if (tag && ti->tag != tag)
+ continue;
+ if (obj && ti->obj != obj)
+ continue;
+ return ti;
+ }
+
+ return NULL;
+}
+
typedef enum {
NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED,
@@ -75,9 +106,13 @@ typedef enum {
* notifications without also subscribing directly to the platform. */
NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE,
+ NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT,
+
_NM_L3_CONFIG_NOTIFY_TYPE_NUM,
} NML3ConfigNotifyType;
+struct _NML3IPv4LL;
+
typedef struct {
NML3ConfigNotifyType notify_type;
union {
@@ -93,6 +128,10 @@ typedef struct {
struct {
guint32 obj_type_flags;
} platform_change_on_idle;
+
+ struct {
+ struct _NML3IPv4LL *ipv4ll;
+ } ipv4ll_event;
};
} NML3ConfigNotifyData;
diff --git a/src/nm-netns.c b/src/nm-netns.c
index 2970e45c9..c5a445ac6 100644
--- a/src/nm-netns.c
+++ b/src/nm-netns.c
@@ -126,6 +126,19 @@ _l3cfg_weak_notify(gpointer data, GObject *where_the_object_was)
}
NML3Cfg *
+nm_netns_get_l3cfg(NMNetns *self, int ifindex)
+{
+ NMNetnsPrivate *priv;
+
+ g_return_val_if_fail(NM_IS_NETNS(self), NULL);
+ g_return_val_if_fail(ifindex > 0, NULL);
+
+ priv = NM_NETNS_GET_PRIVATE(self);
+
+ return g_hash_table_lookup(priv->l3cfgs, &ifindex);
+}
+
+NML3Cfg *
nm_netns_access_l3cfg(NMNetns *self, int ifindex)
{
NMNetnsPrivate *priv;
diff --git a/src/nm-netns.h b/src/nm-netns.h
index bb0c955ef..5ae0c294b 100644
--- a/src/nm-netns.h
+++ b/src/nm-netns.h
@@ -31,6 +31,8 @@ struct _NMDedupMultiIndex *nm_netns_get_multi_idx(NMNetns *self);
#define NM_NETNS_GET (nm_netns_get())
+NML3Cfg *nm_netns_get_l3cfg(NMNetns *self, int ifindex);
+
NML3Cfg *nm_netns_access_l3cfg(NMNetns *netns, int ifindex);
/*****************************************************************************/
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index eca201b08..ca5080b00 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -6515,7 +6515,9 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz
route->plen,
s_gateway,
str_dev,
- route->metric_any ? "??" : nm_sprintf_buf(str_metric, "%u", route->metric),
+ route->metric_any
+ ? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??")
+ : nm_sprintf_buf(str_metric, "%u", route->metric),
route->mss,
nmp_utils_ip_config_source_to_string(route->rt_source, s_source, sizeof(s_source)),
_rtm_flags_to_string_full(str_rtm_flags, sizeof(str_rtm_flags), route->r_rtm_flags),
@@ -6639,7 +6641,9 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
route->plen,
s_gateway,
str_dev,
- route->metric_any ? "??" : nm_sprintf_buf(str_metric, "%u", route->metric),
+ route->metric_any
+ ? (route->metric ? nm_sprintf_buf(str_metric, "??+%u", route->metric) : "??")
+ : nm_sprintf_buf(str_metric, "%u", route->metric),
route->mss,
nmp_utils_ip_config_source_to_string(route->rt_source, s_source, sizeof(s_source)),
route->src_plen || !IN6_IS_ADDR_UNSPECIFIED(&route->src)
@@ -7927,7 +7931,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
nm_utils_ip4_address_clear_host_address(obj->network, obj->plen),
obj->plen,
- nm_platform_ip4_route_get_effective_metric(obj),
+ obj->metric,
obj->tos,
NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any));
break;
@@ -7938,7 +7942,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
nm_utils_ip4_address_clear_host_address(obj->network, obj->plen),
obj->plen,
- nm_platform_ip4_route_get_effective_metric(obj),
+ obj->metric,
obj->tos,
/* on top of WEAK_ID: */
obj->ifindex,
@@ -7970,7 +7974,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
obj->ifindex,
nm_utils_ip4_address_clear_host_address(obj->network, obj->plen),
obj->plen,
- nm_platform_ip4_route_get_effective_metric(obj),
+ obj->metric,
obj->gateway,
nmp_utils_ip_config_source_round_trip_rtprot(obj->rt_source),
_ip_route_scope_inv_get_normalized(obj),
@@ -7999,7 +8003,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
obj->ifindex,
obj->network,
obj->plen,
- nm_platform_ip4_route_get_effective_metric(obj),
+ obj->metric,
obj->gateway,
obj->rt_source,
obj->scope_inv,
@@ -8039,8 +8043,7 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX(a->network, b->network, MIN(a->plen, b->plen));
NM_CMP_FIELD(a, b, plen);
NM_CMP_FIELD_UNSAFE(a, b, metric_any);
- if (!a->metric_any)
- NM_CMP_FIELD(a, b, metric);
+ NM_CMP_FIELD(a, b, metric);
NM_CMP_FIELD(a, b, tos);
if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
NM_CMP_FIELD(a, b, ifindex);
@@ -8081,8 +8084,7 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
NM_CMP_FIELD(a, b, network);
NM_CMP_FIELD(a, b, plen);
NM_CMP_FIELD_UNSAFE(a, b, metric_any);
- if (!a->metric_any)
- NM_CMP_FIELD(a, b, metric);
+ NM_CMP_FIELD(a, b, metric);
NM_CMP_FIELD(a, b, gateway);
if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
NM_CMP_DIRECT(nmp_utils_ip_config_source_round_trip_rtprot(a->rt_source),
@@ -8130,7 +8132,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
*nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen),
obj->plen,
- nm_platform_ip6_route_get_effective_metric(obj),
+ obj->metric,
*nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen),
obj->src_plen,
NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any));
@@ -8142,7 +8144,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
nm_platform_ip_route_get_effective_table(NM_PLATFORM_IP_ROUTE_CAST(obj)),
*nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen),
obj->plen,
- nm_platform_ip6_route_get_effective_metric(obj),
+ obj->metric,
*nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen),
obj->src_plen,
NM_HASH_COMBINE_BOOLS(guint8, obj->metric_any, obj->table_any),
@@ -8158,7 +8160,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
obj->ifindex,
*nm_utils_ip6_address_clear_host_address(&a1, &obj->network, obj->plen),
obj->plen,
- nm_platform_ip6_route_get_effective_metric(obj),
+ obj->metric,
obj->gateway,
obj->pref_src,
*nm_utils_ip6_address_clear_host_address(&a2, &obj->src, obj->src_plen),
@@ -8187,7 +8189,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
obj->table_coerced,
obj->ifindex,
obj->network,
- nm_platform_ip6_route_get_effective_metric(obj),
+ obj->metric,
obj->gateway,
obj->pref_src,
obj->src,
@@ -8228,8 +8230,7 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->network, &b->network, MIN(a->plen, b->plen));
NM_CMP_FIELD(a, b, plen);
NM_CMP_FIELD_UNSAFE(a, b, metric_any);
- if (!a->metric_any)
- NM_CMP_FIELD(a, b, metric);
+ NM_CMP_FIELD(a, b, metric);
NM_CMP_DIRECT_IN6ADDR_SAME_PREFIX(&a->src, &b->src, MIN(a->src_plen, b->src_plen));
NM_CMP_FIELD(a, b, src_plen);
if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) {
@@ -8254,8 +8255,7 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
NM_CMP_FIELD_IN6ADDR(a, b, network);
NM_CMP_FIELD(a, b, plen);
NM_CMP_FIELD_UNSAFE(a, b, metric_any);
- if (!a->metric_any)
- NM_CMP_FIELD(a, b, metric);
+ NM_CMP_FIELD(a, b, metric);
NM_CMP_FIELD_IN6ADDR(a, b, gateway);
NM_CMP_FIELD_IN6ADDR(a, b, pref_src);
if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) {
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 17f3a2fd5..df637fe03 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -415,9 +415,9 @@ typedef union {
* configures addresses. */
#define NM_PLATFORM_ROUTE_METRIC_IP4_DEVICE_ROUTE ((guint32) 0u)
-#define __NMPlatformIPRoute_COMMON \
- __NMPlatformObjWithIfindex_COMMON; \
- \
+#define __NMPlatformIPRoute_COMMON \
+ __NMPlatformObjWithIfindex_COMMON; \
+ \
/* The NMIPConfigSource. For routes that we receive from cache this corresponds
* to the rtm_protocol field (and is one of the NM_IP_CONFIG_SOURCE_RTPROT_* values).
* When adding a route, the source will be coerced to the protocol using
@@ -429,11 +429,11 @@ typedef union {
*
* When deleting an IPv4/IPv6 route, the rtm_protocol field must match (even
* if it is not part of the primary key for IPv6) -- unless rtm_protocol is set
- * to zero, in which case the first matching route (with proto ignored) is deleted. */ \
- NMIPConfigSource rt_source; \
- \
- guint8 plen; \
- \
+ * to zero, in which case the first matching route (with proto ignored) is deleted. */ \
+ NMIPConfigSource rt_source; \
+ \
+ guint8 plen; \
+ \
/* RTA_METRICS:
*
* For IPv4 routes, these properties are part of their
@@ -444,24 +444,29 @@ typedef union {
*
* When deleting a route, kernel seems to ignore the RTA_METRICS properties.
* That is a problem/bug for IPv4 because you cannot explicitly select which
- * route to delete. Kernel just picks the first. See rh#1475642. */ \
- \
- /* RTA_METRICS.RTAX_LOCK (iproute2: "lock" arguments) */ \
- bool lock_window : 1; \
- bool lock_cwnd : 1; \
- bool lock_initcwnd : 1; \
- bool lock_initrwnd : 1; \
- bool lock_mtu : 1; \
- \
- /* if TRUE, the "metric" field gets ignored and can be overridden with settings from
- * the device. This is to track routes that should be configured (e.g. from a DHCP
- * lease), but where the actual metric is determined by NMDevice. */ \
- bool metric_any : 1; \
- \
+ * route to delete. Kernel just picks the first. See rh#1475642. */ \
+ \
+ /* RTA_METRICS.RTAX_LOCK (iproute2: "lock" arguments) */ \
+ bool lock_window : 1; \
+ bool lock_cwnd : 1; \
+ bool lock_initcwnd : 1; \
+ bool lock_initrwnd : 1; \
+ bool lock_mtu : 1; \
+ \
+ /* if TRUE, the "metric" field is interpreted as an offset that is added to a default
+ * metric. For example, form a DHCP lease we don't know the actually used metric, because
+ * that is determined by upper layers (the configuration). However, we have a default
+ * metric that should be used. So we set "metric_any" to %TRUE, which means to use
+ * the default metric. However, we still treat the "metric" field as an offset that
+ * will be added to the default metric. In most case, you want that "metric" is zero
+ * when setting "metric_any". */ \
+ bool metric_any : 1; \
+ \
/* like "metric_any", the table is determined by other layers of the code.
- * This field overrides "table_coerced" field. */ \
- bool table_any : 1; \
- \
+ * This field overrides "table_coerced" field. If "table_any" is true, then
+ * the "table_coerced" field is ignored (unlike for the metric). */ \
+ bool table_any : 1; \
+ \
/* rtnh_flags
*
* Routes with rtm_flags RTM_F_CLONED are hidden by platform and
@@ -471,44 +476,46 @@ typedef union {
* NOTE: currently we ignore all flags except RTM_F_CLONED
* and RTNH_F_ONLINK.
* We also may not properly consider the flags as part of the ID
- * in route-cmp. */ \
- unsigned r_rtm_flags; \
- \
- /* RTA_METRICS.RTAX_ADVMSS (iproute2: advmss) */ \
- guint32 mss; \
- \
- /* RTA_METRICS.RTAX_WINDOW (iproute2: window) */ \
- guint32 window; \
- \
- /* RTA_METRICS.RTAX_CWND (iproute2: cwnd) */ \
- guint32 cwnd; \
- \
- /* RTA_METRICS.RTAX_INITCWND (iproute2: initcwnd) */ \
- guint32 initcwnd; \
- \
- /* RTA_METRICS.RTAX_INITRWND (iproute2: initrwnd) */ \
- guint32 initrwnd; \
- \
- /* RTA_METRICS.RTAX_MTU (iproute2: mtu) */ \
- guint32 mtu; \
- \
- /* RTA_PRIORITY (iproute2: metric) */ \
- guint32 metric; \
- \
+ * in route-cmp. */ \
+ unsigned r_rtm_flags; \
+ \
+ /* RTA_METRICS.RTAX_ADVMSS (iproute2: advmss) */ \
+ guint32 mss; \
+ \
+ /* RTA_METRICS.RTAX_WINDOW (iproute2: window) */ \
+ guint32 window; \
+ \
+ /* RTA_METRICS.RTAX_CWND (iproute2: cwnd) */ \
+ guint32 cwnd; \
+ \
+ /* RTA_METRICS.RTAX_INITCWND (iproute2: initcwnd) */ \
+ guint32 initcwnd; \
+ \
+ /* RTA_METRICS.RTAX_INITRWND (iproute2: initrwnd) */ \
+ guint32 initrwnd; \
+ \
+ /* RTA_METRICS.RTAX_MTU (iproute2: mtu) */ \
+ guint32 mtu; \
+ \
+ /* RTA_PRIORITY (iproute2: metric)
+ * If "metric_any" is %TRUE, then this is interpreted as an offset that will be
+ * added to a default base metric. In such cases, the offset is usually zero. */ \
+ guint32 metric; \
+ \
/* rtm_table, RTA_TABLE.
*
* This is not the original table ID. Instead, 254 (RT_TABLE_MAIN) and
* zero (RT_TABLE_UNSPEC) are swapped, so that the default is the main
- * table. Use nm_platform_route_table_coerce()/nm_platform_route_table_uncoerce(). */ \
- guint32 table_coerced; \
- \
+ * table. Use nm_platform_route_table_coerce()/nm_platform_route_table_uncoerce(). */ \
+ guint32 table_coerced; \
+ \
/* rtm_type.
*
* This is not the original type, if type_coerced is 0 then
* it means RTN_UNSPEC otherwise the type value is preserved.
- * */ \
- guint8 type_coerced; \
- \
+ * */ \
+ guint8 type_coerced; \
+ \
/*end*/
typedef struct {
@@ -2078,18 +2085,18 @@ static inline guint32
nm_platform_ip4_route_get_effective_metric(const NMPlatformIP4Route *r)
{
nm_assert(r);
- nm_assert(!r->metric_any || r->metric == 0);
- return r->metric_any ? NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4 : r->metric;
+ return r->metric_any ? nm_add_u32_clamped(NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, r->metric)
+ : r->metric;
}
static inline guint32
nm_platform_ip6_route_get_effective_metric(const NMPlatformIP6Route *r)
{
nm_assert(r);
- nm_assert(!r->metric_any || r->metric == 0);
- return r->metric_any ? NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6 : r->metric;
+ return r->metric_any ? nm_add_u32_clamped(NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, r->metric)
+ : r->metric;
}
static inline guint32
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index a7fe59056..e225f3392 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -5,13 +5,15 @@
#include "nm-default.h"
+#include "test-common.h"
+
#include <sys/mount.h>
#include <sched.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <linux/if_tun.h>
-#include "test-common.h"
+#include "n-acd/src/n-acd.h"
#define SIGNAL_DATA_FMT "'%s-%s' ifindex %d%s%s%s (%d times received)"
#define SIGNAL_DATA_ARG(data) \
@@ -2566,3 +2568,145 @@ main(int argc, char **argv)
g_object_unref(NM_PLATFORM_GET);
return result;
}
+
+/*****************************************************************************/
+
+struct _NMTstpAcdDefender {
+ int ifindex;
+ in_addr_t ip_addr;
+ NAcd * nacd;
+ NAcdProbe *probe;
+ GSource * source;
+ gint8 announce_started;
+};
+
+static gboolean
+_l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
+{
+ NMTstpAcdDefender *defender = user_data;
+ int r;
+
+ r = n_acd_dispatch(defender->nacd);
+ if (r == N_ACD_E_PREEMPTED)
+ r = 0;
+ g_assert_cmpint(r, ==, 0);
+
+ while (TRUE) {
+ NAcdEvent *event;
+
+ r = n_acd_pop_event(defender->nacd, &event);
+ g_assert_cmpint(r, ==, 0);
+ if (!event)
+ return G_SOURCE_CONTINUE;
+
+ switch (event->event) {
+ case N_ACD_EVENT_READY:
+ g_assert_cmpint(defender->announce_started, ==, 0);
+ g_assert(defender->probe == event->ready.probe);
+ defender->announce_started++;
+ _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT "]: start announcing",
+ NM_HASH_OBFUSCATE_PTR(defender));
+ r = n_acd_probe_announce(defender->probe, N_ACD_DEFEND_ALWAYS);
+ g_assert_cmpint(r, ==, 0);
+ break;
+ case N_ACD_EVENT_DEFENDED:
+ g_assert(defender->probe == event->defended.probe);
+ g_assert_cmpint(event->defended.n_sender, ==, ETH_ALEN);
+ _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT
+ "]: defended from " NM_ETHER_ADDR_FORMAT_STR,
+ NM_HASH_OBFUSCATE_PTR(defender),
+ NM_ETHER_ADDR_FORMAT_VAL((const NMEtherAddr *) event->defended.sender));
+ break;
+ case N_ACD_EVENT_USED:
+ case N_ACD_EVENT_CONFLICT:
+ case N_ACD_EVENT_DOWN:
+ default:
+ g_assert_not_reached();
+ break;
+ }
+ }
+}
+
+NMTstpAcdDefender *
+nmtstp_acd_defender_new(int ifindex, in_addr_t ip_addr, const NMEtherAddr *mac_addr)
+{
+ NMTstpAcdDefender * defender;
+ nm_auto(n_acd_config_freep) NAcdConfig * config = NULL;
+ nm_auto(n_acd_unrefp) NAcd * nacd = NULL;
+ nm_auto(n_acd_probe_config_freep) NAcdProbeConfig *probe_config = NULL;
+ NAcdProbe * probe;
+ int fd;
+ int r;
+ char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+
+ g_assert_cmpint(ifindex, >, 0);
+ g_assert(mac_addr);
+
+ r = n_acd_config_new(&config);
+ g_assert_cmpint(r, ==, 0);
+ g_assert(config);
+
+ n_acd_config_set_ifindex(config, ifindex);
+ n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET);
+ n_acd_config_set_mac(config, (const guint8 *) mac_addr, sizeof(*mac_addr));
+
+ r = n_acd_new(&nacd, config);
+ g_assert_cmpint(r, ==, 0);
+ g_assert(nacd);
+
+ r = n_acd_probe_config_new(&probe_config);
+ g_assert_cmpint(r, ==, 0);
+ g_assert(probe_config);
+
+ n_acd_probe_config_set_ip(probe_config, (struct in_addr){ip_addr});
+ n_acd_probe_config_set_timeout(probe_config, 0);
+
+ r = n_acd_probe(nacd, &probe, probe_config);
+ g_assert_cmpint(r, ==, 0);
+ g_assert(probe);
+
+ defender = g_slice_new(NMTstpAcdDefender);
+ *defender = (NMTstpAcdDefender){
+ .ifindex = ifindex,
+ .ip_addr = ip_addr,
+ .nacd = g_steal_pointer(&nacd),
+ .probe = g_steal_pointer(&probe),
+ };
+
+ _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT
+ "]: new for ifindex=%d, hwaddr=" NM_ETHER_ADDR_FORMAT_STR ", ipaddr=%s",
+ NM_HASH_OBFUSCATE_PTR(defender),
+ ifindex,
+ NM_ETHER_ADDR_FORMAT_VAL(mac_addr),
+ _nm_utils_inet4_ntop(ip_addr, sbuf_addr));
+
+ n_acd_probe_set_userdata(defender->probe, defender);
+
+ n_acd_get_fd(defender->nacd, &fd);
+ g_assert_cmpint(fd, >=, 0);
+
+ defender->source = nm_g_source_attach(nm_g_unix_fd_source_new(fd,
+ G_IO_IN,
+ G_PRIORITY_DEFAULT,
+ _l3_acd_nacd_event,
+ defender,
+ NULL),
+ NULL);
+
+ return defender;
+}
+
+void
+nmtstp_acd_defender_destroy(NMTstpAcdDefender *defender)
+{
+ if (!defender)
+ return;
+
+ _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT "]: destroy", NM_HASH_OBFUSCATE_PTR(defender));
+
+ nm_clear_g_source_inst(&defender->source);
+ nm_clear_pointer(&defender->nacd, n_acd_unref);
+ nm_clear_pointer(&defender->probe, n_acd_probe_free);
+
+ nm_g_slice_free(defender);
+}
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index 1d996f464..3a4bcb3b6 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -606,5 +606,14 @@ void nmtstp_setup_platform(void);
/*****************************************************************************/
+typedef struct _NMTstpAcdDefender NMTstpAcdDefender;
+
+NMTstpAcdDefender *
+nmtstp_acd_defender_new(int ifindex, in_addr_t ip_addr, const NMEtherAddr *mac_addr);
+
+void nmtstp_acd_defender_destroy(NMTstpAcdDefender *defender);
+
+/*****************************************************************************/
+
void _nmtstp_init_tests(int *argc, char ***argv);
void _nmtstp_setup_tests(void);
diff --git a/src/tests/test-l3cfg.c b/src/tests/test-l3cfg.c
index 49e45d388..e3956bae5 100644
--- a/src/tests/test-l3cfg.c
+++ b/src/tests/test-l3cfg.c
@@ -3,6 +3,7 @@
#include "nm-default.h"
#include "nm-l3cfg.h"
+#include "nm-l3-ipv4ll.h"
#include "nm-netns.h"
#include "platform/nm-platform.h"
@@ -10,6 +11,23 @@
/*****************************************************************************/
+static NML3Cfg *
+_netns_access_l3cfg(NMNetns *netns, int ifindex)
+{
+ NML3Cfg *l3cfg;
+
+ g_assert(NM_IS_NETNS(netns));
+ g_assert(ifindex > 0);
+
+ g_assert(!nm_netns_get_l3cfg(netns, ifindex));
+
+ l3cfg = nm_netns_access_l3cfg(netns, ifindex);
+ g_assert(NM_IS_L3CFG(l3cfg));
+ return l3cfg;
+}
+
+/*****************************************************************************/
+
typedef struct {
int test_idx;
NMPlatform * platform;
@@ -28,6 +46,8 @@ _test_fixture_1_setup(TestFixture1 *f, int test_idx)
{
const NMPlatformLink *l0;
const NMPlatformLink *l1;
+ const NMEtherAddr addr0 = NM_ETHER_ADDR_INIT(0xAA, 0xAA, test_idx, 0x00, 0x00, 0x00);
+ const NMEtherAddr addr1 = NM_ETHER_ADDR_INIT(0xAA, 0xAA, test_idx, 0x00, 0x00, 0x11);
g_assert_cmpint(test_idx, >, 0);
g_assert_cmpint(f->test_idx, ==, 0);
@@ -41,14 +61,26 @@ _test_fixture_1_setup(TestFixture1 *f, int test_idx)
f->multiidx = nm_dedup_multi_index_ref(nm_platform_get_multi_idx(f->platform));
f->netns = nm_netns_new(f->platform);
- l0 = nmtstp_link_veth_add(f->platform, -1, f->ifname0, f->ifname1);
+ nmtstp_link_veth_add(f->platform, -1, f->ifname0, f->ifname1);
+
+ l0 = nmtstp_link_get_typed(f->platform, -1, f->ifname0, NM_LINK_TYPE_VETH);
l1 = nmtstp_link_get_typed(f->platform, -1, f->ifname1, NM_LINK_TYPE_VETH);
f->ifindex0 = l0->ifindex;
- f->hwaddr0 = l0->l_address;
-
f->ifindex1 = l1->ifindex;
- f->hwaddr1 = l1->l_address;
+
+ g_assert_cmpint(nm_platform_link_set_address(f->platform, f->ifindex0, &addr0, sizeof(addr0)),
+ ==,
+ 0);
+ g_assert_cmpint(nm_platform_link_set_address(f->platform, f->ifindex1, &addr1, sizeof(addr1)),
+ ==,
+ 0);
+
+ l0 = nmtstp_link_get_typed(f->platform, f->ifindex0, f->ifname0, NM_LINK_TYPE_VETH);
+ l1 = nmtstp_link_get_typed(f->platform, f->ifindex1, f->ifname1, NM_LINK_TYPE_VETH);
+
+ f->hwaddr0 = l0->l_address;
+ f->hwaddr1 = l1->l_address;
g_assert(nm_platform_link_set_up(f->platform, f->ifindex0, NULL));
g_assert(nm_platform_link_set_up(f->platform, f->ifindex1, NULL));
@@ -326,14 +358,13 @@ test_l3cfg(gconstpointer test_data)
NULL);
}
- l3cfg0 = nm_netns_access_l3cfg(f->netns, f->ifindex0);
- g_assert(NM_IS_L3CFG(l3cfg0));
+ l3cfg0 = _netns_access_l3cfg(f->netns, f->ifindex0);
g_signal_connect(l3cfg0, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_test_l3cfg_signal_notify), tdata);
commit_type_1 = nm_l3cfg_commit_type_register(l3cfg0, NM_L3_CFG_COMMIT_TYPE_UPDATE, NULL);
- if ((nmtst_get_rand_uint32() % 4u) != 0) {
+ if (!nmtst_get_rand_one_case_in(4)) {
commit_type_2 =
nm_l3cfg_commit_type_register(l3cfg0,
nmtst_rand_select(NM_L3_CFG_COMMIT_TYPE_NONE,
@@ -374,7 +405,7 @@ test_l3cfg(gconstpointer test_data)
NM_PLATFORM_IP6_ADDRESS_INIT(.address = *nmtst_inet6_from_string("1:2:3:4::45"),
.plen = 64, ));
- if (nmtst_get_rand_bool())
+ if (nmtst_get_rand_one_case_in(2))
nm_l3_config_data_seal(l3cd);
l3cd_a = g_steal_pointer(&l3cd);
break;
@@ -450,12 +481,12 @@ test_l3cfg(gconstpointer test_data)
nm_l3cfg_commit_type_unregister(l3cfg0, commit_type_1);
nm_l3cfg_commit_type_unregister(l3cfg0, commit_type_2);
- if ((nmtst_get_rand_uint32() % 3) == 0)
+ if (nmtst_get_rand_one_case_in(3))
_test_fixture_1_teardown(&test_fixture);
nm_l3cfg_remove_config_all(l3cfg0, GINT_TO_POINTER('a'), FALSE);
- if ((nmtst_get_rand_uint32() % 3) == 0)
+ if (nmtst_get_rand_one_case_in(3))
_test_fixture_1_teardown(&test_fixture);
_LOGD("test end (/l3cfg/%d)", TEST_IDX);
@@ -463,12 +494,293 @@ test_l3cfg(gconstpointer test_data)
/*****************************************************************************/
+#define L3IPV4LL_ACD_TIMEOUT_MSEC 1500u
+
+typedef struct {
+ const TestFixture1 * f;
+ NML3CfgCommitTypeHandle *l3cfg_commit_type_1;
+ guint acd_timeout_msec;
+ NML3IPv4LL * l3ipv4ll;
+ bool has_addr4_101;
+ gint8 ready_seen;
+ gint8 addr_commit;
+ in_addr_t addr_commit_addr;
+ bool add_conflict_checked : 1;
+ bool add_conflict_done;
+} TestL3IPv4LLData;
+
+static gconstpointer
+TEST_L3_IPV4LL_TAG(const TestL3IPv4LLData *tdata, guint offset)
+{
+ return (&(((const char *) tdata)[offset]));
+}
+
+static void
+_test_l3_ipv4ll_maybe_add_addr_4(const TestL3IPv4LLData *tdata,
+ int ifindex,
+ guint one_case_in_num,
+ bool * has_addr,
+ const char * addr)
+{
+ if (has_addr) {
+ if (*has_addr || !nmtst_get_rand_one_case_in(one_case_in_num))
+ return;
+ *has_addr = TRUE;
+ }
+
+ if (ifindex == 0)
+ ifindex = tdata->f->ifindex0;
+
+ g_assert_cmpint(ifindex, >, 0);
+
+ _LOGT("add test address: %s on ifindex=%d", addr, ifindex);
+
+ nmtstp_ip4_address_add(tdata->f->platform,
+ -1,
+ ifindex,
+ nmtst_inet4_from_string(addr),
+ 24,
+ nmtst_inet4_from_string(addr),
+ 100000,
+ 0,
+ 0,
+ NULL);
+}
+
+static void
+_test_l3_ipv4ll_signal_notify(NML3Cfg * l3cfg,
+ const NML3ConfigNotifyData *notify_data,
+ TestL3IPv4LLData * tdata)
+{
+ char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+
+ g_assert(NM_IS_L3CFG(l3cfg));
+ g_assert(tdata);
+ g_assert(notify_data);
+ g_assert(_NM_INT_NOT_NEGATIVE(notify_data->notify_type));
+ g_assert(notify_data->notify_type < _NM_L3_CONFIG_NOTIFY_TYPE_NUM);
+
+ if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT) {
+ g_assert(tdata->l3ipv4ll == notify_data->ipv4ll_event.ipv4ll);
+ g_assert(NM_IN_SET(tdata->ready_seen, 0, 1));
+ g_assert(NM_IN_SET(tdata->addr_commit, 0, 1));
+
+ if (nm_l3_ipv4ll_get_state(tdata->l3ipv4ll) == NM_L3_IPV4LL_STATE_READY) {
+ g_assert_cmpint(tdata->ready_seen, ==, 0);
+ g_assert_cmpint(tdata->addr_commit, ==, 0);
+ tdata->ready_seen++;
+
+ if (tdata->f->test_idx == 2 && nmtst_get_rand_bool()) {
+ tdata->addr_commit++;
+ tdata->addr_commit_addr = nm_l3_ipv4ll_get_addr(tdata->l3ipv4ll);
+ g_assert(nm_utils_ip4_address_is_link_local(tdata->addr_commit_addr));
+ _LOGT("add address %s that passed ACD",
+ _nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr));
+ if (!nm_l3cfg_add_config(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
+ TEST_L3_IPV4LL_TAG(tdata, 1),
+ nmtst_get_rand_bool(),
+ nm_l3_ipv4ll_get_l3cd(tdata->l3ipv4ll),
+ NM_L3CFG_CONFIG_PRIORITY_IPV4LL,
+ 0,
+ 0,
+ 104,
+ 105,
+ 0,
+ 0,
+ NM_L3_ACD_DEFEND_TYPE_ONCE,
+ nmtst_get_rand_bool() ? tdata->acd_timeout_msec : 0u,
+ NM_L3_CONFIG_MERGE_FLAGS_NONE))
+ g_assert_not_reached();
+ nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll));
+
+ tdata->l3cfg_commit_type_1 =
+ nm_l3cfg_commit_type_register(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
+ NM_L3_CFG_COMMIT_TYPE_UPDATE,
+ tdata->l3cfg_commit_type_1);
+ }
+ } else if (nm_l3_ipv4ll_get_state(tdata->l3ipv4ll) != NM_L3_IPV4LL_STATE_DEFENDING
+ && tdata->ready_seen > 0) {
+ g_assert_cmpint(tdata->ready_seen, ==, 1);
+ tdata->ready_seen--;
+ if (tdata->addr_commit > 0) {
+ g_assert_cmpint(tdata->addr_commit, ==, 1);
+ tdata->addr_commit--;
+ g_assert(nm_utils_ip4_address_is_link_local(tdata->addr_commit_addr));
+ _LOGT("remove address %s that previously passed ACD",
+ _nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr));
+ if (!nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
+ TEST_L3_IPV4LL_TAG(tdata, 1),
+ FALSE))
+ g_assert_not_reached();
+ nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll));
+ nm_l3cfg_commit_type_unregister(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
+ g_steal_pointer(&tdata->l3cfg_commit_type_1));
+ }
+ }
+ return;
+ }
+}
+
+static void
+test_l3_ipv4ll(gconstpointer test_data)
+{
+ const int TEST_IDX = GPOINTER_TO_INT(test_data);
+ nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {};
+ const TestFixture1 * f;
+ gs_unref_object NML3Cfg *l3cfg0 = NULL;
+ TestL3IPv4LLData tdata_stack = {
+ .f = NULL,
+ };
+ TestL3IPv4LLData *const tdata = &tdata_stack;
+ NMTstpAcdDefender * acd_defender_1 = NULL;
+ NMTstpAcdDefender * acd_defender_2 = NULL;
+ nm_auto_unref_l3ipv4ll NML3IPv4LL * l3ipv4ll = NULL;
+ gint64 start_time_msec;
+ gint64 total_poll_time_msec;
+ nm_auto_remove_l3ipv4ll_registration NML3IPv4LLRegistration *l3ipv4ll_reg = NULL;
+ char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+
+ _LOGD("test start (/l3-ipv4ll/%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-ipv4ll");
+
+ g_test_skip(msg);
+ return;
+ }
+
+ f = _test_fixture_1_setup(&test_fixture, TEST_IDX);
+
+ tdata->f = f;
+
+ if (tdata->f->test_idx == 1)
+ tdata->acd_timeout_msec = 0;
+ else
+ tdata->acd_timeout_msec = L3IPV4LL_ACD_TIMEOUT_MSEC;
+
+ _test_l3_ipv4ll_maybe_add_addr_4(tdata, 0, 4, &tdata->has_addr4_101, "192.168.133.101");
+
+ l3cfg0 = _netns_access_l3cfg(f->netns, f->ifindex0);
+
+ g_signal_connect(l3cfg0,
+ NM_L3CFG_SIGNAL_NOTIFY,
+ G_CALLBACK(_test_l3_ipv4ll_signal_notify),
+ tdata);
+
+ l3ipv4ll = nm_l3_ipv4ll_new(l3cfg0);
+
+ tdata->l3ipv4ll = l3ipv4ll;
+
+ g_assert_cmpint(nm_l3_ipv4ll_get_ifindex(l3ipv4ll), ==, f->ifindex0);
+ g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_DISABLED);
+ g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll), ==, 0u);
+
+ if (tdata->f->test_idx == 1) {
+ if (nmtst_get_rand_one_case_in(2))
+ l3ipv4ll_reg = nm_l3_ipv4ll_register_new(l3ipv4ll, tdata->acd_timeout_msec);
+ } else
+ l3ipv4ll_reg = nm_l3_ipv4ll_register_new(l3ipv4ll, tdata->acd_timeout_msec);
+
+ g_assert(tdata->acd_timeout_msec == 0 || l3ipv4ll_reg);
+ g_assert(!l3ipv4ll_reg || l3ipv4ll == nm_l3_ipv4ll_register_get_instance(l3ipv4ll_reg));
+
+ if (tdata->acd_timeout_msec == 0) {
+ g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_DISABLED);
+ g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll), ==, 0u);
+ } else {
+ g_assert_cmpint(nm_l3_ipv4ll_get_state(l3ipv4ll), ==, NM_L3_IPV4LL_STATE_PROBING);
+ if (f->test_idx == 1) {
+ g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll),
+ ==,
+ nmtst_inet4_from_string("169.254.30.158"));
+ } else {
+ g_assert_cmpint(nm_l3_ipv4ll_get_addr(l3ipv4ll),
+ ==,
+ nmtst_inet4_from_string("169.254.17.45"));
+ }
+ g_assert(nm_l3_ipv4ll_get_l3cd(l3ipv4ll));
+ }
+
+ _test_l3_ipv4ll_maybe_add_addr_4(tdata, 0, 4, &tdata->has_addr4_101, "192.168.133.101");
+
+ if (tdata->f->test_idx == 2 && nmtst_get_rand_one_case_in(3)) {
+ in_addr_t a = nm_l3_ipv4ll_get_addr(l3ipv4ll);
+
+ g_assert(nm_utils_ip4_address_is_link_local(a));
+ _test_l3_ipv4ll_maybe_add_addr_4(tdata,
+ tdata->f->ifindex1,
+ 2,
+ &tdata->add_conflict_done,
+ _nm_utils_inet4_ntop(a, sbuf_addr));
+ g_assert_cmpint(tdata->f->hwaddr1.len, ==, sizeof(NMEtherAddr));
+ acd_defender_2 =
+ nmtstp_acd_defender_new(tdata->f->ifindex1, a, &tdata->f->hwaddr1.ether_addr);
+ }
+
+ start_time_msec = nm_utils_get_monotonic_timestamp_msec();
+ total_poll_time_msec =
+ (L3IPV4LL_ACD_TIMEOUT_MSEC * 3 / 2) + (nmtst_get_rand_uint32() % L3IPV4LL_ACD_TIMEOUT_MSEC);
+ _LOGT("poll 1 start (wait %" G_GINT64_FORMAT " msec)", total_poll_time_msec);
+ while (TRUE) {
+ gint64 next_timeout_msec;
+
+ next_timeout_msec =
+ start_time_msec + total_poll_time_msec - nm_utils_get_monotonic_timestamp_msec();
+ if (next_timeout_msec <= 0)
+ break;
+
+ next_timeout_msec = NM_MIN(next_timeout_msec, nmtst_get_rand_uint32() % 1000u);
+ nmtst_main_context_iterate_until(NULL, next_timeout_msec, FALSE);
+ _LOGT("poll 1 intermezzo");
+
+ _test_l3_ipv4ll_maybe_add_addr_4(tdata,
+ 0,
+ 1 + total_poll_time_msec / 1000,
+ &tdata->has_addr4_101,
+ "192.168.133.101");
+
+ if (tdata->addr_commit == 1 && !tdata->add_conflict_checked) {
+ tdata->add_conflict_checked = TRUE;
+ _test_l3_ipv4ll_maybe_add_addr_4(
+ tdata,
+ tdata->f->ifindex1,
+ 2,
+ &tdata->add_conflict_done,
+ _nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr));
+ if (tdata->add_conflict_done)
+ total_poll_time_msec += L3IPV4LL_ACD_TIMEOUT_MSEC / 2;
+ g_assert_cmpint(tdata->f->hwaddr1.len, ==, sizeof(NMEtherAddr));
+ acd_defender_2 = nmtstp_acd_defender_new(tdata->f->ifindex1,
+ tdata->addr_commit_addr,
+ &tdata->f->hwaddr1.ether_addr);
+ }
+ }
+ _LOGT("poll 1 end");
+
+ if (tdata->addr_commit || nmtst_get_rand_bool()) {
+ nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(l3ipv4ll),
+ TEST_L3_IPV4LL_TAG(tdata, 1),
+ FALSE);
+ }
+
+ nmtstp_acd_defender_destory(g_steal_pointer(&acd_defender_1));
+ nmtstp_acd_defender_destory(g_steal_pointer(&acd_defender_2));
+
+ nm_l3cfg_commit_type_unregister(l3cfg0, g_steal_pointer(&tdata->l3cfg_commit_type_1));
+
+ g_signal_handlers_disconnect_by_func(l3cfg0, G_CALLBACK(_test_l3_ipv4ll_signal_notify), tdata);
+}
+
+/*****************************************************************************/
+
NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup;
void
_nmtstp_init_tests(int *argc, char ***argv)
{
- nmtst_init_with_logging(argc, argv, NULL, "ALL");
+ nmtst_init_with_logging(argc, argv, "ERR", "ALL");
}
void
@@ -478,4 +790,6 @@ _nmtstp_setup_tests(void)
g_test_add_data_func("/l3cfg/2", GINT_TO_POINTER(2), test_l3cfg);
g_test_add_data_func("/l3cfg/3", GINT_TO_POINTER(3), test_l3cfg);
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);
}