/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "src/core/nm-default-daemon.h" #include #include "nm-l3cfg.h" #include "nm-l3-ipv4ll.h" #include "nm-l3-ipv6ll.h" #include "nm-netns.h" #include "libnm-platform/nm-platform.h" #include "platform/tests/test-common.h" /*****************************************************************************/ 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_l3cfg_get(netns, ifindex)); l3cfg = nm_netns_l3cfg_acquire(netns, ifindex); g_assert(NM_IS_L3CFG(l3cfg)); return l3cfg; } /*****************************************************************************/ typedef struct { int test_idx; NMPlatform *platform; NMNetns *netns; NMDedupMultiIndex *multiidx; const char *ifname0; const char *ifname1; NMPLinkAddress hwaddr0; NMPLinkAddress hwaddr1; int ifindex0; int ifindex1; } TestFixture1; static const TestFixture1 * _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); f->test_idx = test_idx; f->ifname0 = "nm-test-veth0"; f->ifname1 = "nm-test-veth1"; f->platform = g_object_ref(NM_PLATFORM_GET); f->multiidx = nm_dedup_multi_index_ref(nm_platform_get_multi_idx(f->platform)); f->netns = nm_netns_new(f->platform); 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->ifindex1 = l1->ifindex; 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_change_flags(f->platform, f->ifindex0, IFF_UP, TRUE) >= 0); g_assert(nm_platform_link_change_flags(f->platform, f->ifindex1, IFF_UP, TRUE) >= 0); return f; } static void _test_fixture_1_teardown(TestFixture1 *f) { g_assert(f); if (f->test_idx == 0) return; _LOGD("test teatdown"); nmtstp_link_delete(f->platform, -1, f->ifindex0, f->ifname0, TRUE); g_assert(!nm_platform_link_get(f->platform, f->ifindex0)); g_assert(!nm_platform_link_get(f->platform, f->ifindex1)); g_assert(!nm_platform_link_get_by_ifname(f->platform, f->ifname0)); g_assert(!nm_platform_link_get_by_ifname(f->platform, f->ifname1)); g_object_unref(f->netns); g_object_unref(f->platform); nm_dedup_multi_index_unref(f->multiidx); *f = (TestFixture1){ .test_idx = 0, }; } /*****************************************************************************/ typedef enum { TEST_L3CFG_NOTIFY_TYPE_NONE, TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL, TEST_L3CFG_NOTIFY_TYPE_COMMIT_1, TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1, } TestL3cfgNotifyType; typedef struct { const TestFixture1 *f; bool has_addr4_101 : 1; bool add_addr4_101 : 1; guint32 acd_timeout_msec_a; NML3AcdDefendType acd_defend_type_a; TestL3cfgNotifyType notify_type; guint pre_commit_event_count; guint post_commit_event_count; guint general_event_count; guint general_event_flags; union { struct { int cb_count; bool expected_probe_result : 1; bool acd_event_ready_45 : 1; bool acd_event_ready_101 : 1; } wait_for_acd_ready_1; } notify_result; } TestL3cfgData; static void _test_l3cfg_data_set_notify_type(TestL3cfgData *tdata, TestL3cfgNotifyType notify_type) { g_assert(tdata); tdata->notify_type = notify_type; tdata->pre_commit_event_count = 0; tdata->post_commit_event_count = 0; tdata->general_event_count = 0; tdata->general_event_flags = 0; memset(&tdata->notify_result, 0, sizeof(tdata->notify_result)); } static void _test_l3cfg_signal_notify(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, TestL3cfgData *tdata) { guint i; 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_PLATFORM_CHANGE_ON_IDLE) g_assert(notify_data->platform_change_on_idle.obj_type_flags != 0u); else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE) { g_assert(NMP_OBJECT_IS_VALID(notify_data->platform_change.obj)); g_assert(notify_data->platform_change.change_type != 0); } else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) { g_assert_cmpint(notify_data->acd_event.info.n_track_infos, >=, 1); g_assert(notify_data->acd_event.info.track_infos); for (i = 0; i < notify_data->acd_event.info.n_track_infos; i++) { const NML3AcdAddrTrackInfo *ti = ¬ify_data->acd_event.info.track_infos[i]; nm_assert(NMP_OBJECT_GET_TYPE(ti->obj) == NMP_OBJECT_TYPE_IP4_ADDRESS); nm_assert(NMP_OBJECT_CAST_IP4_ADDRESS(ti->obj)->address == notify_data->acd_event.info.addr); nm_assert(NM_IS_L3_CONFIG_DATA(ti->l3cd)); nm_assert(ti->tag); } } else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED) { g_assert(!notify_data->l3cd_changed.l3cd_old || NM_IS_L3_CONFIG_DATA(notify_data->l3cd_changed.l3cd_old)); g_assert(!notify_data->l3cd_changed.l3cd_new || NM_IS_L3_CONFIG_DATA(notify_data->l3cd_changed.l3cd_new)); return; } switch (tdata->notify_type) { case TEST_L3CFG_NOTIFY_TYPE_NONE: g_assert_not_reached(); break; case TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL: if (NM_IN_SET(notify_data->notify_type, NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE)) return; g_assert_not_reached(); return; case TEST_L3CFG_NOTIFY_TYPE_COMMIT_1: g_assert_cmpint(tdata->post_commit_event_count, ==, 0); switch (notify_data->notify_type) { case NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT: g_assert_cmpint(tdata->pre_commit_event_count, ==, 0); tdata->pre_commit_event_count++; return; case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT: tdata->post_commit_event_count++; return; case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT: switch (tdata->f->test_idx) { case 2: case 3: nmtst_assert_ip4_address(notify_data->acd_event.info.addr, "192.168.133.45"); if (tdata->f->test_idx == 2) g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_DEFENDING); else g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING); g_assert(tdata->general_event_count == 0); tdata->general_event_count++; return; case 4: if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.45")) { g_assert(!NM_FLAGS_HAS(tdata->general_event_flags, 0x1u)); tdata->general_event_flags |= 0x1u; g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING); tdata->general_event_count++; } else if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.101")) { g_assert(!NM_FLAGS_HAS(tdata->general_event_flags, 0x4u)); tdata->general_event_flags |= 0x4u; g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING); tdata->general_event_count++; } else g_assert_not_reached(); return; default: g_assert_not_reached(); return; } case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE: return; default: g_assert_not_reached(); return; } case TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1: { int num_acd_completed_events = 1 + 2 + (tdata->add_addr4_101 ? (tdata->has_addr4_101 ? 1 : 3) : 0); if (NM_IN_SET(notify_data->notify_type, NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT, NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE)) return; if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) { if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.45")) { g_assert(NM_IN_SET(notify_data->acd_event.info.state, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING)); tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_45 = TRUE; } else if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.101")) { if (tdata->has_addr4_101) { g_assert( NM_IN_SET(notify_data->acd_event.info.state, NM_L3_ACD_ADDR_STATE_USED)); } else { g_assert(NM_IN_SET(notify_data->acd_event.info.state, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING)); tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_101 = TRUE; } } else g_assert_not_reached(); g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, <, num_acd_completed_events); tdata->notify_result.wait_for_acd_ready_1.cb_count++; return; } if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT) { g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, >, 0); g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, <, num_acd_completed_events); tdata->notify_result.wait_for_acd_ready_1.cb_count++; nmtstp_platform_ip_addresses_assert( tdata->f->platform, tdata->f->ifindex0, TRUE, TRUE, TRUE, tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_45 ? "192.168.133.45" : NULL, tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_101 ? "192.168.133.101" : NULL, "1:2:3:4::45"); return; } g_assert_not_reached(); return; } } g_assert_not_reached(); } static void test_l3cfg(gconstpointer test_data) { const int TEST_IDX = GPOINTER_TO_INT(test_data); const guint32 ACD_TIMEOUT_BASE_MSEC = 1000; nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {}; const TestFixture1 *f; NML3CfgCommitTypeHandle *commit_type_1; NML3CfgCommitTypeHandle *commit_type_2; gs_unref_object NML3Cfg *l3cfg0 = NULL; nm_auto_unref_l3cd const NML3ConfigData *l3cd_a = NULL; TestL3cfgData tdata_stack = { .f = NULL, }; TestL3cfgData *const tdata = &tdata_stack; _LOGD("test start (/l3cfg/%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-l3cfg"); g_test_skip(msg); return; } f = _test_fixture_1_setup(&test_fixture, TEST_IDX); tdata->f = f; tdata->has_addr4_101 = (f->test_idx == 4 && nmtst_get_rand_bool()); tdata->add_addr4_101 = (f->test_idx == 4 && nmtst_get_rand_bool()); tdata->acd_timeout_msec_a = NM_IN_SET(f->test_idx, 3, 4) ? ACD_TIMEOUT_BASE_MSEC : 0u; tdata->acd_defend_type_a = NM_IN_SET(f->test_idx, 4) ? nmtst_rand_select(NM_L3_ACD_DEFEND_TYPE_NEVER, NM_L3_ACD_DEFEND_TYPE_ONCE, NM_L3_ACD_DEFEND_TYPE_ALWAYS) : NM_L3_ACD_DEFEND_TYPE_NEVER; if (tdata->has_addr4_101) { nmtstp_ip4_address_add(f->platform, -1, f->ifindex1, nmtst_inet4_from_string("192.168.133.101"), 24, nmtst_inet4_from_string("192.168.133.101"), 100000, 0, 0, NULL); } 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, "test1"); 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, NM_L3_CFG_COMMIT_TYPE_ASSUME, NM_L3_CFG_COMMIT_TYPE_UPDATE), NULL, "test2"); } else commit_type_2 = NULL; switch (f->test_idx) { case 1: break; case 2: case 3: case 4: { nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; l3cd = nm_l3_config_data_new(f->multiidx, f->ifindex0, NM_IP_CONFIG_SOURCE_UNKNOWN); nm_l3_config_data_add_address_4( l3cd, NM_PLATFORM_IP4_ADDRESS_INIT(.address = nmtst_inet4_from_string("192.168.133.45"), .peer_address = nmtst_inet4_from_string("192.168.133.45"), .plen = 24, )); if (tdata->add_addr4_101) { nm_l3_config_data_add_address_4( l3cd, NM_PLATFORM_IP4_ADDRESS_INIT(.address = nmtst_inet4_from_string("192.168.133.101"), .peer_address = nmtst_inet4_from_string("192.168.133.101"), .plen = 24, )); } nm_l3_config_data_add_address_6( l3cd, NM_PLATFORM_IP6_ADDRESS_INIT(.address = *nmtst_inet6_from_string("1:2:3:4::45"), .plen = 64, )); if (nmtst_get_rand_one_case_in(2)) nm_l3_config_data_seal(l3cd); l3cd_a = g_steal_pointer(&l3cd); break; } } nm_l3_config_data_log(l3cd_a, "l3cd_a", "platform-test: l3cd_a: ", LOGL_DEBUG, LOGD_PLATFORM); if (l3cd_a) { nm_l3cfg_add_config(l3cfg0, GINT_TO_POINTER('a'), nmtst_get_rand_bool(), l3cd_a, 'a', 0, 0, NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, 0, 0, NM_DNS_PRIORITY_DEFAULT_NORMAL, NM_DNS_PRIORITY_DEFAULT_NORMAL, tdata->acd_defend_type_a, tdata->acd_timeout_msec_a, NM_L3CFG_CONFIG_FLAGS_NONE, NM_L3_CONFIG_MERGE_FLAGS_NONE); } nm_l3_config_data_log(nm_l3cfg_get_combined_l3cd(l3cfg0, FALSE), "test", "platform-test: l3cfg0: ", LOGL_DEBUG, LOGD_PLATFORM); _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_COMMIT_1); nm_l3cfg_commit(l3cfg0, NM_L3_CFG_COMMIT_TYPE_REAPPLY); g_assert_cmpint(tdata->pre_commit_event_count, ==, 1); g_assert_cmpint(tdata->post_commit_event_count, ==, 1); _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE); nmtstp_platform_ip_addresses_assert(tdata->f->platform, tdata->f->ifindex0, TRUE, TRUE, TRUE, NM_IN_SET(f->test_idx, 2) ? "192.168.133.45" : NULL, NM_IN_SET(f->test_idx, 2, 3, 4) ? "1:2:3:4::45" : NULL); if (NM_IN_SET(f->test_idx, 1, 2)) { _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL); _LOGT("poll 1 start"); nmtst_main_context_iterate_until(NULL, nmtst_get_rand_uint32() % (ACD_TIMEOUT_BASE_MSEC * 5u), FALSE); _LOGT("poll 1 end"); _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE); } if (NM_IN_SET(f->test_idx, 3, 4)) { _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1); tdata->notify_result.wait_for_acd_ready_1.expected_probe_result = TRUE; _LOGT("poll 2 start"); nmtst_main_context_iterate_until( NULL, ACD_TIMEOUT_BASE_MSEC * 3u / 2u + (nmtst_get_rand_uint32() % (2u * ACD_TIMEOUT_BASE_MSEC)), FALSE); _LOGT("poll 2 end"); g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, ==, 1 + 2 + (tdata->add_addr4_101 ? (tdata->has_addr4_101 ? 1 : 3) : 0)); _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE); } g_signal_handlers_disconnect_by_func(l3cfg0, G_CALLBACK(_test_l3cfg_signal_notify), tdata); nm_l3cfg_commit_type_unregister(l3cfg0, commit_type_1); nm_l3cfg_commit_type_unregister(l3cfg0, commit_type_2); if (nmtst_get_rand_one_case_in(3)) _test_fixture_1_teardown(&test_fixture); nm_l3cfg_remove_config_all(l3cfg0, GINT_TO_POINTER('a')); if (nmtst_get_rand_one_case_in(3)) _test_fixture_1_teardown(&test_fixture); _LOGD("test end (/l3cfg/%d)", TEST_IDX); } /*****************************************************************************/ #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_DNS_PRIORITY_DEFAULT_NORMAL, NM_DNS_PRIORITY_DEFAULT_NORMAL, NM_L3_ACD_DEFEND_TYPE_ONCE, nmtst_get_rand_bool() ? tdata->acd_timeout_msec : 0u, NM_L3CFG_CONFIG_FLAGS_NONE, NM_L3_CONFIG_MERGE_FLAGS_NONE)) g_assert_not_reached(); nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll), NM_L3_CFG_COMMIT_TYPE_AUTO); 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, "test"); } } 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))) g_assert_not_reached(); nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll), NM_L3_CFG_COMMIT_TYPE_AUTO); 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)); } nmtstp_acd_defender_destroy(g_steal_pointer(&acd_defender_1)); nmtstp_acd_defender_destroy(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); } /*****************************************************************************/ #define _LLADDR_TEST1 "fe80::dd5a:8a44:48bc:3ad" #define _LLADDR_TEST2 "fe80::878b:938e:46f9:4807" typedef struct { const TestFixture1 *f; NML3Cfg *l3cfg0; NML3IPv6LL *l3ipv6ll; int step; int ipv6ll_callback_step; bool steps_done : 1; const NMPObject *lladdr0; } TestL3IPv6LLData; static const NMPlatformIP6Address * _test_l3_ipv6ll_find_lladdr(TestL3IPv6LLData *tdata, int ifindex) { const NMPlatformIP6Address *found = NULL; NMDedupMultiIter iter; const NMPObject *obj; NMPLookup lookup; g_assert(tdata); nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex); nm_platform_iter_obj_for_each (&iter, tdata->f->platform, &lookup, &obj) { const NMPlatformIP6Address *a = NMP_OBJECT_CAST_IP6_ADDRESS(obj); if (!IN6_IS_ADDR_LINKLOCAL(&a->address)) continue; if (!found) found = a; else g_assert_not_reached(); } return found; } static const NMPObject * _test_l3_ipv6ll_find_lladdr_wait(TestL3IPv6LLData *tdata, int ifindex) { const NMPObject *obj = NULL; nmtst_main_context_iterate_until_assert(NULL, 3000, ({ const NMPlatformIP6Address *a; a = _test_l3_ipv6ll_find_lladdr(tdata, ifindex); if (a && !NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TENTATIVE)) obj = NMP_OBJECT_UP_CAST(a); obj; })); return obj; } static const NMPlatformIP6Address * _test_l3_ipv6ll_find_inet6(TestL3IPv6LLData *tdata, const struct in6_addr *addr) { const NMPlatformIP6Address *a; a = nmtstp_platform_ip6_address_find(nm_l3cfg_get_platform(tdata->l3cfg0), nmtst_get_rand_bool() ? 0 : tdata->f->ifindex0, addr); if (a) { g_assert_cmpint(a->ifindex, ==, tdata->f->ifindex0); g_assert_cmpmem(addr, sizeof(*addr), &a->address, sizeof(a->address)); } g_assert(a == nm_platform_ip6_address_get(nm_l3cfg_get_platform(tdata->l3cfg0), tdata->f->ifindex0, addr)); return a; } static void _test_l3_ipv6ll_signal_notify(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, TestL3IPv6LLData *tdata) { g_assert_cmpint(tdata->step, >=, 1); g_assert_cmpint(tdata->step, <=, 2); } static void _test_l3_ipv6ll_callback_changed(NML3IPv6LL *ipv6ll, NML3IPv6LLState state, const struct in6_addr *lladdr, gpointer user_data) { TestL3IPv6LLData *tdata = user_data; int step = tdata->ipv6ll_callback_step++; const NMPlatformIP6Address *a1; g_assert_cmpint(tdata->step, ==, 1); g_assert(!tdata->steps_done); switch (step) { case 0: if (NM_IN_SET(tdata->f->test_idx, 1, 2, 4)) { g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS); g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST1); } else if (NM_IN_SET(tdata->f->test_idx, 3)) { g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY); g_assert( IN6_ARE_ADDR_EQUAL(lladdr, &NMP_OBJECT_CAST_IP6_ADDRESS(tdata->lladdr0)->address)); tdata->steps_done = TRUE; } else g_assert_not_reached(); break; case 1: if (NM_IN_SET(tdata->f->test_idx, 1, 2)) { g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY); g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST1); a1 = _test_l3_ipv6ll_find_inet6(tdata, lladdr); g_assert(a1); g_assert(!NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE)); tdata->steps_done = TRUE; } else if (NM_IN_SET(tdata->f->test_idx, 4)) { g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS); g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST2); } else g_assert_not_reached(); break; case 2: if (NM_IN_SET(tdata->f->test_idx, 4)) { g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY); g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST2); a1 = _test_l3_ipv6ll_find_inet6(tdata, lladdr); g_assert(a1); g_assert(!NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE)); tdata->steps_done = TRUE; } else g_assert_not_reached(); break; default: g_assert_not_reached(); } } static void test_l3_ipv6ll(gconstpointer test_data) { NMTST_UTILS_HOST_ID_CONTEXT("l3-ipv6ll"); const int TEST_IDX = GPOINTER_TO_INT(test_data); nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {}; gs_unref_object NML3Cfg *l3cfg0 = NULL; TestL3IPv6LLData tdata_stack = { .step = 0, .steps_done = FALSE, }; TestL3IPv6LLData *const tdata = &tdata_stack; char sbuf1[NM_UTILS_TO_STRING_BUFFER_SIZE]; int r; _LOGD("test start (/l3-ipv6ll/%d)", TEST_IDX); if (nmtst_test_quick()) { gs_free char *msg = g_strdup_printf("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", g_get_prgname() ?: "test-l3-ipv6ll"); g_test_skip(msg); return; } tdata->f = _test_fixture_1_setup(&test_fixture, TEST_IDX); if (NM_IN_SET(tdata->f->test_idx, 4)) { _LOGD("add conflicting IPv6LL on other interface..."); r = nm_platform_link_change_flags(tdata->f->platform, tdata->f->ifindex1, IFF_UP, FALSE); g_assert_cmpint(r, >=, 0); r = nm_platform_link_set_inet6_addr_gen_mode(tdata->f->platform, tdata->f->ifindex1, NM_IN6_ADDR_GEN_MODE_NONE); g_assert_cmpint(r, >=, 0); r = nm_platform_link_change_flags(tdata->f->platform, tdata->f->ifindex1, IFF_UP, TRUE); g_assert_cmpint(r, >=, 0); nmtstp_ip6_address_add(tdata->f->platform, -1, tdata->f->ifindex1, *nmtst_inet6_from_string(_LLADDR_TEST1), 64, in6addr_any, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0); _LOGD("wait for IPv6 LL address..."); tdata->lladdr0 = nmp_object_ref(_test_l3_ipv6ll_find_lladdr_wait(tdata, tdata->f->ifindex1)); } else if (NM_IN_SET(tdata->f->test_idx, 2, 3)) { _LOGD("wait for IPv6 LL address..."); tdata->lladdr0 = nmp_object_ref(_test_l3_ipv6ll_find_lladdr_wait(tdata, tdata->f->ifindex0)); } if (tdata->lladdr0) { _LOGD("got IPv6 LL address %s", nmp_object_to_string(tdata->lladdr0, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof(sbuf1))); } l3cfg0 = _netns_access_l3cfg(tdata->f->netns, tdata->f->ifindex0); tdata->l3cfg0 = l3cfg0; g_signal_connect(tdata->l3cfg0, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_test_l3_ipv6ll_signal_notify), tdata); tdata->l3ipv6ll = nm_l3_ipv6ll_new_stable_privacy(tdata->l3cfg0, NM_IN_SET(tdata->f->test_idx, 3), NM_UTILS_STABLE_TYPE_UUID, tdata->f->ifname0, "b6a5b934-c649-43dc-a524-3dfdb74f9419", 0, _test_l3_ipv6ll_callback_changed, tdata); g_assert(nm_l3_ipv6ll_get_l3cfg(tdata->l3ipv6ll) == tdata->l3cfg0); g_assert_cmpint(nm_l3_ipv6ll_get_ifindex(tdata->l3ipv6ll), ==, tdata->f->ifindex0); tdata->step = 1; nmtst_main_context_iterate_until_assert(NULL, 7000, tdata->steps_done); g_assert_cmpint(tdata->step, ==, 1); if (NM_IN_SET(tdata->f->test_idx, 3)) g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 1); else if (NM_IN_SET(tdata->f->test_idx, 4)) g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 3); else g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 2); g_assert(tdata->steps_done); tdata->step = 2; nmtst_main_context_iterate_until(NULL, nmtst_get_rand_uint32() % 1000, FALSE); g_assert_cmpint(tdata->step, ==, 2); if (NM_IN_SET(tdata->f->test_idx, 3)) g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 1); else if (NM_IN_SET(tdata->f->test_idx, 4)) g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 3); else g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 2); g_assert(tdata->steps_done); g_assert(tdata->steps_done); tdata->step = 0; tdata->steps_done = FALSE; g_signal_handlers_disconnect_by_func(tdata->l3cfg0, G_CALLBACK(_test_l3_ipv6ll_signal_notify), tdata); nm_l3_ipv6ll_destroy(tdata->l3ipv6ll); nm_clear_nmp_object(&tdata->lladdr0); } /*****************************************************************************/ NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup; void _nmtstp_init_tests(int *argc, char ***argv) { nmtst_init_with_logging(argc, argv, "ERR", "ALL"); } void _nmtstp_setup_tests(void) { g_test_add_data_func("/l3cfg/1", GINT_TO_POINTER(1), test_l3cfg); 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); g_test_add_data_func("/l3-ipv6ll/1", GINT_TO_POINTER(1), test_l3_ipv6ll); g_test_add_data_func("/l3-ipv6ll/2", GINT_TO_POINTER(2), test_l3_ipv6ll); g_test_add_data_func("/l3-ipv6ll/3", GINT_TO_POINTER(3), test_l3_ipv6ll); g_test_add_data_func("/l3-ipv6ll/4", GINT_TO_POINTER(4), test_l3_ipv6ll); }