diff options
author | Thomas Haller <thaller@redhat.com> | 2020-10-23 17:12:42 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2020-10-23 17:12:42 +0200 |
commit | b4ced8b91106e0506a4e27d916c4164287337a8b (patch) | |
tree | 79ef8b1dabdc2fef73201dd89c4d60883aab60f1 | |
parent | ad18612c3644e7797bc173077bfc8731b0b5fd97 (diff) | |
parent | 17269b05200aa565b5de7054a224e8ecd5b6aeca (diff) | |
download | NetworkManager-b4ced8b91106e0506a4e27d916c4164287337a8b.tar.gz |
l3cfg: merge branch 'th/l3cfg-13'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/657
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | shared/nm-glib-aux/nm-glib.h | 2 | ||||
-rw-r--r-- | shared/nm-std-aux/nm-std-aux.h | 30 | ||||
-rw-r--r-- | shared/nm-utils/nm-test-utils.h | 25 | ||||
-rw-r--r-- | src/meson.build | 1 | ||||
-rw-r--r-- | src/nm-l3-config-data.c | 2 | ||||
-rw-r--r-- | src/nm-l3-ipv4ll.c | 1001 | ||||
-rw-r--r-- | src/nm-l3-ipv4ll.h | 103 | ||||
-rw-r--r-- | src/nm-l3cfg.c | 184 | ||||
-rw-r--r-- | src/nm-l3cfg.h | 49 | ||||
-rw-r--r-- | src/nm-netns.c | 13 | ||||
-rw-r--r-- | src/nm-netns.h | 2 | ||||
-rw-r--r-- | src/platform/nm-platform.c | 36 | ||||
-rw-r--r-- | src/platform/nm-platform.h | 125 | ||||
-rw-r--r-- | src/platform/tests/test-common.c | 146 | ||||
-rw-r--r-- | src/platform/tests/test-common.h | 9 | ||||
-rw-r--r-- | src/tests/test-l3cfg.c | 336 |
17 files changed, 1874 insertions, 192 deletions
diff --git a/Makefile.am b/Makefile.am index 0d02d3ff52..61437e4cbf 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 5125617d78..7330b007a8 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 762f104be3..44d0dd273b 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 9d48488297..6c945b1920 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 667a6a2ce2..b3eddadcdd 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 864924e88d..9b1e017b14 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 0000000000..c5dd7ec88c --- /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_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_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_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, ¬ify_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(¬ify_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 0000000000..1d45b0c51a --- /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 b689206992..c6ad7f100d 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 d89e2fa280..d6aa1172a8 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 2970e45c91..c5a445ac67 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 bb0c955ef3..5ae0c294b3 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 eca201b087..ca5080b009 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 17f3a2fd5a..df637fe03e 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 a7fe590567..e225f33923 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 1d996f4644..3a4bcb3b66 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 49e45d3881..e3956bae57 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); } |