summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-09-16 19:14:20 +0200
committerThomas Haller <thaller@redhat.com>2020-10-22 22:58:33 +0200
commitdff70f5c044060010aba874076adf2b3f12de44f (patch)
tree44e0073dd374cb456a34d01e305b4f217c3fb737
parente421513b6d4a266c74f34911dd4b3b580e0f9604 (diff)
downloadNetworkManager-th/l3cfg-13.tar.gz
l3cfg: add support for IPv4 link local addresses (ipv4ll) to NML3Cfgth/l3cfg-13
NML3Cfg already handles IPv4 ACD. IPv4LL is just a small additional layer on top of that, so it makes sense that it also is handled by NML3Cfg. Also, the overall goal is that multiple NMDevice and NMVpnConnection instances can cooperate independently. So if multiple "users" enable IPv4LL on an interface, then we should only run it once. This is achieved by NML3IPv4LL's API where users register what they want, and NML3IPv4LL figures out what that means as a whole. Also, we thus will no longer need to use sd_ipv4ll/n-ipv4ll, because we implement it ourself.
-rw-r--r--Makefile.am2
-rw-r--r--src/meson.build1
-rw-r--r--src/nm-l3-ipv4ll.c1001
-rw-r--r--src/nm-l3-ipv4ll.h103
-rw-r--r--src/nm-l3cfg.c28
-rw-r--r--src/nm-l3cfg.h10
-rw-r--r--src/tests/test-l3cfg.c288
7 files changed, 1427 insertions, 6 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/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-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->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 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 b6197661d2..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"
/*****************************************************************************/
@@ -294,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"),
@@ -338,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);
@@ -371,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;
}
@@ -2651,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,
diff --git a/src/nm-l3cfg.h b/src/nm-l3cfg.h
index cd9dd45b1a..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))
@@ -104,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 {
@@ -122,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/tests/test-l3cfg.c b/src/tests/test-l3cfg.c
index 06b69b1098..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"
@@ -404,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;
@@ -493,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
@@ -508,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);
}