From c3fe895ebcca12339f8ce92be2c961bdd1f33e6b Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 22 Oct 2020 13:29:27 +0200 Subject: [PATCH 01/13] trivial: whitespace fixes --- shared/nm-glib-aux/nm-glib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); \ \ From b38075b751710a1d954188e595e65749d67abf29 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 22 Oct 2020 10:22:48 +0200 Subject: [PATCH 02/13] shared: add nm_add_u32_clamped() helper --- shared/nm-std-aux/nm-std-aux.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/shared/nm-std-aux/nm-std-aux.h b/shared/nm-std-aux/nm-std-aux.h index 762f104be3..b181f93821 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. * From c947c51651ad3a5a1914f368c4b3ca948f63e122 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 22 Oct 2020 11:50:23 +0200 Subject: [PATCH 03/13] shared: add nm_ptr_to_uintptr() helper --- shared/nm-std-aux/nm-std-aux.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/shared/nm-std-aux/nm-std-aux.h b/shared/nm-std-aux/nm-std-aux.h index b181f93821..44d0dd273b 100644 --- a/shared/nm-std-aux/nm-std-aux.h +++ b/shared/nm-std-aux/nm-std-aux.h @@ -780,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); \ @@ -826,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))) From dd7ce063fb46e1a8e17e5faf70c7cda70924e1e9 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 21 Oct 2020 16:49:46 +0200 Subject: [PATCH 04/13] tests: add nmtst_get_rand_one_case_in() helper --- shared/nm-utils/nm-test-utils.h | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h index 9d48488297..431397f0bf 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) { From 6cb688840461c0093337e66184bcd1a5106a8c3f Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 21 Oct 2020 12:51:48 +0200 Subject: [PATCH 05/13] platform/tests: better handling "timeout_msec" argument in nmtst_main_context_iterate_until() nmtst_main_context_iterate_until() is a macro, and we don't want to restrict the valid integer type (or range) of the "timeout_msec" argument. In particular, if the user calculates a timeout with "timestamp_msec - now_msec", the resulting "timeout_msec" might be a negative gint64. We should handle that gracefully, and not let it be cast to a huge unsigned int. --- shared/nm-utils/nm-test-utils.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared/nm-utils/nm-test-utils.h b/shared/nm-utils/nm-test-utils.h index 431397f0bf..6c945b1920 100644 --- a/shared/nm-utils/nm-test-utils.h +++ b/shared/nm-utils/nm-test-utils.h @@ -1204,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; \ \ - _source = g_timeout_source_new(timeout_msec); \ + g_assert_cmpint(_timeout_msec0, ==, _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); \ \ From 4561b3c114c13f5a8ad52917db89c949e748bb84 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 21 Oct 2020 12:13:14 +0200 Subject: [PATCH 06/13] l3cfg: various bugfixes in NML3Cfg - fix iterating logic in nm_l3cfg_add_config() - fix preserving order during nm_l3cfg_commit_type_register() - fix logic in _l3cfg_externally_removed_objs_drop_unused() to handle all object types and not only routes. - in nm-l3-config-data.c, fix coercing the route metric from metric_any to the default metric. - in _l3cfg_externally_removed_objs_drop_unused() and _l3cfg_externally_removed_objs_track() ensure that the object is kept alive long enough. The externally_removed_objs_hash hash keeps a reference to the object, so when removing it, we either must no longer access the object, or delay the destruction until later. --- src/nm-l3-config-data.c | 2 +- src/nm-l3cfg.c | 30 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/nm-l3-config-data.c b/src/nm-l3-config-data.c index 864924e88d..ef8d67a8f9 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 = default_route_metric_x[IS_IPv4]; } if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT(r_src)) { diff --git a/src/nm-l3cfg.c b/src/nm-l3cfg.c index b689206992..d07ec90375 100644 --- a/src/nm-l3cfg.c +++ b/src/nm-l3cfg.c @@ -576,15 +576,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 +602,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; @@ -2703,12 +2710,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 +3509,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; } From b38fbb3bf621a45c8a00beb290ae0c29fb226ba3 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 21 Oct 2020 12:14:08 +0200 Subject: [PATCH 07/13] l3cfg: add nm_l3_acd_addr_info_find_track_info() helper --- src/nm-l3cfg.h | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/nm-l3cfg.h b/src/nm-l3cfg.h index d89e2fa280..0bbe692408 100644 --- a/src/nm-l3cfg.h +++ b/src/nm-l3cfg.h @@ -55,6 +55,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, From 15f5d10352544b628164f38f207e29236b0ff9e5 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 21 Oct 2020 12:15:06 +0200 Subject: [PATCH 08/13] l3cfg: add nm_netns_get_l3cfg() accessor This is more for debugging and testing. Usually you want to call nm_netns_access_l3cfg() which creates a NML3Cfg instance, if necessary. --- src/nm-netns.c | 13 +++++++++++++ src/nm-netns.h | 2 ++ 2 files changed, 15 insertions(+) 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 @@ -125,6 +125,19 @@ _l3cfg_weak_notify(gpointer data, GObject *where_the_object_was) g_object_unref(self); } +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) { 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); /*****************************************************************************/ From f6a8aca1b41e31eb71709ef8b3500a0e7229e246 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 21 Oct 2020 12:56:50 +0200 Subject: [PATCH 09/13] l3cfg/tests: cleanup l3cfg tests and set a fixed MAC address on the test interfaces It's better to have a well-known, fixed MAC address on our test veth devices. Also, because later we will test IPv4 link local addressing, which generates addresses by hashing the MAC address (among others). --- src/tests/test-l3cfg.c | 48 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/tests/test-l3cfg.c b/src/tests/test-l3cfg.c index 49e45d3881..06b69b1098 100644 --- a/src/tests/test-l3cfg.c +++ b/src/tests/test-l3cfg.c @@ -10,6 +10,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 +45,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 +60,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 +357,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, @@ -450,12 +480,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); From 60da4cb494ea68007ab7b31e2aaed012d85dd084 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 22 Oct 2020 10:29:03 +0200 Subject: [PATCH 10/13] platform: interpret metric_any for routes to accept offset for default metric The DHCP client likes to order multiple default routes by adding them with different, increasing metric. To support that, let "metric_any" not completely disable the "metric" field, but instead interpret it as an offset that should be added to the default metric. --- src/nm-l3-config-data.c | 2 +- src/platform/nm-platform.c | 36 +++++------ src/platform/nm-platform.h | 125 ++++++++++++++++++++----------------- 3 files changed, 85 insertions(+), 78 deletions(-) diff --git a/src/nm-l3-config-data.c b/src/nm-l3-config-data.c index ef8d67a8f9..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 = 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/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 From 3874e061d40633f5be4e60d8161ff7395dedc1ce Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 21 Oct 2020 21:25:43 +0200 Subject: [PATCH 11/13] platform/tests: add NMTstpAcdDefender helper for testing --- src/platform/tests/test-common.c | 146 ++++++++++++++++++++++++++++++- src/platform/tests/test-common.h | 9 ++ 2 files changed, 154 insertions(+), 1 deletion(-) 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 #include #include #include #include -#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); From 3caf419df650e4f6cca2a44b7968971c6d0780c5 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 22 Oct 2020 12:18:39 +0200 Subject: [PATCH 12/13] l3cfg: combine NML3AcdAddrTrackInfo and AcdTrackData On the one hand, we want to keep the private fields internal. On the other hand, we want to directly expose the NML3AcdAddrTrackInfo, so that the user can access them without copying or calling a function. Previously, there was some union ugliness and some padding involved. That was probably correct, but lets solve this somewhat nicer by having the private fields in a "_priv" struct and use NML3AcdAddrTrackInfo throughout. --- src/nm-l3cfg.c | 126 ++++++++++++++++++++----------------------------- src/nm-l3cfg.h | 12 +++-- 2 files changed, 58 insertions(+), 80 deletions(-) diff --git a/src/nm-l3cfg.c b/src/nm-l3cfg.c index d07ec90375..b6197661d2 100644 --- a/src/nm-l3cfg.c +++ b/src/nm-l3cfg.c @@ -57,32 +57,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 { @@ -984,13 +958,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); @@ -1005,7 +979,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); } @@ -1021,17 +995,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); @@ -1042,7 +1016,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, @@ -1051,10 +1025,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; @@ -1390,19 +1364,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++; @@ -1462,11 +1436,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; @@ -1516,36 +1490,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 @@ -1563,7 +1539,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 @@ -1801,7 +1777,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. * diff --git a/src/nm-l3cfg.h b/src/nm-l3cfg.h index 0bbe692408..cd9dd45b1a 100644 --- a/src/nm-l3cfg.h +++ b/src/nm-l3cfg.h @@ -40,11 +40,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 { From 17269b05200aa565b5de7054a224e8ecd5b6aeca Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 16 Sep 2020 19:14:20 +0200 Subject: [PATCH 13/13] l3cfg: add support for IPv4 link local addresses (ipv4ll) to NML3Cfg 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. --- Makefile.am | 2 + src/meson.build | 1 + src/nm-l3-ipv4ll.c | 1001 ++++++++++++++++++++++++++++++++++++++++ src/nm-l3-ipv4ll.h | 103 +++++ src/nm-l3cfg.c | 28 +- src/nm-l3cfg.h | 10 + src/tests/test-l3cfg.c | 288 +++++++++++- 7 files changed, 1427 insertions(+), 6 deletions(-) create mode 100644 src/nm-l3-ipv4ll.c create mode 100644 src/nm-l3-ipv4ll.h 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 + +#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 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); }