diff options
Diffstat (limited to 'src/core/platform/tests')
| -rw-r--r-- | src/core/platform/tests/meson.build | 43 | ||||
| -rw-r--r-- | src/core/platform/tests/monitor.c | 78 | ||||
| -rw-r--r-- | src/core/platform/tests/test-address.c | 465 | ||||
| -rw-r--r-- | src/core/platform/tests/test-cleanup.c | 188 | ||||
| -rw-r--r-- | src/core/platform/tests/test-common.c | 2731 | ||||
| -rw-r--r-- | src/core/platform/tests/test-common.h | 619 | ||||
| -rw-r--r-- | src/core/platform/tests/test-link.c | 3823 | ||||
| -rw-r--r-- | src/core/platform/tests/test-nmp-object.c | 617 | ||||
| -rw-r--r-- | src/core/platform/tests/test-platform-general.c | 772 | ||||
| -rw-r--r-- | src/core/platform/tests/test-route.c | 1926 | ||||
| -rw-r--r-- | src/core/platform/tests/test-tc.c | 222 |
11 files changed, 11484 insertions, 0 deletions
diff --git a/src/core/platform/tests/meson.build b/src/core/platform/tests/meson.build new file mode 100644 index 00000000..5d55707a --- /dev/null +++ b/src/core/platform/tests/meson.build @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +test_fake_c_flags = test_c_flags + ['-DSETUP=nm_fake_platform_setup'] +test_linux_c_flags = test_c_flags + ['-DSETUP=nm_linux_platform_setup'] + +test_units = [ + ['test-address-fake', 'test-address.c', test_fake_c_flags, default_test_timeout], + ['test-address-linux', 'test-address.c', test_linux_c_flags, default_test_timeout], + ['test-cleanup-fake', 'test-cleanup.c', test_fake_c_flags, default_test_timeout], + ['test-cleanup-linux', 'test-cleanup.c', test_linux_c_flags, default_test_timeout], + ['test-link-fake', 'test-link.c', test_fake_c_flags, default_test_timeout], + ['test-link-linux', 'test-link.c', test_linux_c_flags, 900], + ['test-nmp-object', 'test-nmp-object.c', test_c_flags, default_test_timeout], + ['test-platform-general', 'test-platform-general.c', test_c_flags, default_test_timeout], + ['test-route-fake', 'test-route.c', test_fake_c_flags, default_test_timeout], + ['test-route-linux', 'test-route.c', test_linux_c_flags, default_test_timeout], + ['test-tc-fake', 'test-tc.c', test_fake_c_flags, default_test_timeout], + ['test-tc-linux', 'test-tc.c', test_linux_c_flags, default_test_timeout], +] + +foreach test_unit: test_units + exe = executable( + test_unit[0], + test_unit[1], + dependencies: libNetworkManagerTest_dep, + c_args: test_unit[2], + ) + test( + 'platform/' + test_unit[0], + test_script, + timeout: test_unit[3], + args: test_args + [exe.full_path()], + ) +endforeach + +name = 'monitor' + +executable( + name, + name + '.c', + dependencies: libNetworkManagerTest_dep, + c_args: test_c_flags, +) diff --git a/src/core/platform/tests/monitor.c b/src/core/platform/tests/monitor.c new file mode 100644 index 00000000..abdf2cd5 --- /dev/null +++ b/src/core/platform/tests/monitor.c @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include <stdlib.h> +#include <syslog.h> + +#include "platform/nm-linux-platform.h" + +#include "nm-test-utils-core.h" + +NMTST_DEFINE(); + +static struct { + gboolean persist; +} global_opt = { + .persist = TRUE, +}; + +static gboolean +read_argv(int *argc, char ***argv) +{ + GOptionContext *context; + GOptionEntry options[] = { + {"no-persist", + 'P', + G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, + &global_opt.persist, + "Exit after processing netlink messages", + NULL}, + {0}, + }; + gs_free_error GError *error = NULL; + + context = g_option_context_new(NULL); + g_option_context_set_summary(context, "Monitor netlink events in NMPlatform."); + g_option_context_add_main_entries(context, options, NULL); + + if (!g_option_context_parse(context, argc, argv, &error)) { + g_warning("Error parsing command line arguments: %s", error->message); + g_option_context_free(context); + return FALSE; + } + + g_option_context_free(context); + return TRUE; +} + +int +main(int argc, char **argv) +{ + GMainLoop *loop; + + if (!g_getenv("G_MESSAGES_DEBUG")) + g_setenv("G_MESSAGES_DEBUG", "all", TRUE); + + nmtst_init_with_logging(&argc, &argv, "DEBUG", "ALL"); + + if (!read_argv(&argc, &argv)) + return 2; + + nm_log_info(LOGD_PLATFORM, "platform monitor start"); + + loop = g_main_loop_new(NULL, FALSE); + + nm_linux_platform_setup(); + + if (global_opt.persist) + g_main_loop_run(loop); + + g_main_loop_unref(loop); + + return EXIT_SUCCESS; +} diff --git a/src/core/platform/tests/test-address.c b/src/core/platform/tests/test-address.c new file mode 100644 index 00000000..d4d67105 --- /dev/null +++ b/src/core/platform/tests/test-address.c @@ -0,0 +1,465 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "test-common.h" + +#define IP4_ADDRESS "192.0.2.1" +#define IP4_ADDRESS_PEER "192.0.2.2" +#define IP4_ADDRESS_PEER2 "192.0.3.1" +#define IP4_PLEN 24 +#define IP6_ADDRESS "2001:db8:a:b:1:2:3:4" +#define IP6_PLEN 64 + +#define DEVICE_IFINDEX NMTSTP_ENV1_IFINDEX +#define EX NMTSTP_ENV1_EX + +/*****************************************************************************/ + +static void +ip4_address_callback(NMPlatform * platform, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP4Address * received, + NMPlatformSignalChangeType change_type, + SignalData * data) +{ + g_assert(received); + g_assert_cmpint(received->ifindex, ==, ifindex); + g_assert(data && data->name); + g_assert_cmpstr(data->name, ==, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + if (data->change_type != change_type) + return; + + if (data->loop) + g_main_loop_quit(data->loop); + + data->received_count++; + _LOGD("Received signal '%s' %dth time.", data->name, data->received_count); +} + +static void +ip6_address_callback(NMPlatform * platform, + NMPObjectType obj_type, + int ifindex, + NMPlatformIP6Address * received, + NMPlatformSignalChangeType change_type, + SignalData * data) +{ + g_assert(received); + g_assert_cmpint(received->ifindex, ==, ifindex); + g_assert(data && data->name); + g_assert_cmpstr(data->name, ==, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + if (data->change_type != change_type) + return; + + if (data->loop) + g_main_loop_quit(data->loop); + + data->received_count++; + _LOGD("Received signal '%s' %dth time.", data->name, data->received_count); +} + +/*****************************************************************************/ + +static void +test_ip4_address_general(void) +{ + const int ifindex = DEVICE_IFINDEX; + SignalData *address_added = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip4_address_callback, + ifindex); + SignalData *address_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + ip4_address_callback, + ifindex); + SignalData *address_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip4_address_callback, + ifindex); + GArray * addresses; + NMPlatformIP4Address *address; + in_addr_t addr; + guint32 lifetime = 2000; + guint32 preferred = 1000; + + inet_pton(AF_INET, IP4_ADDRESS, &addr); + + /* Add address */ + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + nmtstp_ip4_address_add(NULL, EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, 0, NULL); + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + accept_signal(address_added); + + /* Add address again (aka update) */ + nmtstp_ip4_address_add(NULL, + EX, + ifindex, + addr, + IP4_PLEN, + addr, + lifetime + 100, + preferred + 50, + 0, + NULL); + accept_signals(address_changed, 0, 1); + + /* Test address listing */ + addresses = nmtstp_platform_ip4_address_get_all(NM_PLATFORM_GET, ifindex); + g_assert(addresses); + g_assert_cmpint(addresses->len, ==, 1); + address = &g_array_index(addresses, NMPlatformIP4Address, 0); + g_assert_cmpint(address->ifindex, ==, ifindex); + g_assert_cmphex(address->address, ==, addr); + g_assert_cmphex(address->peer_address, ==, addr); + g_assert_cmpint(address->plen, ==, IP4_PLEN); + g_array_unref(addresses); + + /* Remove address */ + nmtstp_ip4_address_del(NULL, EX, ifindex, addr, IP4_PLEN, addr); + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + accept_signal(address_removed); + + /* Remove address again */ + nmtstp_ip4_address_del(NULL, EX, ifindex, addr, IP4_PLEN, addr); + + free_signal(address_added); + free_signal(address_changed); + free_signal(address_removed); +} + +static void +test_ip6_address_general(void) +{ + const int ifindex = DEVICE_IFINDEX; + SignalData *address_added = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip6_address_callback, + ifindex); + SignalData *address_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + ip6_address_callback, + ifindex); + SignalData *address_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip6_address_callback, + ifindex); + GArray * addresses; + NMPlatformIP6Address *address; + struct in6_addr addr; + guint32 lifetime = 2000; + guint32 preferred = 1000; + guint flags = 0; + + inet_pton(AF_INET6, IP6_ADDRESS, &addr); + + /* Add address */ + g_assert(!nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + nmtstp_ip6_address_add(NULL, + EX, + ifindex, + addr, + IP6_PLEN, + in6addr_any, + lifetime, + preferred, + flags); + g_assert(nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + accept_signal(address_added); + + /* Add address again (aka update) */ + nmtstp_ip6_address_add(NULL, + EX, + ifindex, + addr, + IP6_PLEN, + in6addr_any, + lifetime, + preferred, + flags); + accept_signals(address_changed, 0, 2); + + /* Test address listing */ + addresses = nmtstp_platform_ip6_address_get_all(NM_PLATFORM_GET, ifindex); + g_assert(addresses); + g_assert_cmpint(addresses->len, ==, 1); + address = &g_array_index(addresses, NMPlatformIP6Address, 0); + g_assert_cmpint(address->ifindex, ==, ifindex); + g_assert(!memcmp(&address->address, &addr, sizeof(addr))); + g_assert_cmpint(address->plen, ==, IP6_PLEN); + g_array_unref(addresses); + + /* Remove address */ + nmtstp_ip6_address_del(NULL, EX, ifindex, addr, IP6_PLEN); + g_assert(!nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + accept_signal(address_removed); + + /* Remove address again */ + nmtstp_ip6_address_del(NULL, EX, ifindex, addr, IP6_PLEN); + + /* ensure not pending signal. */ + accept_signals(address_changed, 0, 1); + + free_signal(address_added); + free_signal(address_changed); + free_signal(address_removed); +} + +static void +test_ip4_address_general_2(void) +{ + const int ifindex = DEVICE_IFINDEX; + SignalData *address_added = add_signal(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip4_address_callback); + SignalData *address_removed = add_signal(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip4_address_callback); + in_addr_t addr; + guint32 lifetime = 2000; + guint32 preferred = 1000; + + inet_pton(AF_INET, IP4_ADDRESS, &addr); + g_assert(ifindex > 0); + + /* Looks like addresses are not announced by kernel when the interface + * is down. Link-local IPv6 address is automatically added. + */ + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, DEVICE_IFINDEX, NULL)); + + /* Add/delete notification */ + nmtstp_ip4_address_add(NULL, EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, 0, NULL); + accept_signal(address_added); + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + nmtstp_ip4_address_del(NULL, EX, ifindex, addr, IP4_PLEN, addr); + accept_signal(address_removed); + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + + /* Add/delete conflict */ + nmtstp_ip4_address_add(NULL, EX, ifindex, addr, IP4_PLEN, addr, lifetime, preferred, 0, NULL); + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr)); + accept_signal(address_added); + + free_signal(address_added); + free_signal(address_removed); +} + +static void +test_ip6_address_general_2(void) +{ + const int ifindex = DEVICE_IFINDEX; + SignalData * address_added = add_signal(NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip6_address_callback); + SignalData * address_removed = add_signal(NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip6_address_callback); + struct in6_addr addr; + guint32 lifetime = 2000; + guint32 preferred = 1000; + guint flags = 0; + + inet_pton(AF_INET6, IP6_ADDRESS, &addr); + + /* Add/delete notification */ + nmtstp_ip6_address_add(NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0); + accept_signal(address_added); + g_assert(nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + + nmtstp_ip6_address_del(NULL, EX, ifindex, addr, IP6_PLEN); + accept_signal(address_removed); + g_assert(!nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + + /* Add/delete conflict */ + nmtstp_ip6_address_add(NULL, EX, ifindex, addr, IP6_PLEN, in6addr_any, lifetime, preferred, 0); + accept_signal(address_added); + g_assert(nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + + nmtstp_ip6_address_add(NULL, + EX, + ifindex, + addr, + IP6_PLEN, + in6addr_any, + lifetime, + preferred, + flags); + ensure_no_signal(address_added); + g_assert(nm_platform_ip6_address_get(NM_PLATFORM_GET, ifindex, &addr)); + + free_signal(address_added); + free_signal(address_removed); +} + +/*****************************************************************************/ + +static void +test_ip4_address_peer(void) +{ + const int ifindex = DEVICE_IFINDEX; + SignalData * address_added = add_signal(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip4_address_callback); + SignalData * address_removed = add_signal(NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip4_address_callback); + in_addr_t addr, addr_peer, addr_peer2; + guint32 lifetime = 2000; + guint32 preferred = 1000; + const NMPlatformIP4Address *a; + + inet_pton(AF_INET, IP4_ADDRESS, &addr); + inet_pton(AF_INET, IP4_ADDRESS_PEER, &addr_peer); + inet_pton(AF_INET, IP4_ADDRESS_PEER2, &addr_peer2); + g_assert(ifindex > 0); + + g_assert(addr != addr_peer); + + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL)); + accept_signals(address_removed, 0, G_MAXINT); + accept_signals(address_added, 0, G_MAXINT); + + /* Add/delete notification */ + nmtstp_ip4_address_add(NULL, + EX, + ifindex, + addr, + IP4_PLEN, + addr_peer, + lifetime, + preferred, + 0, + NULL); + accept_signal(address_added); + a = nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer); + g_assert(a); + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2)); + + nmtstp_ip_address_assert_lifetime((NMPlatformIPAddress *) a, -1, lifetime, preferred); + + nmtstp_ip4_address_add(NULL, + EX, + ifindex, + addr, + IP4_PLEN, + addr_peer2, + lifetime, + preferred, + 0, + NULL); + accept_signal(address_added); + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer)); + a = nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2); + g_assert(a); + + nmtstp_ip_address_assert_lifetime((NMPlatformIPAddress *) a, -1, lifetime, preferred); + + g_assert(addr != addr_peer); + nmtstp_ip4_address_del(NULL, EX, ifindex, addr, IP4_PLEN, addr_peer); + accept_signal(address_removed); + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer)); + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, IP4_PLEN, addr_peer2)); + + free_signal(address_added); + free_signal(address_removed); +} + +/*****************************************************************************/ + +static void +test_ip4_address_peer_zero(void) +{ + const int ifindex = DEVICE_IFINDEX; + in_addr_t addr, addr_peer; + guint32 lifetime = 2000; + guint32 preferred = 1000; + const gint8 plen = 24; + const char *label = NULL; + in_addr_t peers[3], r_peers[3]; + int i; + GArray * addrs; + + g_assert(ifindex > 0); + + inet_pton(AF_INET, "192.168.5.2", &addr); + inet_pton(AF_INET, "192.168.6.2", &addr_peer); + peers[0] = addr; + peers[1] = addr_peer; + peers[2] = 0; + + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL)); + + nmtst_rand_perm(NULL, r_peers, peers, sizeof(peers[0]), G_N_ELEMENTS(peers)); + for (i = 0; i < G_N_ELEMENTS(peers); i++) { + g_assert(!nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, plen, r_peers[i])); + + nmtstp_ip4_address_add(NULL, + EX, + ifindex, + addr, + plen, + r_peers[i], + lifetime, + preferred, + 0, + label); + + addrs = nmtstp_platform_ip4_address_get_all(NM_PLATFORM_GET, ifindex); + g_assert(addrs); + g_assert_cmpint(addrs->len, ==, i + 1); + g_array_unref(addrs); + } + + if (nmtst_is_debug() && nmtstp_is_root_test()) + nmtstp_run_command_check("ip address show dev %s", DEVICE_NAME); + + nmtst_rand_perm(NULL, r_peers, peers, sizeof(peers[0]), G_N_ELEMENTS(peers)); + for (i = 0; i < G_N_ELEMENTS(peers); i++) { + g_assert(nm_platform_ip4_address_get(NM_PLATFORM_GET, ifindex, addr, plen, r_peers[i])); + + nmtstp_ip4_address_del(NULL, EX, ifindex, addr, plen, r_peers[i]); + + addrs = nmtstp_platform_ip4_address_get_all(NM_PLATFORM_GET, ifindex); + g_assert(addrs); + g_assert_cmpint(addrs->len, ==, G_N_ELEMENTS(peers) - i - 1); + g_array_unref(addrs); + } +} + +/*****************************************************************************/ + +NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +/***************************************************************************** + * SETUP TESTS + *****************************************************************************/ + +void +_nmtstp_setup_tests(void) +{ +#define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, FALSE) + add_test_func("/address/ipv4/general", test_ip4_address_general); + add_test_func("/address/ipv6/general", test_ip6_address_general); + + add_test_func("/address/ipv4/general-2", test_ip4_address_general_2); + add_test_func("/address/ipv6/general-2", test_ip6_address_general_2); + + add_test_func("/address/ipv4/peer", test_ip4_address_peer); + add_test_func("/address/ipv4/peer/zero", test_ip4_address_peer_zero); +} diff --git a/src/core/platform/tests/test-cleanup.c b/src/core/platform/tests/test-cleanup.c new file mode 100644 index 00000000..b54eca34 --- /dev/null +++ b/src/core/platform/tests/test-cleanup.c @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "test-common.h" + +static void +test_cleanup_internal(void) +{ + SignalData * link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + DEVICE_NAME); + int ifindex; + GArray * addresses4; + GArray * addresses6; + GPtrArray * routes4; + GPtrArray * routes6; + in_addr_t addr4; + in_addr_t network4; + int plen4 = 24; + in_addr_t gateway4; + struct in6_addr addr6; + struct in6_addr network6; + int plen6 = 64; + struct in6_addr gateway6; + int lifetime = NM_PLATFORM_LIFETIME_PERMANENT; + int preferred = NM_PLATFORM_LIFETIME_PERMANENT; + int metric = 20; + int mss = 1000; + guint32 flags = 0; + + inet_pton(AF_INET, "192.0.2.1", &addr4); + inet_pton(AF_INET, "192.0.3.0", &network4); + inet_pton(AF_INET, "198.51.100.1", &gateway4); + inet_pton(AF_INET6, "2001:db8:a:b:1:2:3:4", &addr6); + inet_pton(AF_INET6, "2001:db8:c:d:0:0:0:0", &network6); + inet_pton(AF_INET6, "2001:db8:e:f:1:2:3:4", &gateway6); + + /* Create and set up device */ + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(NM_PLATFORM_GET, DEVICE_NAME, NULL))); + accept_signal(link_added); + free_signal(link_added); + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, + nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME), + NULL)); + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert(ifindex > 0); + + /* wait for kernel to add the IPv6 link local address... it takes a bit. */ + NMTST_WAIT_ASSERT(300, { + gs_unref_array GArray * addrs = NULL; + const NMPlatformIP6Address *a; + + if (nmtst_wait_iteration > 0) { + nmtstp_wait_for_signal(NM_PLATFORM_GET, nmtst_wait_remaining_us / 1000); + nm_platform_process_events(NM_PLATFORM_GET); + } + addrs = nmtstp_platform_ip6_address_get_all(NM_PLATFORM_GET, ifindex); + if (addrs->len == 1 && (a = &g_array_index(addrs, NMPlatformIP6Address, 0)) + && IN6_IS_ADDR_LINKLOCAL(&a->address)) + break; + }); + + g_assert(nm_platform_ip4_address_add(NM_PLATFORM_GET, + ifindex, + addr4, + plen4, + addr4, + nm_platform_ip4_broadcast_address_create(addr4, plen4), + lifetime, + preferred, + 0, + NULL)); + g_assert(nm_platform_ip6_address_add(NM_PLATFORM_GET, + ifindex, + addr6, + plen6, + in6addr_any, + lifetime, + preferred, + flags)); + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + gateway4, + 32, + INADDR_ANY, + 0, + metric, + mss); + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network4, + plen4, + gateway4, + 0, + metric, + mss); + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + gateway4, + 0, + metric, + mss); + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + gateway6, + 128, + in6addr_any, + in6addr_any, + metric, + mss); + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network6, + plen6, + gateway6, + in6addr_any, + metric, + mss); + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + in6addr_any, + 0, + gateway6, + in6addr_any, + metric, + mss); + + addresses4 = nmtstp_platform_ip4_address_get_all(NM_PLATFORM_GET, ifindex); + addresses6 = nmtstp_platform_ip6_address_get_all(NM_PLATFORM_GET, ifindex); + routes4 = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, ifindex); + routes6 = nmtstp_ip6_route_get_all(NM_PLATFORM_GET, ifindex); + + g_assert_cmpint(addresses4->len, ==, 1); + g_assert_cmpint(addresses6->len, ==, 2); /* also has a IPv6 LL address. */ + g_assert_cmpint(routes4->len, ==, 3); + g_assert_cmpint(routes6->len, ==, 3); + + g_array_unref(addresses4); + g_array_unref(addresses6); + g_ptr_array_unref(routes4); + g_ptr_array_unref(routes6); + + /* Delete interface with all addresses and routes */ + g_assert(nm_platform_link_delete(NM_PLATFORM_GET, ifindex)); + + addresses4 = nmtstp_platform_ip4_address_get_all(NM_PLATFORM_GET, ifindex); + addresses6 = nmtstp_platform_ip6_address_get_all(NM_PLATFORM_GET, ifindex); + routes4 = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, ifindex); + routes6 = nmtstp_ip6_route_get_all(NM_PLATFORM_GET, ifindex); + + g_assert_cmpint(addresses4->len, ==, 0); + g_assert_cmpint(addresses6->len, ==, 0); + g_assert(!routes4); + g_assert(!routes6); + + g_array_unref(addresses4); + g_array_unref(addresses6); +} + +NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE); + + g_test_add_func("/internal", test_cleanup_internal); + /* FIXME: add external cleanup check */ +} diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c new file mode 100644 index 00000000..87e5329a --- /dev/null +++ b/src/core/platform/tests/test-common.c @@ -0,0 +1,2731 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 - 2017 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include "test-common.h" + +#include <sys/mount.h> +#include <sched.h> +#include <sys/wait.h> +#include <fcntl.h> +#include <linux/if_tun.h> + +#include "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) \ + (data)->name, nm_platform_signal_change_type_to_string((data)->change_type), (data)->ifindex, \ + (data)->ifname ? " ifname '" : "", (data)->ifname ?: "", (data)->ifname ? "'" : "", \ + (data)->received_count + +int NMTSTP_ENV1_IFINDEX = -1; +int NMTSTP_ENV1_EX = -1; + +/*****************************************************************************/ + +void +nmtstp_setup_platform(void) +{ + g_assert(_nmtstp_setup_platform_func); + _nmtstp_setup_platform_func(); +} + +gboolean +nmtstp_is_root_test(void) +{ + g_assert(_nmtstp_setup_platform_func); + return _nmtstp_setup_platform_func == nm_linux_platform_setup; +} + +gboolean +nmtstp_is_sysfs_writable(void) +{ + return !nmtstp_is_root_test() || (access("/sys/devices", W_OK) == 0); +} + +static void +_init_platform(NMPlatform **platform, gboolean external_command) +{ + g_assert(platform); + if (!*platform) + *platform = NM_PLATFORM_GET; + g_assert(NM_IS_PLATFORM(*platform)); + + if (external_command) + g_assert(NM_IS_LINUX_PLATFORM(*platform)); +} + +/*****************************************************************************/ + +static GArray * +_ipx_address_get_all(NMPlatform *self, int ifindex, NMPObjectType obj_type) +{ + NMPLookup lookup; + + g_assert(NM_IS_PLATFORM(self)); + g_assert(ifindex > 0); + g_assert(NM_IN_SET(obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)); + nmp_lookup_init_object(&lookup, obj_type, ifindex); + return nmp_cache_lookup_to_array(nm_platform_lookup(self, &lookup), + obj_type, + FALSE /*addresses are always visible. */); +} + +GArray * +nmtstp_platform_ip4_address_get_all(NMPlatform *self, int ifindex) +{ + return _ipx_address_get_all(self, ifindex, NMP_OBJECT_TYPE_IP4_ADDRESS); +} + +GArray * +nmtstp_platform_ip6_address_get_all(NMPlatform *self, int ifindex) +{ + return _ipx_address_get_all(self, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS); +} + +const NMPlatformIPAddress * +nmtstp_platform_ip_address_find(NMPlatform *self, int ifindex, int addr_family, gconstpointer addr) +{ + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMPlatformIPAddress *found = NULL; + NMDedupMultiIter iter; + const NMPObject * obj; + NMPLookup lookup; + + g_assert(NM_IS_PLATFORM(self)); + nm_assert(ifindex >= 0); + nm_assert_addr_family(addr_family); + nm_assert(addr); + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex); + nm_platform_iter_obj_for_each (&iter, self, &lookup, &obj) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(obj); + + g_assert(NMP_OBJECT_GET_ADDR_FAMILY(obj) == addr_family); + g_assert(ifindex <= 0 || a->ifindex == ifindex); + + if (memcmp(addr, a->address_ptr, nm_utils_addr_family_to_size(addr_family)) != 0) + continue; + + g_assert(!found); + found = a; + } + + if (!IS_IPv4 && ifindex > 0) + g_assert(found + == (const NMPlatformIPAddress *) nm_platform_ip6_address_get(self, ifindex, addr)); + + return found; +} + +/*****************************************************************************/ + +typedef struct { + NMIPAddr addr; + int addr_family; + bool found : 1; +} IPAddressesAssertData; + +void +_nmtstp_platform_ip_addresses_assert(const char * filename, + int lineno, + NMPlatform * self, + int ifindex, + gboolean force_exact_4, + gboolean force_exact_6, + gboolean ignore_ll6, + guint addrs_len, + const char *const *addrs) +{ + gs_free IPAddressesAssertData *addrs_bin = NULL; + int IS_IPv4; + guint i; + + g_assert(filename); + g_assert(lineno >= 0); + g_assert(NM_IS_PLATFORM(self)); + g_assert(ifindex >= 0); + + addrs_bin = g_new(IPAddressesAssertData, addrs_len); + + for (i = 0; i < addrs_len; i++) { + const char *addrstr = addrs[i]; + int addr_family; + NMIPAddr a; + + if (!addrstr) { + addr_family = AF_UNSPEC; + a = nm_ip_addr_zero; + } else if (inet_pton(AF_INET, addrstr, &a) == 1) + addr_family = AF_INET; + else if (inet_pton(AF_INET6, addrstr, &a) == 1) + addr_family = AF_INET6; + else + g_error("%s:%d: invalid IP address in argument: %s", filename, lineno, addrstr); + + addrs_bin[i] = (IPAddressesAssertData){ + .addr_family = addr_family, + .addr = a, + .found = FALSE, + }; + } + + for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { + const int addr_family = IS_IPv4 ? AF_INET : AF_INET6; + gs_unref_ptrarray GPtrArray *plat_addrs = NULL; + NMPLookup lookup; + guint j; + + plat_addrs = nm_platform_lookup_clone( + self, + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex), + NULL, + NULL); + + for (i = 0; i < addrs_len; i++) { + IPAddressesAssertData *addr_bin = &addrs_bin[i]; + + if (addr_bin->addr_family != addr_family) + continue; + + g_assert(!addr_bin->found); + for (j = 0; j < nm_g_ptr_array_len(plat_addrs);) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(plat_addrs->pdata[j]); + + if (memcmp(&addr_bin->addr, + a->address_ptr, + nm_utils_addr_family_to_size(addr_family)) + != 0) { + j++; + continue; + } + + g_assert(!addr_bin->found); + addr_bin->found = TRUE; + g_ptr_array_remove_index_fast(plat_addrs, j); + } + + if (!addr_bin->found) { + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + g_error("%s:%d: IPv%c address %s was not found on ifindex %d", + filename, + lineno, + nm_utils_addr_family_to_char(addr_bin->addr_family), + nm_utils_inet_ntop(addr_bin->addr_family, &addr_bin->addr, sbuf), + ifindex); + } + } + if (!IS_IPv4 && ignore_ll6 && nm_g_ptr_array_len(plat_addrs) > 0) { + /* we prune all remaining, non-matching IPv6 link local addresses. */ + for (j = 0; j < nm_g_ptr_array_len(plat_addrs);) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(plat_addrs->pdata[j]); + + if (!IN6_IS_ADDR_LINKLOCAL(a->address_ptr)) { + j++; + continue; + } + + g_ptr_array_remove_index_fast(plat_addrs, j); + } + } + if ((IS_IPv4 ? force_exact_4 : force_exact_6) && nm_g_ptr_array_len(plat_addrs) > 0) { + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + + g_error("%s:%d: %u IPv%c addresses found on ifindex %d that should not be there (one " + "is %s)", + filename, + lineno, + plat_addrs->len, + nm_utils_addr_family_to_char(addr_family), + ifindex, + nmp_object_to_string(plat_addrs->pdata[0], + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf, + sizeof(sbuf))); + } + } +} + +/*****************************************************************************/ + +gboolean +nmtstp_platform_ip4_route_delete(NMPlatform *platform, + int ifindex, + in_addr_t network, + guint8 plen, + guint32 metric) +{ + NMDedupMultiIter iter; + + nm_platform_process_events(platform); + + nm_dedup_multi_iter_for_each ( + &iter, + nm_platform_lookup_object(platform, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex)) { + const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE(iter.current->obj); + + if (r->ifindex != ifindex || r->network != network || r->plen != plen + || r->metric != metric) { + continue; + } + + return nm_platform_object_delete(platform, NMP_OBJECT_UP_CAST(r)); + } + + return TRUE; +} + +gboolean +nmtstp_platform_ip6_route_delete(NMPlatform * platform, + int ifindex, + struct in6_addr network, + guint8 plen, + guint32 metric) +{ + NMDedupMultiIter iter; + + nm_platform_process_events(platform); + + nm_dedup_multi_iter_for_each ( + &iter, + nm_platform_lookup_object(platform, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex)) { + const NMPlatformIP6Route *r = NMP_OBJECT_CAST_IP6_ROUTE(iter.current->obj); + + if (r->ifindex != ifindex || !IN6_ARE_ADDR_EQUAL(&r->network, &network) || r->plen != plen + || r->metric != metric) { + continue; + } + + return nm_platform_object_delete(platform, NMP_OBJECT_UP_CAST(r)); + } + + return TRUE; +} + +/*****************************************************************************/ + +SignalData * +add_signal_full(const char * name, + NMPlatformSignalChangeType change_type, + GCallback callback, + int ifindex, + const char * ifname) +{ + SignalData *data = g_new0(SignalData, 1); + + data->name = name; + data->change_type = change_type; + data->received_count = 0; + data->handler_id = g_signal_connect(NM_PLATFORM_GET, name, callback, data); + data->ifindex = ifindex; + data->ifname = ifname; + + g_assert(data->handler_id > 0); + + return data; +} + +void +_accept_signal(const char *file, int line, const char *func, SignalData *data) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal one time: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + if (data->received_count != 1) + g_error("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal one " + "time: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + data->received_count = 0; +} + +void +_accept_signals(const char *file, int line, const char *func, SignalData *data, int min, int max) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal [%d,%d] times: " SIGNAL_DATA_FMT, + file, + line, + func, + min, + max, + SIGNAL_DATA_ARG(data)); + if (data->received_count < min || data->received_count > max) + g_error("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal [%d,%d] " + "times: " SIGNAL_DATA_FMT, + file, + line, + func, + min, + max, + SIGNAL_DATA_ARG(data)); + data->received_count = 0; +} + +void +_ensure_no_signal(const char *file, int line, const char *func, SignalData *data) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal 0 times: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + if (data->received_count > 0) + g_error("NMPlatformSignalAssert: %s:%d, %s(): failure to accept signal 0 " + "times: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); +} + +void +_accept_or_wait_signal(const char *file, int line, const char *func, SignalData *data) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): accept-or-wait signal: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + if (data->received_count == 0) { + data->loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(data->loop); + nm_clear_pointer(&data->loop, g_main_loop_unref); + } + + _accept_signal(file, line, func, data); +} + +void +_wait_signal(const char *file, int line, const char *func, SignalData *data) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): wait signal: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + if (data->received_count) + g_error("NMPlatformSignalAssert: %s:%d, %s(): failure to wait for signal: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + + data->loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(data->loop); + nm_clear_pointer(&data->loop, g_main_loop_unref); + + _accept_signal(file, line, func, data); +} + +void +_free_signal(const char *file, int line, const char *func, SignalData *data) +{ + _LOGD("NMPlatformSignalAssert: %s:%d, %s(): free signal: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + if (data->received_count != 0) + g_error("NMPlatformSignalAssert: %s:%d, %s(): failure to free non-accepted " + "signal: " SIGNAL_DATA_FMT, + file, + line, + func, + SIGNAL_DATA_ARG(data)); + + g_signal_handler_disconnect(NM_PLATFORM_GET, data->handler_id); + g_free(data); +} + +void +link_callback(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *received, + int change_type_i, + SignalData * data) +{ + const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; + NMPLookup lookup; + NMDedupMultiIter iter; + const NMPlatformLink * cached; + + g_assert_cmpint(obj_type, ==, NMP_OBJECT_TYPE_LINK); + g_assert(received); + g_assert_cmpint(received->ifindex, ==, ifindex); + g_assert(data && data->name); + g_assert_cmpstr(data->name, ==, NM_PLATFORM_SIGNAL_LINK_CHANGED); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + if (data->ifname + && g_strcmp0(data->ifname, nm_platform_link_get_name(NM_PLATFORM_GET, ifindex)) != 0) + return; + if (change_type != data->change_type) + return; + + if (data->loop) { + _LOGD("Quitting main loop."); + g_main_loop_quit(data->loop); + } + + data->received_count++; + _LOGD("Received signal '%s-%s' ifindex %d ifname '%s' %dth time.", + data->name, + nm_platform_signal_change_type_to_string(data->change_type), + ifindex, + received->name, + data->received_count); + + if (change_type == NM_PLATFORM_SIGNAL_REMOVED) + g_assert(!nm_platform_link_get_name(NM_PLATFORM_GET, ifindex)); + else + g_assert(nm_platform_link_get_name(NM_PLATFORM_GET, ifindex)); + + /* Check the data */ + g_assert(received->ifindex > 0); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); + nmp_cache_iter_for_each_link (&iter, nm_platform_lookup(platform, &lookup), &cached) { + if (!nmp_object_is_visible(NMP_OBJECT_UP_CAST(cached))) + continue; + if (cached->ifindex == received->ifindex) { + g_assert_cmpint(nm_platform_link_cmp(cached, received), ==, 0); + g_assert(!memcmp(cached, received, sizeof(*cached))); + if (data->change_type == NM_PLATFORM_SIGNAL_REMOVED) + g_error("Deleted link still found in the local cache."); + return; + } + } + + if (data->change_type != NM_PLATFORM_SIGNAL_REMOVED) + g_error("Added/changed link not found in the local cache."); +} + +/*****************************************************************************/ + +static const NMPlatformIP4Route * +_ip4_route_get(NMPlatform *platform, + int ifindex, + guint32 network, + int plen, + guint32 metric, + guint8 tos, + guint * out_c_exists) +{ + NMDedupMultiIter iter; + NMPLookup lookup; + const NMPObject * o = NULL; + guint c; + const NMPlatformIP4Route *r = NULL; + + _init_platform(&platform, FALSE); + + nmp_lookup_init_ip4_route_by_weak_id(&lookup, network, plen, metric, tos); + + c = 0; + nmp_cache_iter_for_each (&iter, nm_platform_lookup(platform, &lookup), &o) { + if (NMP_OBJECT_CAST_IP4_ROUTE(o)->ifindex != ifindex && ifindex > 0) + continue; + if (!r) + r = NMP_OBJECT_CAST_IP4_ROUTE(o); + c++; + } + + NM_SET_OUT(out_c_exists, c); + return r; +} + +const NMPlatformIP4Route * +_nmtstp_assert_ip4_route_exists(const char *file, + guint line, + const char *func, + NMPlatform *platform, + int c_exists, + const char *ifname, + guint32 network, + int plen, + guint32 metric, + guint8 tos) +{ + int ifindex; + guint c; + const NMPlatformIP4Route *r = NULL; + + _init_platform(&platform, FALSE); + + ifindex = -1; + if (ifname) { + ifindex = nm_platform_link_get_ifindex(platform, ifname); + g_assert(ifindex > 0); + } + + r = _ip4_route_get(platform, ifindex, network, plen, metric, tos, &c); + + if (c != c_exists && c_exists != -1) { + char sbuf[NM_UTILS_INET_ADDRSTRLEN]; + + g_error("[%s:%u] %s(): The ip4 route %s/%d metric %u tos %u shall exist %u times, but " + "platform has it %u times", + file, + line, + func, + _nm_utils_inet4_ntop(network, sbuf), + plen, + metric, + tos, + c_exists, + c); + } + + return r; +} + +const NMPlatformIP4Route * +nmtstp_ip4_route_get(NMPlatform *platform, + int ifindex, + guint32 network, + int plen, + guint32 metric, + guint8 tos) +{ + return _ip4_route_get(platform, ifindex, network, plen, metric, tos, NULL); +} + +/*****************************************************************************/ + +static const NMPlatformIP6Route * +_ip6_route_get(NMPlatform * platform, + int ifindex, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen, + guint * out_c_exists) +{ + NMDedupMultiIter iter; + NMPLookup lookup; + const NMPObject * o = NULL; + guint c; + const NMPlatformIP6Route *r = NULL; + + _init_platform(&platform, FALSE); + + nmp_lookup_init_ip6_route_by_weak_id(&lookup, network, plen, metric, src, src_plen); + + c = 0; + nmp_cache_iter_for_each (&iter, nm_platform_lookup(platform, &lookup), &o) { + if (NMP_OBJECT_CAST_IP6_ROUTE(o)->ifindex != ifindex && ifindex > 0) + continue; + if (!r) + r = NMP_OBJECT_CAST_IP6_ROUTE(o); + c++; + } + + NM_SET_OUT(out_c_exists, c); + return r; +} + +const NMPlatformIP6Route * +_nmtstp_assert_ip6_route_exists(const char * file, + guint line, + const char * func, + NMPlatform * platform, + int c_exists, + const char * ifname, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen) +{ + int ifindex; + guint c; + const NMPlatformIP6Route *r = NULL; + + _init_platform(&platform, FALSE); + + ifindex = -1; + if (ifname) { + ifindex = nm_platform_link_get_ifindex(platform, ifname); + g_assert(ifindex > 0); + } + + r = _ip6_route_get(platform, ifindex, network, plen, metric, src, src_plen, &c); + + if (c != c_exists && c_exists != -1) { + char s_src[NM_UTILS_INET_ADDRSTRLEN]; + char s_network[NM_UTILS_INET_ADDRSTRLEN]; + + g_error("[%s:%u] %s(): The ip6 route %s/%d metric %u src %s/%d shall exist %u times, but " + "platform has it %u times", + file, + line, + func, + _nm_utils_inet6_ntop(network, s_network), + plen, + metric, + _nm_utils_inet6_ntop(src, s_src), + src_plen, + c_exists, + c); + } + + return r; +} + +const NMPlatformIP6Route * +nmtstp_ip6_route_get(NMPlatform * platform, + int ifindex, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen) +{ + return _ip6_route_get(platform, ifindex, network, plen, metric, src, src_plen, NULL); +} + +/*****************************************************************************/ + +int +nmtstp_run_command(const char *format, ...) +{ + int result; + gs_free char *command = NULL; + va_list ap; + + va_start(ap, format); + command = g_strdup_vprintf(format, ap); + va_end(ap); + + _LOGD("Running command: %s", command); + result = system(command); + _LOGD("Command finished: result=%d", result); + + return result; +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *loop; + guint signal_counts; + guint id; +} WaitForSignalData; + +static void +_wait_for_signal_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *plink, + int change_type_i, + gpointer user_data) +{ + WaitForSignalData *data = user_data; + + data->signal_counts++; + nm_clear_g_source(&data->id); + g_main_loop_quit(data->loop); +} + +static gboolean +_wait_for_signal_timeout(gpointer user_data) +{ + WaitForSignalData *data = user_data; + + g_assert(data->id); + data->id = 0; + g_main_loop_quit(data->loop); + return G_SOURCE_REMOVE; +} + +guint +nmtstp_wait_for_signal(NMPlatform *platform, gint64 timeout_msec) +{ + WaitForSignalData data = {0}; + gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route; + gulong id_qdisc, id_tfilter; + + _init_platform(&platform, FALSE); + + data.loop = g_main_loop_new(NULL, FALSE); + + id_link = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_ip4_address = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_ip6_address = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_ip4_route = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_ip6_route = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_qdisc = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_QDISC_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + id_tfilter = g_signal_connect(platform, + NM_PLATFORM_SIGNAL_TFILTER_CHANGED, + G_CALLBACK(_wait_for_signal_cb), + &data); + + /* if timeout_msec is negative, it means the wait-time already expired. + * Maybe, we should do nothing and return right away, without even + * processing events from platform. However, that inconsistency (of not + * processing events from mainloop) is inconvenient. + * + * It's better that on the return of nmtstp_wait_for_signal(), we always + * have no events pending. So, a negative timeout is treated the same as + * a zero timeout: we check whether there are any events pending in platform, + * and quite the mainloop immediately afterwards. But we always check. */ + + data.id = g_timeout_add(CLAMP(timeout_msec, 0, G_MAXUINT32), _wait_for_signal_timeout, &data); + + g_main_loop_run(data.loop); + + g_assert(!data.id); + g_assert(nm_clear_g_signal_handler(platform, &id_link)); + g_assert(nm_clear_g_signal_handler(platform, &id_ip4_address)); + g_assert(nm_clear_g_signal_handler(platform, &id_ip6_address)); + g_assert(nm_clear_g_signal_handler(platform, &id_ip4_route)); + g_assert(nm_clear_g_signal_handler(platform, &id_ip6_route)); + g_assert(nm_clear_g_signal_handler(platform, &id_tfilter)); + g_assert(nm_clear_g_signal_handler(platform, &id_qdisc)); + + nm_clear_pointer(&data.loop, g_main_loop_unref); + + /* return the number of signals, or 0 if timeout was reached .*/ + return data.signal_counts; +} + +guint +nmtstp_wait_for_signal_until(NMPlatform *platform, gint64 until_ms) +{ + gint64 now; + guint signal_counts; + + while (TRUE) { + now = nm_utils_get_monotonic_timestamp_msec(); + + if (until_ms < now) + return 0; + + signal_counts = nmtstp_wait_for_signal(platform, until_ms - now); + if (signal_counts) + return signal_counts; + } +} + +const NMPlatformLink * +nmtstp_wait_for_link(NMPlatform *platform, + const char *ifname, + NMLinkType expected_link_type, + gint64 timeout_msec) +{ + return nmtstp_wait_for_link_until( + platform, + ifname, + expected_link_type, + timeout_msec ? nm_utils_get_monotonic_timestamp_msec() + timeout_msec : 0); +} + +const NMPlatformLink * +nmtstp_wait_for_link_until(NMPlatform *platform, + const char *ifname, + NMLinkType expected_link_type, + gint64 until_ms) +{ + const NMPlatformLink *plink; + gint64 now; + gboolean waited_once = FALSE; + + _init_platform(&platform, FALSE); + + while (TRUE) { + now = nm_utils_get_monotonic_timestamp_msec(); + + plink = nm_platform_link_get_by_ifname(platform, ifname); + if (plink && (expected_link_type == NM_LINK_TYPE_NONE || plink->type == expected_link_type)) + return plink; + + if (until_ms == 0) { + /* don't wait, don't even poll the socket. */ + return NULL; + } + + if (waited_once && until_ms < now) { + /* timeout reached (+ we already waited for a signal at least once). */ + return NULL; + } + + waited_once = TRUE; + /* regardless of whether timeout is already reached, we poll the netlink + * socket a bit. */ + nmtstp_wait_for_signal(platform, until_ms - now); + } +} + +/*****************************************************************************/ + +int +nmtstp_run_command_check_external_global(void) +{ + if (!nmtstp_is_root_test()) + return FALSE; + switch (nmtst_get_rand_uint32() % 3) { + case 0: + return -1; + case 1: + return FALSE; + default: + return TRUE; + } +} + +gboolean +nmtstp_run_command_check_external(int external_command) +{ + if (external_command != -1) { + g_assert(NM_IN_SET(external_command, FALSE, TRUE)); + g_assert(!external_command || nmtstp_is_root_test()); + return !!external_command; + } + if (!nmtstp_is_root_test()) + return FALSE; + return (nmtst_get_rand_uint32() % 2) == 0; +} + +/*****************************************************************************/ + +#define CHECK_LIFETIME_MAX_DIFF 2 + +gboolean +nmtstp_ip_address_check_lifetime(const NMPlatformIPAddress *addr, + gint64 now, + guint32 expected_lifetime, + guint32 expected_preferred) +{ + gint64 offset; + int i; + + g_assert(addr); + + if (now == -1) + now = nm_utils_get_monotonic_timestamp_sec(); + g_assert(now > 0); + + g_assert(expected_preferred <= expected_lifetime); + + if (expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT + && expected_preferred == NM_PLATFORM_LIFETIME_PERMANENT) { + return addr->timestamp == 0 && addr->lifetime == NM_PLATFORM_LIFETIME_PERMANENT + && addr->preferred == NM_PLATFORM_LIFETIME_PERMANENT; + } + + if (addr->timestamp == 0) + return FALSE; + + offset = (gint64) now - addr->timestamp; + + for (i = 0; i < 2; i++) { + guint32 lft = i ? expected_lifetime : expected_preferred; + guint32 adr = i ? addr->lifetime : addr->preferred; + + if (lft == NM_PLATFORM_LIFETIME_PERMANENT) { + if (adr != NM_PLATFORM_LIFETIME_PERMANENT) + return FALSE; + } else { + if (((gint64) adr) - offset <= ((gint64) lft) - CHECK_LIFETIME_MAX_DIFF + || ((gint64) adr) - offset >= ((gint64) lft) + CHECK_LIFETIME_MAX_DIFF) + return FALSE; + } + } + return TRUE; +} + +void +nmtstp_ip_address_assert_lifetime(const NMPlatformIPAddress *addr, + gint64 now, + guint32 expected_lifetime, + guint32 expected_preferred) +{ + gint64 n = now; + gint64 offset; + int i; + + g_assert(addr); + + if (now == -1) + now = nm_utils_get_monotonic_timestamp_sec(); + g_assert(now > 0); + + g_assert(expected_preferred <= expected_lifetime); + + if (expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT + && expected_preferred == NM_PLATFORM_LIFETIME_PERMANENT) { + g_assert_cmpint(addr->timestamp, ==, 0); + g_assert_cmpint(addr->lifetime, ==, NM_PLATFORM_LIFETIME_PERMANENT); + g_assert_cmpint(addr->preferred, ==, NM_PLATFORM_LIFETIME_PERMANENT); + return; + } + + g_assert_cmpint(addr->timestamp, >, 0); + g_assert_cmpint(addr->timestamp, <=, now); + + offset = (gint64) now - addr->timestamp; + g_assert_cmpint(offset, >=, 0); + + for (i = 0; i < 2; i++) { + guint32 lft = i ? expected_lifetime : expected_preferred; + guint32 adr = i ? addr->lifetime : addr->preferred; + + if (lft == NM_PLATFORM_LIFETIME_PERMANENT) + g_assert_cmpint(adr, ==, NM_PLATFORM_LIFETIME_PERMANENT); + else { + g_assert_cmpint(adr - offset, <=, lft + CHECK_LIFETIME_MAX_DIFF); + g_assert_cmpint(adr - offset, >=, lft - CHECK_LIFETIME_MAX_DIFF); + } + } + + g_assert(nmtstp_ip_address_check_lifetime(addr, n, expected_lifetime, expected_preferred)); +} + +/*****************************************************************************/ + +static void +_ip_address_add(NMPlatform * platform, + gboolean external_command, + gboolean is_v4, + int ifindex, + const NMIPAddr *address, + int plen, + const NMIPAddr *peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char * label) +{ + gint64 end_time; + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + const char * ifname; + gs_free char *s_valid = NULL; + gs_free char *s_preferred = NULL; + gs_free char *s_label = NULL; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + + ifname = nm_platform_link_get_name(platform, ifindex); + g_assert(ifname); + + if (lifetime != NM_PLATFORM_LIFETIME_PERMANENT) + s_valid = g_strdup_printf(" valid_lft %d", lifetime); + if (preferred != NM_PLATFORM_LIFETIME_PERMANENT) + s_preferred = g_strdup_printf(" preferred_lft %d", preferred); + if (label) + s_label = g_strdup_printf("%s:%s", ifname, label); + + if (is_v4) { + char s_peer[NM_UTILS_INET_ADDRSTRLEN + 50]; + + g_assert(flags == 0); + + if (peer_address->addr4 != address->addr4 || nmtst_get_rand_uint32() % 2) { + /* If the peer is the same as the local address, we can omit it. The result should be identical */ + nm_sprintf_buf(s_peer, " peer %s", _nm_utils_inet4_ntop(peer_address->addr4, b2)); + } else + s_peer[0] = '\0'; + + nmtstp_run_command_check("ip address change %s%s/%d dev %s%s%s%s", + _nm_utils_inet4_ntop(address->addr4, b1), + s_peer, + plen, + ifname, + s_valid ?: "", + s_preferred ?: "", + s_label ?: ""); + } else { + g_assert(label == NULL); + + /* flags not implemented (yet) */ + g_assert(flags == 0); + nmtstp_run_command_check("ip address change %s%s%s/%d dev %s%s%s%s", + _nm_utils_inet6_ntop(&address->addr6, b1), + !IN6_IS_ADDR_UNSPECIFIED(&peer_address->addr6) ? " peer " : "", + !IN6_IS_ADDR_UNSPECIFIED(&peer_address->addr6) + ? _nm_utils_inet6_ntop(&peer_address->addr6, b2) + : "", + plen, + ifname, + s_valid ?: "", + s_preferred ?: "", + s_label ?: ""); + } + } else { + gboolean success; + + if (is_v4) { + success = nm_platform_ip4_address_add(platform, + ifindex, + address->addr4, + plen, + peer_address->addr4, + 0u, + lifetime, + preferred, + flags, + label); + } else { + g_assert(label == NULL); + success = nm_platform_ip6_address_add(platform, + ifindex, + address->addr6, + plen, + peer_address->addr6, + lifetime, + preferred, + flags); + } + g_assert(success); + } + + /* Let's wait until we see the address. */ + end_time = nm_utils_get_monotonic_timestamp_msec() + 500; + do { + if (external_command) + nm_platform_process_events(platform); + + /* let's wait until we see the address as we added it. */ + if (is_v4) { + const NMPlatformIP4Address *a; + + g_assert(flags == 0); + a = nm_platform_ip4_address_get(platform, + ifindex, + address->addr4, + plen, + peer_address->addr4); + if (a && a->peer_address == peer_address->addr4 + && nmtstp_ip_address_check_lifetime((NMPlatformIPAddress *) a, + -1, + lifetime, + preferred) + && strcmp(a->label, label ?: "") == 0) + break; + } else { + const NMPlatformIP6Address *a; + + g_assert(label == NULL); + g_assert(flags == 0); + + a = nm_platform_ip6_address_get(platform, ifindex, &address->addr6); + if (a + && !memcmp(nm_platform_ip6_address_get_peer(a), + (IN6_IS_ADDR_UNSPECIFIED(&peer_address->addr6) + || IN6_ARE_ADDR_EQUAL(&address->addr6, &peer_address->addr6)) + ? &address->addr6 + : &peer_address->addr6, + sizeof(struct in6_addr)) + && nmtstp_ip_address_check_lifetime((NMPlatformIPAddress *) a, + -1, + lifetime, + preferred)) + break; + } + + /* for internal command, we expect not to reach this line.*/ + g_assert(external_command); + + nmtstp_assert_wait_for_signal_until(platform, end_time); + } while (TRUE); +} + +void +nmtstp_ip4_address_add(NMPlatform *platform, + gboolean external_command, + int ifindex, + in_addr_t address, + int plen, + in_addr_t peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char *label) +{ + _ip_address_add(platform, + external_command, + TRUE, + ifindex, + (NMIPAddr *) &address, + plen, + (NMIPAddr *) &peer_address, + lifetime, + preferred, + flags, + label); +} + +void +nmtstp_ip6_address_add(NMPlatform * platform, + gboolean external_command, + int ifindex, + struct in6_addr address, + int plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags) +{ + _ip_address_add(platform, + external_command, + FALSE, + ifindex, + (NMIPAddr *) &address, + plen, + (NMIPAddr *) &peer_address, + lifetime, + preferred, + flags, + NULL); +} + +void +nmtstp_ip4_route_add(NMPlatform * platform, + int ifindex, + NMIPConfigSource source, + in_addr_t network, + guint8 plen, + in_addr_t gateway, + in_addr_t pref_src, + guint32 metric, + guint32 mss) +{ + NMPlatformIP4Route route = {}; + + route.ifindex = ifindex; + route.rt_source = source; + route.network = network; + route.plen = plen; + route.gateway = gateway; + route.pref_src = pref_src; + route.metric = metric; + route.mss = mss; + + g_assert( + NMTST_NM_ERR_SUCCESS(nm_platform_ip4_route_add(platform, NMP_NLM_FLAG_REPLACE, &route))); +} + +void +nmtstp_ip6_route_add(NMPlatform * platform, + int ifindex, + NMIPConfigSource source, + struct in6_addr network, + guint8 plen, + struct in6_addr gateway, + struct in6_addr pref_src, + guint32 metric, + guint32 mss) +{ + NMPlatformIP6Route route = {}; + + route.ifindex = ifindex; + route.rt_source = source; + route.network = network; + route.plen = plen; + route.gateway = gateway; + route.pref_src = pref_src; + route.metric = metric; + route.mss = mss; + + g_assert( + NMTST_NM_ERR_SUCCESS(nm_platform_ip6_route_add(platform, NMP_NLM_FLAG_REPLACE, &route))); +} + +/*****************************************************************************/ + +static void +_ip_address_del(NMPlatform * platform, + gboolean external_command, + gboolean is_v4, + int ifindex, + const NMIPAddr *address, + int plen, + const NMIPAddr *peer_address) +{ + gint64 end_time; + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + const char *ifname; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + int success; + gboolean had_address; + + ifname = nm_platform_link_get_name(platform, ifindex); + g_assert(ifname); + + /* let's wait until we see the address as we added it. */ + if (is_v4) + had_address = !!nm_platform_ip4_address_get(platform, + ifindex, + address->addr4, + plen, + peer_address->addr4); + else + had_address = !!nm_platform_ip6_address_get(platform, ifindex, &address->addr6); + + if (is_v4) { + success = nmtstp_run_command("ip address delete %s%s%s/%d dev %s", + _nm_utils_inet4_ntop(address->addr4, b1), + peer_address->addr4 != address->addr4 ? " peer " : "", + peer_address->addr4 != address->addr4 + ? _nm_utils_inet4_ntop(peer_address->addr4, b2) + : "", + plen, + ifname); + } else { + g_assert(!peer_address); + success = nmtstp_run_command("ip address delete %s/%d dev %s", + _nm_utils_inet6_ntop(&address->addr6, b1), + plen, + ifname); + } + g_assert(success == 0 || !had_address); + } else { + gboolean success; + + if (is_v4) { + success = nm_platform_ip4_address_delete(platform, + ifindex, + address->addr4, + plen, + peer_address->addr4); + } else { + g_assert(!peer_address); + success = nm_platform_ip6_address_delete(platform, ifindex, address->addr6, plen); + } + g_assert(success); + } + + /* Let's wait until we get the result */ + end_time = nm_utils_get_monotonic_timestamp_msec() + 250; + do { + if (external_command) + nm_platform_process_events(platform); + + /* let's wait until we see the address as we added it. */ + if (is_v4) { + const NMPlatformIP4Address *a; + + a = nm_platform_ip4_address_get(platform, + ifindex, + address->addr4, + plen, + peer_address->addr4); + if (!a) + break; + } else { + const NMPlatformIP6Address *a; + + a = nm_platform_ip6_address_get(platform, ifindex, &address->addr6); + if (!a) + break; + } + + /* for internal command, we expect not to reach this line.*/ + g_assert(external_command); + + nmtstp_assert_wait_for_signal_until(platform, end_time); + } while (TRUE); +} + +void +nmtstp_ip4_address_del(NMPlatform *platform, + gboolean external_command, + int ifindex, + in_addr_t address, + int plen, + in_addr_t peer_address) +{ + _ip_address_del(platform, + external_command, + TRUE, + ifindex, + (NMIPAddr *) &address, + plen, + (NMIPAddr *) &peer_address); +} + +void +nmtstp_ip6_address_del(NMPlatform * platform, + gboolean external_command, + int ifindex, + struct in6_addr address, + int plen) +{ + _ip_address_del(platform, external_command, FALSE, ifindex, (NMIPAddr *) &address, plen, NULL); +} + +/*****************************************************************************/ + +#define _assert_pllink(platform, success, pllink, name, type) \ + G_STMT_START \ + { \ + const NMPlatformLink *_pllink = (pllink); \ + \ + if ((success)) { \ + g_assert(_pllink); \ + g_assert(_pllink \ + == nmtstp_link_get_typed(platform, _pllink->ifindex, (name), (type))); \ + } else { \ + g_assert(!_pllink); \ + g_assert(!nmtstp_link_get(platform, 0, (name))); \ + } \ + } \ + G_STMT_END + +const NMPlatformLink * +nmtstp_link_bridge_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkBridge *lnk) +{ + const NMPlatformLink * pllink = NULL; + const NMPlatformLnkBridge *ll = NULL; + int r = 0; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + char sbuf_gfw[100]; + char sbuf_mhm[100]; + char sbuf_mlmc[100]; + char sbuf_mlmi[100]; + char sbuf_mmi[100]; + char sbuf_mqi[100]; + char sbuf_mqii[100]; + char sbuf_msqc[100]; + char sbuf_msqi[100]; + char sbuf_mqri[100]; + + r = nmtstp_run_command( + "ip link add %s type bridge " + "forward_delay %u " + "hello_time %u " + "max_age %u " + "ageing_time %u " + "stp_state %d " + "priority %u " + "vlan_protocol %u " + "vlan_stats_enabled %d " + "%s" /* group_fwd_mask */ + "group_address " NM_ETHER_ADDR_FORMAT_STR " " + "mcast_snooping %d " + "mcast_router %u " + "mcast_query_use_ifaddr %d " + "mcast_querier %d " + "%s" /* mcast_hash_max */ + "%s" /* mcast_last_member_count */ + "%s" /* mcast_startup_query_count */ + "%s" /* mcast_last_member_interval */ + "%s" /* mcast_membership_interval */ + "%s" /* mcast_querier_interval */ + "%s" /* mcast_query_interval */ + "%s" /* mcast_query_response_interval */ + "%s" /* mcast_startup_query_interval */ + "", + name, + lnk->forward_delay, + lnk->hello_time, + lnk->max_age, + lnk->ageing_time, + (int) lnk->stp_state, + lnk->priority, + lnk->vlan_protocol, + (int) lnk->vlan_stats_enabled, + lnk->group_fwd_mask != 0 + ? nm_sprintf_buf(sbuf_gfw, "group_fwd_mask %#x ", lnk->group_fwd_mask) + : "", + NM_ETHER_ADDR_FORMAT_VAL(&lnk->group_addr), + (int) lnk->mcast_snooping, + lnk->mcast_router, + (int) lnk->mcast_query_use_ifaddr, + (int) lnk->mcast_querier, + lnk->mcast_hash_max != NM_BRIDGE_MULTICAST_HASH_MAX_DEF + ? nm_sprintf_buf(sbuf_mhm, "mcast_hash_max %u ", lnk->mcast_hash_max) + : "", + lnk->mcast_last_member_count != NM_BRIDGE_MULTICAST_LAST_MEMBER_COUNT_DEF + ? nm_sprintf_buf(sbuf_mlmc, + "mcast_last_member_count %u ", + lnk->mcast_last_member_count) + : "", + lnk->mcast_startup_query_count != NM_BRIDGE_MULTICAST_STARTUP_QUERY_COUNT_DEF + ? nm_sprintf_buf(sbuf_msqc, + "mcast_startup_query_count %u ", + lnk->mcast_startup_query_count) + : "", + lnk->mcast_last_member_interval != NM_BRIDGE_MULTICAST_LAST_MEMBER_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_mlmi, + "mcast_last_member_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_last_member_interval) + : "", + lnk->mcast_membership_interval != NM_BRIDGE_MULTICAST_MEMBERSHIP_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_mmi, + "mcast_membership_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_membership_interval) + : "", + lnk->mcast_querier_interval != NM_BRIDGE_MULTICAST_QUERIER_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_mqi, + "mcast_querier_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_querier_interval) + : "", + lnk->mcast_query_interval != NM_BRIDGE_MULTICAST_QUERY_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_mqii, + "mcast_query_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_query_interval) + : "", + lnk->mcast_query_response_interval != NM_BRIDGE_MULTICAST_QUERY_RESPONSE_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_mqri, + "mcast_query_response_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_query_response_interval) + : "", + lnk->mcast_startup_query_interval != NM_BRIDGE_MULTICAST_STARTUP_QUERY_INTERVAL_DEF + ? nm_sprintf_buf(sbuf_msqi, + "mcast_startup_query_interval %" G_GUINT64_FORMAT " ", + lnk->mcast_startup_query_interval) + : ""); + if (r == 0) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_BRIDGE, 100); + else + _LOGI( + "Adding bridge device via iproute2 failed. Assume iproute2 is not up to the task."); + } + + if (!pllink) { + r = nm_platform_link_bridge_add(platform, name, NULL, 0, 0, lnk, &pllink); + } + + _assert_pllink(platform, r == 0, pllink, name, NM_LINK_TYPE_BRIDGE); + + ll = NMP_OBJECT_CAST_LNK_BRIDGE(NMP_OBJECT_UP_CAST(pllink)->_link.netlink.lnk); + + g_assert_cmpint(lnk->forward_delay, ==, ll->forward_delay); + g_assert_cmpint(lnk->hello_time, ==, ll->hello_time); + g_assert_cmpint(lnk->max_age, ==, ll->max_age); + g_assert_cmpint(lnk->ageing_time, ==, ll->ageing_time); + g_assert_cmpint(lnk->stp_state, ==, ll->stp_state); + g_assert_cmpint(lnk->priority, ==, ll->priority); + g_assert_cmpint(lnk->vlan_stats_enabled, ==, ll->vlan_stats_enabled); + g_assert_cmpint(lnk->group_fwd_mask, ==, ll->group_fwd_mask); + g_assert_cmpint(lnk->mcast_snooping, ==, ll->mcast_snooping); + g_assert_cmpint(lnk->mcast_router, ==, ll->mcast_router); + g_assert_cmpint(lnk->mcast_query_use_ifaddr, ==, ll->mcast_query_use_ifaddr); + g_assert_cmpint(lnk->mcast_querier, ==, ll->mcast_querier); + g_assert_cmpint(lnk->mcast_hash_max, ==, ll->mcast_hash_max); + g_assert_cmpint(lnk->mcast_last_member_count, ==, ll->mcast_last_member_count); + g_assert_cmpint(lnk->mcast_startup_query_count, ==, ll->mcast_startup_query_count); + g_assert_cmpint(lnk->mcast_last_member_interval, ==, ll->mcast_last_member_interval); + g_assert_cmpint(lnk->mcast_membership_interval, ==, ll->mcast_membership_interval); + g_assert_cmpint(lnk->mcast_querier_interval, ==, ll->mcast_querier_interval); + g_assert_cmpint(lnk->mcast_query_interval, ==, ll->mcast_query_interval); + g_assert_cmpint(lnk->mcast_query_response_interval, ==, ll->mcast_query_response_interval); + g_assert_cmpint(lnk->mcast_startup_query_interval, ==, ll->mcast_startup_query_interval); + + return pllink; +} +const NMPlatformLink * +nmtstp_link_veth_add(NMPlatform *platform, + gboolean external_command, + const char *name, + const char *peer) +{ + const NMPlatformLink *pllink = NULL; + gboolean success = FALSE; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + success = !nmtstp_run_command("ip link add dev %s type veth peer name %s", name, peer); + if (success) { + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_VETH, 100); + nmtstp_assert_wait_for_link(platform, peer, NM_LINK_TYPE_VETH, 10); + } else { + /* iproute2 might fail in copr. See below. + * We accept that and try our platform implementation instead. */ + _LOGI("iproute2 failed to add veth device. Retry with platform code."); + external_command = FALSE; + } + } + + if (!external_command) { + int try_count = 0; + int r; + +again: + r = nm_platform_link_veth_add(platform, name, peer, &pllink); + if (r == -EPERM && try_count++ < 5) { + /* in copr (mock with Fedora 33 builders), this randomly fails with EPERM. + * Very odd. Try to work around by retrying. */ + _LOGI("netlink failuer EPERM to add veth device. Retry."); + goto again; + } + success = NMTST_NM_ERR_SUCCESS(r); + } + + g_assert(success); + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_VETH); + return pllink; +} + +const NMPlatformLink * +nmtstp_link_dummy_add(NMPlatform *platform, gboolean external_command, const char *name) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + success = !nmtstp_run_command("ip link add %s type dummy", name); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_DUMMY, 100); + } else + success = NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(platform, name, &pllink)); + + g_assert(success); + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_DUMMY); + return pllink; +} + +const NMPlatformLink * +nmtstp_link_gre_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkGre *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char b1[INET_ADDRSTRLEN]; + char b2[INET_ADDRSTRLEN]; + NMLinkType link_type; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + link_type = lnk->is_tap ? NM_LINK_TYPE_GRETAP : NM_LINK_TYPE_GRE; + + _init_platform(&platform, external_command); + + if (external_command) { + gs_free char *dev = NULL; + char * obj, *type; + + if (lnk->parent_ifindex) + dev = + g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex)); + + obj = lnk->is_tap ? "link" : "tunnel"; + type = lnk->is_tap ? "type gretap" : "mode gre"; + + success = !nmtstp_run_command("ip %s add %s %s %s local %s remote %s ttl %u tos %02x %s", + obj, + name, + type, + dev ?: "", + _nm_utils_inet4_ntop(lnk->local, b1), + _nm_utils_inet4_ntop(lnk->remote, b2), + lnk->ttl, + lnk->tos, + lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc"); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, link_type, 100); + } else + success = + NMTST_NM_ERR_SUCCESS(nm_platform_link_gre_add(platform, name, NULL, 0, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, link_type); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_ip6tnl_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIp6Tnl *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + char encap[20]; + char tclass[20]; + gboolean encap_ignore; + gboolean tclass_inherit; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + g_assert(!lnk->is_gre); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + gs_free char *dev = NULL; + const char * mode; + + if (lnk->parent_ifindex) + dev = + g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex)); + + switch (lnk->proto) { + case IPPROTO_IPIP: + mode = "ipip6"; + break; + case IPPROTO_IPV6: + mode = "ip6ip6"; + break; + default: + g_assert_not_reached(); + } + + encap_ignore = NM_FLAGS_HAS(lnk->flags, IP6_TNL_F_IGN_ENCAP_LIMIT); + tclass_inherit = NM_FLAGS_HAS(lnk->flags, IP6_TNL_F_USE_ORIG_TCLASS); + + success = !nmtstp_run_command( + "ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %s encaplimit %s " + "flowlabel %x", + name, + mode, + dev, + _nm_utils_inet6_ntop(&lnk->local, b1), + _nm_utils_inet6_ntop(&lnk->remote, b2), + lnk->ttl, + tclass_inherit ? "inherit" : nm_sprintf_buf(tclass, "%02x", lnk->tclass), + encap_ignore ? "none" : nm_sprintf_buf(encap, "%u", lnk->encap_limit), + lnk->flow_label); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_IP6TNL, 100); + } else + success = NMTST_NM_ERR_SUCCESS(nm_platform_link_ip6tnl_add(platform, name, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_IP6TNL); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_ip6gre_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIp6Tnl *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char b1[NM_UTILS_INET_ADDRSTRLEN]; + char b2[NM_UTILS_INET_ADDRSTRLEN]; + char tclass[20]; + gboolean tclass_inherit; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + g_assert(lnk->is_gre); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + gs_free char *dev = NULL; + + if (lnk->parent_ifindex) + dev = + g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex)); + + tclass_inherit = NM_FLAGS_HAS(lnk->flags, IP6_TNL_F_USE_ORIG_TCLASS); + + success = !nmtstp_run_command( + "ip link add %s type %s %s local %s remote %s ttl %u tclass %s flowlabel %x", + name, + lnk->is_tap ? "ip6gretap" : "ip6gre", + dev, + _nm_utils_inet6_ntop(&lnk->local, b1), + _nm_utils_inet6_ntop(&lnk->remote, b2), + lnk->ttl, + tclass_inherit ? "inherit" : nm_sprintf_buf(tclass, "%02x", lnk->tclass), + lnk->flow_label); + if (success) { + pllink = nmtstp_assert_wait_for_link(platform, + name, + lnk->is_tap ? NM_LINK_TYPE_IP6GRETAP + : NM_LINK_TYPE_IP6GRE, + 100); + } + } else + success = NMTST_NM_ERR_SUCCESS( + nm_platform_link_ip6gre_add(platform, name, NULL, 0, lnk, &pllink)); + + _assert_pllink(platform, + success, + pllink, + name, + lnk->is_tap ? NM_LINK_TYPE_IP6GRETAP : NM_LINK_TYPE_IP6GRE); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_ipip_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIpIp *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char b1[INET_ADDRSTRLEN]; + char b2[INET_ADDRSTRLEN]; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + gs_free char *dev = NULL; + + if (lnk->parent_ifindex) + dev = + g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex)); + + success = !nmtstp_run_command( + "ip tunnel add %s mode ipip %s local %s remote %s ttl %u tos %02x %s", + name, + dev, + _nm_utils_inet4_ntop(lnk->local, b1), + _nm_utils_inet4_ntop(lnk->remote, b2), + lnk->ttl, + lnk->tos, + lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc"); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_IPIP, 100); + } else + success = NMTST_NM_ERR_SUCCESS(nm_platform_link_ipip_add(platform, name, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_IPIP); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_macvlan_add(NMPlatform * platform, + gboolean external_command, + const char * name, + int parent, + const NMPlatformLnkMacvlan *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + NMLinkType link_type; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + link_type = lnk->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN; + + if (external_command) { + const char *dev; + char * modes[] = { + [MACVLAN_MODE_BRIDGE] = "bridge", + [MACVLAN_MODE_VEPA] = "vepa", + [MACVLAN_MODE_PRIVATE] = "private", + [MACVLAN_MODE_PASSTHRU] = "passthru", + }; + + dev = nm_platform_link_get_name(platform, parent); + g_assert(dev); + g_assert_cmpint(lnk->mode, <, G_N_ELEMENTS(modes)); + + success = !nmtstp_run_command("ip link add name %s link %s type %s mode %s %s", + name, + dev, + lnk->tap ? "macvtap" : "macvlan", + modes[lnk->mode], + lnk->no_promisc ? "nopromisc" : ""); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, link_type, 100); + } else + success = NMTST_NM_ERR_SUCCESS( + nm_platform_link_macvlan_add(platform, name, parent, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, link_type); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_sit_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkSit *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char b1[INET_ADDRSTRLEN]; + char b2[INET_ADDRSTRLEN]; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + const char *dev = ""; + + if (lnk->parent_ifindex) { + const char *parent_name; + + parent_name = nm_platform_link_get_name(platform, lnk->parent_ifindex); + g_assert(parent_name); + dev = nm_sprintf_bufa(100, " dev %s", parent_name); + } + + success = + !nmtstp_run_command("ip tunnel add %s mode sit%s local %s remote %s ttl %u tos %02x %s", + name, + dev, + _nm_utils_inet4_ntop(lnk->local, b1), + _nm_utils_inet4_ntop(lnk->remote, b2), + lnk->ttl, + lnk->tos, + lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc"); + if (success) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_SIT, 100); + } else + success = NMTST_NM_ERR_SUCCESS(nm_platform_link_sit_add(platform, name, lnk, &pllink)); + + _assert_pllink(platform, success, pllink, name, NM_LINK_TYPE_SIT); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_tun_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkTun *lnk, + int * out_fd) +{ + const NMPlatformLink *pllink = NULL; + int err; + int r; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + g_assert(lnk); + g_assert(NM_IN_SET(lnk->type, IFF_TUN, IFF_TAP)); + g_assert(!out_fd || *out_fd == -1); + + if (!lnk->persist) { + /* ip tuntap does not support non-persistent devices. + * + * Add this device only via NMPlatform. */ + if (external_command == -1) + external_command = FALSE; + } + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + g_assert(lnk->persist); + + err = nmtstp_run_command( + "ip tuntap add" + " mode %s" + "%s" /* user */ + "%s" /* group */ + "%s" /* pi */ + "%s" /* vnet_hdr */ + "%s" /* multi_queue */ + " name %s", + lnk->type == IFF_TUN ? "tun" : "tap", + lnk->owner_valid ? nm_sprintf_bufa(100, " user %u", (guint) lnk->owner) : "", + lnk->group_valid ? nm_sprintf_bufa(100, " group %u", (guint) lnk->group) : "", + lnk->pi ? " pi" : "", + lnk->vnet_hdr ? " vnet_hdr" : "", + lnk->multi_queue ? " multi_queue" : "", + name); + /* Older versions of iproute2 don't support adding devices. + * On failure, fallback to using platform code. */ + if (err == 0) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_TUN, 100); + else + g_error("failure to add tun/tap device via ip-route"); + } else { + g_assert(lnk->persist || out_fd); + r = nm_platform_link_tun_add(platform, name, lnk, &pllink, out_fd); + g_assert_cmpint(r, ==, 0); + } + + g_assert(pllink); + g_assert_cmpint(pllink->type, ==, NM_LINK_TYPE_TUN); + g_assert_cmpstr(pllink->name, ==, name); + return pllink; +} + +const NMPlatformLink * +nmtstp_link_vrf_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkVrf *lnk, + gboolean * out_not_supported) +{ + const NMPlatformLink *pllink = NULL; + int r = 0; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + NM_SET_OUT(out_not_supported, FALSE); + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + r = nmtstp_run_command("ip link add %s type vrf table %u", name, lnk->table); + + if (r == 0) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_VRF, 100); + else + _LOGI("Adding vrf device via iproute2 failed. Assume iproute2 is not up to the task."); + } + + if (!pllink) { + r = nm_platform_link_vrf_add(platform, name, lnk, &pllink); + if (r == -EOPNOTSUPP) + NM_SET_OUT(out_not_supported, TRUE); + } + + _assert_pllink(platform, r == 0, pllink, name, NM_LINK_TYPE_VRF); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_vxlan_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkVxlan *lnk) +{ + const NMPlatformLink *pllink = NULL; + int err; + int r; + + g_assert(nm_utils_ifname_valid_kernel(name, NULL)); + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + gs_free char *dev = NULL; + char local[NM_UTILS_INET_ADDRSTRLEN]; + char group[NM_UTILS_INET_ADDRSTRLEN]; + + if (lnk->parent_ifindex) + dev = + g_strdup_printf("dev %s", nm_platform_link_get_name(platform, lnk->parent_ifindex)); + + if (lnk->local) + _nm_utils_inet4_ntop(lnk->local, local); + else if (memcmp(&lnk->local6, &in6addr_any, sizeof(in6addr_any))) + _nm_utils_inet6_ntop(&lnk->local6, local); + else + local[0] = '\0'; + + if (lnk->group) + _nm_utils_inet4_ntop(lnk->group, group); + else if (memcmp(&lnk->group6, &in6addr_any, sizeof(in6addr_any))) + _nm_utils_inet6_ntop(&lnk->group6, group); + else + group[0] = '\0'; + + err = nmtstp_run_command("ip link add %s type vxlan id %u %s local %s group %s ttl %u tos " + "%02x dstport %u srcport %u %u ageing %u", + name, + lnk->id, + dev ?: "", + local, + group, + lnk->ttl, + lnk->tos, + lnk->dst_port, + lnk->src_port_min, + lnk->src_port_max, + lnk->ageing); + /* Older versions of iproute2 don't support adding vxlan devices. + * On failure, fallback to using platform code. */ + if (err == 0) + pllink = nmtstp_assert_wait_for_link(platform, name, NM_LINK_TYPE_VXLAN, 100); + else + _LOGI( + "Adding vxlan device via iproute2 failed. Assume iproute2 is not up to the task."); + } + if (!pllink) { + r = nm_platform_link_vxlan_add(platform, name, lnk, &pllink); + g_assert(NMTST_NM_ERR_SUCCESS(r)); + g_assert(pllink); + } + + g_assert_cmpint(pllink->type, ==, NM_LINK_TYPE_VXLAN); + g_assert_cmpstr(pllink->name, ==, name); + return pllink; +} + +/*****************************************************************************/ + +const NMPlatformLink * +nmtstp_link_get_typed(NMPlatform *platform, int ifindex, const char *name, NMLinkType link_type) +{ + const NMPlatformLink *pllink = NULL; + + _init_platform(&platform, FALSE); + + if (ifindex > 0) { + pllink = nm_platform_link_get(platform, ifindex); + + if (pllink) { + g_assert_cmpint(pllink->ifindex, ==, ifindex); + if (name) + g_assert_cmpstr(name, ==, pllink->name); + } else { + if (name) + g_assert(!nm_platform_link_get_by_ifname(platform, name)); + } + } else { + g_assert(name); + + pllink = nm_platform_link_get_by_ifname(platform, name); + + if (pllink) + g_assert_cmpstr(name, ==, pllink->name); + } + + g_assert(!name || nm_utils_ifname_valid_kernel(name, NULL)); + + if (pllink && link_type != NM_LINK_TYPE_NONE) + g_assert_cmpint(pllink->type, ==, link_type); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_get(NMPlatform *platform, int ifindex, const char *name) +{ + return nmtstp_link_get_typed(platform, ifindex, name, NM_LINK_TYPE_NONE); +} + +/*****************************************************************************/ + +void +nmtstp_link_delete(NMPlatform *platform, + gboolean external_command, + int ifindex, + const char *name, + gboolean require_exist) +{ + gint64 end_time; + const NMPlatformLink *pllink; + gboolean success; + gs_free char * name_copy = NULL; + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + pllink = nmtstp_link_get(platform, ifindex, name); + + if (!pllink) { + g_assert(!require_exist); + return; + } + + name = name_copy = g_strdup(pllink->name); + ifindex = pllink->ifindex; + + if (external_command) { + nmtstp_run_command_check("ip link delete %s", name); + } else { + success = nm_platform_link_delete(platform, ifindex); + g_assert(success); + } + + /* Let's wait until we get the result */ + end_time = nm_utils_get_monotonic_timestamp_msec() + 250; + do { + if (external_command) + nm_platform_process_events(platform); + + if (!nm_platform_link_get(platform, ifindex)) { + g_assert(!nm_platform_link_get_by_ifname(platform, name)); + break; + } + + /* for internal command, we expect not to reach this line.*/ + g_assert(external_command); + + nmtstp_assert_wait_for_signal_until(platform, end_time); + } while (TRUE); +} + +/*****************************************************************************/ + +void +nmtstp_link_set_updown(NMPlatform *platform, gboolean external_command, int ifindex, gboolean up) +{ + const NMPlatformLink *plink; + gint64 end_time; + + external_command = nmtstp_run_command_check_external(external_command); + + _init_platform(&platform, external_command); + + if (external_command) { + const char *ifname; + + ifname = nm_platform_link_get_name(platform, ifindex); + g_assert(ifname); + + nmtstp_run_command_check("ip link set %s %s", ifname, up ? "up" : "down"); + } else { + if (up) + g_assert(nm_platform_link_set_up(platform, ifindex, NULL)); + else + g_assert(nm_platform_link_set_down(platform, ifindex)); + } + + /* Let's wait until we get the result */ + end_time = nm_utils_get_monotonic_timestamp_msec() + 250; + do { + if (external_command) + nm_platform_process_events(platform); + + /* let's wait until we see the address as we added it. */ + plink = nm_platform_link_get(platform, ifindex); + g_assert(plink); + + if (NM_FLAGS_HAS(plink->n_ifi_flags, IFF_UP) == !!up) + break; + + /* for internal command, we expect not to reach this line.*/ + g_assert(external_command); + + nmtstp_assert_wait_for_signal_until(platform, end_time); + } while (TRUE); +} + +/*****************************************************************************/ + +gboolean +nmtstp_kernel_support_get(NMPlatformKernelSupportType type) +{ + const NMPlatformLink *pllink; + NMOptionBool v; + + v = nm_platform_kernel_support_get_full(type, FALSE); + if (v != NM_OPTION_BOOL_DEFAULT) + return v != NM_OPTION_BOOL_FALSE; + + switch (type) { + case NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED: + pllink = nmtstp_link_bridge_add(NULL, -1, "br-test-11", &nm_platform_lnk_bridge_default); + nmtstp_link_delete(NULL, -1, pllink->ifindex, NULL, TRUE); + v = nm_platform_kernel_support_get_full(type, FALSE); + g_assert(v != NM_OPTION_BOOL_DEFAULT); + return v; + default: + g_assert_not_reached(); + } +} + +/*****************************************************************************/ + +struct _NMTstpNamespaceHandle { + pid_t pid; + int pipe_fd; +}; + +NMTstpNamespaceHandle * +nmtstp_namespace_create(int unshare_flags, GError **error) +{ + NMTstpNamespaceHandle *ns_handle; + int e; + int errsv; + pid_t pid, pid2; + int pipefd_c2p[2]; + int pipefd_p2c[2]; + ssize_t r; + + e = pipe2(pipefd_c2p, O_CLOEXEC); + if (e != 0) { + errsv = errno; + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "pipe() failed with %d (%s)", + errsv, + nm_strerror_native(errsv)); + return FALSE; + } + + e = pipe2(pipefd_p2c, O_CLOEXEC); + if (e != 0) { + errsv = errno; + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "pipe() failed with %d (%s)", + errsv, + nm_strerror_native(errsv)); + nm_close(pipefd_c2p[0]); + nm_close(pipefd_c2p[1]); + return FALSE; + } + + pid = fork(); + if (pid < 0) { + errsv = errno; + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "fork() failed with %d (%s)", + errsv, + nm_strerror_native(errsv)); + nm_close(pipefd_c2p[0]); + nm_close(pipefd_c2p[1]); + nm_close(pipefd_p2c[0]); + nm_close(pipefd_p2c[1]); + return FALSE; + } + + if (pid == 0) { + char read_buf[1]; + + nm_close(pipefd_c2p[0]); /* close read-end */ + nm_close(pipefd_p2c[1]); /* close write-end */ + + if (unshare(unshare_flags) != 0) { + errsv = errno; + if (errsv == 0) + errsv = -1; + } else + errsv = 0; + + /* sync with parent process and send result. */ + do { + r = write(pipefd_c2p[1], &errsv, sizeof(errsv)); + } while (r < 0 && errno == EINTR); + if (r != sizeof(errsv)) { + errsv = errno; + if (errsv == 0) + errsv = -2; + } + nm_close(pipefd_c2p[1]); + + /* wait until parent process terminates (or kills us). */ + if (errsv == 0) { + do { + r = read(pipefd_p2c[0], read_buf, sizeof(read_buf)); + } while (r < 0 && errno == EINTR); + } + nm_close(pipefd_p2c[0]); + _exit(0); + } + + nm_close(pipefd_c2p[1]); /* close write-end */ + nm_close(pipefd_p2c[0]); /* close read-end */ + + /* sync with child process. */ + do { + r = read(pipefd_c2p[0], &errsv, sizeof(errsv)); + } while (r < 0 && errno == EINTR); + + nm_close(pipefd_c2p[0]); + + if (r != sizeof(errsv) || errsv != 0) { + int status; + + if (r != sizeof(errsv)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "child process failed for unknown reason"); + } else { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "child process signaled failure %d (%s)", + errsv, + nm_strerror_native(errsv)); + } + nm_close(pipefd_p2c[1]); + kill(pid, SIGKILL); + do { + pid2 = waitpid(pid, &status, 0); + } while (pid2 == -1 && errno == EINTR); + return FALSE; + } + + ns_handle = g_new0(NMTstpNamespaceHandle, 1); + ns_handle->pid = pid; + ns_handle->pipe_fd = pipefd_p2c[1]; + return ns_handle; +} + +pid_t +nmtstp_namespace_handle_get_pid(NMTstpNamespaceHandle *ns_handle) +{ + g_return_val_if_fail(ns_handle, 0); + g_return_val_if_fail(ns_handle->pid > 0, 0); + + return ns_handle->pid; +} + +void +nmtstp_namespace_handle_release(NMTstpNamespaceHandle *ns_handle) +{ + pid_t pid; + int status; + + if (!ns_handle) + return; + + g_return_if_fail(ns_handle->pid > 0); + + nm_close(ns_handle->pipe_fd); + ns_handle->pipe_fd = 0; + + kill(ns_handle->pid, SIGKILL); + + do { + pid = waitpid(ns_handle->pid, &status, 0); + } while (pid == -1 && errno == EINTR); + ns_handle->pid = 0; + + g_free(ns_handle); +} + +int +nmtstp_namespace_get_fd_for_process(pid_t pid, const char *ns_name) +{ + char p[1000]; + + g_return_val_if_fail(pid > 0, 0); + g_return_val_if_fail(ns_name && ns_name[0] && strlen(ns_name) < 50, 0); + + nm_sprintf_buf(p, "/proc/%lu/ns/%s", (unsigned long) pid, ns_name); + + return open(p, O_RDONLY | O_CLOEXEC); +} + +/*****************************************************************************/ + +void +nmtstp_netns_select_random(NMPlatform **platforms, gsize n_platforms, NMPNetns **netns) +{ + int i; + + g_assert(platforms); + g_assert(n_platforms && n_platforms <= G_MAXINT32); + g_assert(netns && !*netns); + for (i = 0; i < n_platforms; i++) + g_assert(NM_IS_PLATFORM(platforms[i])); + + i = nmtst_get_rand_uint32() % (n_platforms + 1); + if (i == 0) + return; + g_assert(nm_platform_netns_push(platforms[i - 1], netns)); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +static gboolean +unshare_user(void) +{ + FILE *f; + uid_t uid = geteuid(); + gid_t gid = getegid(); + + /* Already a root? */ + if (gid == 0 && uid == 0) + return TRUE; + + /* Become a root in new user NS. */ + if (unshare(CLONE_NEWUSER) != 0) + return FALSE; + + /* Since Linux 3.19 we have to disable setgroups() in order to map users. + * Just proceed if the file is not there. */ + f = fopen("/proc/self/setgroups", "we"); + if (f) { + fprintf(f, "deny"); + fclose(f); + } + + /* Map current UID to root in NS to be created. */ + f = fopen("/proc/self/uid_map", "we"); + if (!f) + return FALSE; + fprintf(f, "0 %d 1", uid); + fclose(f); + + /* Map current GID to root in NS to be created. */ + f = fopen("/proc/self/gid_map", "we"); + if (!f) + return FALSE; + fprintf(f, "0 %d 1", gid); + fclose(f); + + return TRUE; +} + +int +main(int argc, char **argv) +{ + int result; + const char *program = *argv; + + _nmtstp_init_tests(&argc, &argv); + + if (nmtstp_is_root_test() && (geteuid() != 0 || getegid() != 0)) { + if (g_getenv("NMTST_FORCE_REAL_ROOT") || !unshare_user()) { + /* Try to exec as sudo, this function does not return, if a sudo-cmd is set. */ + nmtst_reexec_sudo(); + +#ifdef REQUIRE_ROOT_TESTS + g_print("Fail test: requires root privileges (%s)\n", program); + return EXIT_FAILURE; +#else + g_print("Skipping test: requires root privileges (%s)\n", program); + return g_test_run(); +#endif + } + } + + if (nmtstp_is_root_test() && !g_getenv("NMTST_NO_UNSHARE")) { + int errsv; + + if (unshare(CLONE_NEWNET | CLONE_NEWNS) != 0) { + errsv = errno; + g_error("unshare(CLONE_NEWNET|CLONE_NEWNS) failed with %s (%d)", + nm_strerror_native(errsv), + errsv); + } + + /* We need a read-only /sys so that the platform knows there's no udev. */ + mount(NULL, "/sys", "sysfs", MS_SLAVE, NULL); + if (mount("sys", "/sys", "sysfs", MS_RDONLY, NULL) != 0) { + errsv = errno; + g_error("mount(\"/sys\") failed with %s (%d)", nm_strerror_native(errsv), errsv); + } + } + + nmtstp_setup_platform(); + + _nmtstp_setup_tests(); + + result = g_test_run(); + + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE); + + 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 = NULL; + 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/core/platform/tests/test-common.h b/src/core/platform/tests/test-common.h new file mode 100644 index 00000000..618645fe --- /dev/null +++ b/src/core/platform/tests/test-common.h @@ -0,0 +1,619 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 - 2017 Red Hat, Inc. + */ + +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <arpa/inet.h> +#include <linux/if.h> +#include <linux/if_link.h> +#include <linux/ip6_tunnel.h> + +#include "platform/nm-platform.h" +#include "platform/nmp-object.h" +#include "platform/nm-fake-platform.h" +#include "platform/nm-linux-platform.h" + +#include "nm-test-utils-core.h" + +#define DEVICE_NAME "nm-test-device" + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "platform-test" +#define _NMLOG_DOMAIN LOGD_PLATFORM +#define _NMLOG(level, ...) _LOG(level, _NMLOG_DOMAIN, __VA_ARGS__) + +#define _LOG(level, domain, ...) \ + G_STMT_START \ + { \ + const NMLogLevel __level = (level); \ + const NMLogDomain __domain = (domain); \ + \ + if (nm_logging_enabled(__level, __domain)) { \ + gint64 _ts = nm_utils_get_monotonic_timestamp_nsec(); \ + \ + _nm_log(__level, \ + __domain, \ + 0, \ + NULL, \ + NULL, \ + "%s[%ld.%09ld]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + (long) (_ts / NM_UTILS_NSEC_PER_SEC), \ + (long) (_ts % NM_UTILS_NSEC_PER_SEC) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +/*****************************************************************************/ + +gboolean nmtstp_is_root_test(void); +gboolean nmtstp_is_sysfs_writable(void); + +/*****************************************************************************/ + +typedef struct _NMTstpNamespaceHandle NMTstpNamespaceHandle; + +NMTstpNamespaceHandle *nmtstp_namespace_create(int flags, GError **error); + +void nmtstp_namespace_handle_release(NMTstpNamespaceHandle *handle); +pid_t nmtstp_namespace_handle_get_pid(NMTstpNamespaceHandle *handle); + +int nmtstp_namespace_get_fd_for_process(pid_t pid, const char *ns_name); + +/*****************************************************************************/ + +void nmtstp_netns_select_random(NMPlatform **platforms, gsize n_platforms, NMPNetns **netns); + +/*****************************************************************************/ + +typedef struct { + gulong handler_id; + const char * name; + NMPlatformSignalChangeType change_type; + int received_count; + GMainLoop * loop; + int ifindex; + const char * ifname; +} SignalData; + +SignalData *add_signal_full(const char * name, + NMPlatformSignalChangeType change_type, + GCallback callback, + int ifindex, + const char * ifname); +#define add_signal(name, change_type, callback) \ + add_signal_full(name, change_type, (GCallback) callback, 0, NULL) +#define add_signal_ifindex(name, change_type, callback, ifindex) \ + add_signal_full(name, change_type, (GCallback) callback, ifindex, NULL) +#define add_signal_ifname(name, change_type, callback, ifname) \ + add_signal_full(name, change_type, (GCallback) callback, 0, ifname) +void _accept_signal(const char *file, int line, const char *func, SignalData *data); +void +_accept_signals(const char *file, int line, const char *func, SignalData *data, int min, int max); +void _wait_signal(const char *file, int line, const char *func, SignalData *data); +void _accept_or_wait_signal(const char *file, int line, const char *func, SignalData *data); +void _ensure_no_signal(const char *file, int line, const char *func, SignalData *data); +void _free_signal(const char *file, int line, const char *func, SignalData *data); +#define accept_signal(data) _accept_signal(__FILE__, __LINE__, G_STRFUNC, data) +#define accept_signals(data, min, max) \ + _accept_signals(__FILE__, __LINE__, G_STRFUNC, data, min, max) +#define wait_signal(data) _wait_signal(__FILE__, __LINE__, G_STRFUNC, data) +#define accept_or_wait_signal(data) _accept_or_wait_signal(__FILE__, __LINE__, G_STRFUNC, data) +#define ensure_no_signal(data) _ensure_no_signal(__FILE__, __LINE__, G_STRFUNC, data) +#define free_signal(data) _free_signal(__FILE__, __LINE__, G_STRFUNC, data) + +void link_callback(NMPlatform * platform, + int obj_type_i, + int ifindex, + NMPlatformLink *received, + int change_type_i, + SignalData * data); + +/*****************************************************************************/ + +int nmtstp_run_command(const char *format, ...) _nm_printf(1, 2); +#define nmtstp_run_command_check(...) \ + do { \ + g_assert_cmpint(nmtstp_run_command(__VA_ARGS__), ==, 0); \ + } while (0) + +/*****************************************************************************/ + +guint nmtstp_wait_for_signal(NMPlatform *platform, gint64 timeout_msec); +guint nmtstp_wait_for_signal_until(NMPlatform *platform, gint64 until_ms); +const NMPlatformLink *nmtstp_wait_for_link(NMPlatform *platform, + const char *ifname, + NMLinkType expected_link_type, + gint64 timeout_msec); +const NMPlatformLink *nmtstp_wait_for_link_until(NMPlatform *platform, + const char *ifname, + NMLinkType expected_link_type, + gint64 until_ms); + +#define nmtstp_assert_wait_for_signal(platform, timeout_msec) \ + G_STMT_START \ + { \ + if (nmtstp_wait_for_signal(platform, timeout_msec) == 0) \ + g_assert_not_reached(); \ + } \ + G_STMT_END + +#define nmtstp_assert_wait_for_signal_until(platform, until_ms) \ + G_STMT_START \ + { \ + if (nmtstp_wait_for_signal_until(platform, until_ms) == 0) \ + g_assert_not_reached(); \ + } \ + G_STMT_END + +#define nmtstp_assert_wait_for_link(platform, ifname, expected_link_type, timeout_msec) \ + nmtst_assert_nonnull(nmtstp_wait_for_link(platform, ifname, expected_link_type, timeout_msec)) + +#define nmtstp_assert_wait_for_link_until(platform, ifname, expected_link_type, until_ms) \ + nmtst_assert_nonnull(nmtstp_wait_for_link_until(platform, ifname, expected_link_type, until_ms)) + +/*****************************************************************************/ + +int nmtstp_run_command_check_external_global(void); +gboolean nmtstp_run_command_check_external(int external_command); + +/*****************************************************************************/ + +const NMPlatformIP4Route *_nmtstp_assert_ip4_route_exists(const char *file, + guint line, + const char *func, + NMPlatform *platform, + int c_exists, + const char *ifname, + guint32 network, + int plen, + guint32 metric, + guint8 tos); +#define nmtstp_assert_ip4_route_exists(platform, c_exists, ifname, network, plen, metric, tos) \ + _nmtstp_assert_ip4_route_exists(__FILE__, \ + __LINE__, \ + G_STRFUNC, \ + platform, \ + c_exists, \ + ifname, \ + network, \ + plen, \ + metric, \ + tos) + +const NMPlatformIP4Route *nmtstp_ip4_route_get(NMPlatform *platform, + int ifindex, + guint32 network, + int plen, + guint32 metric, + guint8 tos); + +const NMPlatformIP6Route *_nmtstp_assert_ip6_route_exists(const char * file, + guint line, + const char * func, + NMPlatform * platform, + int c_exists, + const char * ifname, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen); +#define nmtstp_assert_ip6_route_exists(platform, \ + c_exists, \ + ifname, \ + network, \ + plen, \ + metric, \ + src, \ + src_plen) \ + _nmtstp_assert_ip6_route_exists(__FILE__, \ + __LINE__, \ + G_STRFUNC, \ + platform, \ + c_exists, \ + ifname, \ + network, \ + plen, \ + metric, \ + src, \ + src_plen) + +const NMPlatformIP6Route *nmtstp_ip6_route_get(NMPlatform * platform, + int ifindex, + const struct in6_addr *network, + guint plen, + guint32 metric, + const struct in6_addr *src, + guint8 src_plen); + +/*****************************************************************************/ + +gboolean nmtstp_ip_address_check_lifetime(const NMPlatformIPAddress *addr, + gint64 now, + guint32 expected_lifetime, + guint32 expected_preferred); +void nmtstp_ip_address_assert_lifetime(const NMPlatformIPAddress *addr, + gint64 now, + guint32 expected_lifetime, + guint32 expected_preferred); + +void nmtstp_ip4_address_add(NMPlatform *platform, + gboolean external_command, + int ifindex, + in_addr_t address, + int plen, + in_addr_t peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags, + const char *label); +void nmtstp_ip6_address_add(NMPlatform * platform, + gboolean external_command, + int ifindex, + struct in6_addr address, + int plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred, + guint32 flags); +void nmtstp_ip4_address_del(NMPlatform *platform, + gboolean external_command, + int ifindex, + in_addr_t address, + int plen, + in_addr_t peer_address); +void nmtstp_ip6_address_del(NMPlatform * platform, + gboolean external_command, + int ifindex, + struct in6_addr address, + int plen); + +void nmtstp_ip4_route_add(NMPlatform * platform, + int ifindex, + NMIPConfigSource source, + in_addr_t network, + guint8 plen, + in_addr_t gateway, + in_addr_t pref_src, + guint32 metric, + guint32 mss); + +void nmtstp_ip6_route_add(NMPlatform * platform, + int ifindex, + NMIPConfigSource source, + struct in6_addr network, + guint8 plen, + struct in6_addr gateway, + struct in6_addr pref_src, + guint32 metric, + guint32 mss); + +static inline GPtrArray * +nmtstp_ip4_route_get_all(NMPlatform *platform, int ifindex) +{ + return nm_platform_lookup_object_clone( + platform, + NMP_OBJECT_TYPE_IP4_ROUTE, + ifindex, + nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel, + NULL); +} + +static inline GPtrArray * +nmtstp_ip6_route_get_all(NMPlatform *platform, int ifindex) +{ + return nm_platform_lookup_object_clone( + platform, + NMP_OBJECT_TYPE_IP6_ROUTE, + ifindex, + nm_platform_lookup_predicate_routes_main_skip_rtprot_kernel, + NULL); +} + +/*****************************************************************************/ + +GArray *nmtstp_platform_ip4_address_get_all(NMPlatform *self, int ifindex); +GArray *nmtstp_platform_ip6_address_get_all(NMPlatform *self, int ifindex); + +/*****************************************************************************/ + +const NMPlatformIPAddress * +nmtstp_platform_ip_address_find(NMPlatform *self, int ifindex, int addr_family, gconstpointer addr); + +static inline const NMPlatformIP4Address * +nmtstp_platform_ip4_address_find(NMPlatform *self, int ifindex, in_addr_t addr) +{ + return (const NMPlatformIP4Address *) + nmtstp_platform_ip_address_find(self, ifindex, AF_INET, &addr); +} + +static inline const NMPlatformIP6Address * +nmtstp_platform_ip6_address_find(NMPlatform *self, int ifindex, const struct in6_addr *addr) +{ + return (const NMPlatformIP6Address *) + nmtstp_platform_ip_address_find(self, ifindex, AF_INET6, addr); +} + +void _nmtstp_platform_ip_addresses_assert(const char * filename, + int lineno, + NMPlatform * self, + int ifindex, + gboolean force_exact_4, + gboolean force_exact_6, + gboolean ignore_ll6, + guint addrs_len, + const char *const *addrs); + +#define nmtstp_platform_ip_addresses_assert(self, \ + ifindex, \ + force_exact_4, \ + force_exact_6, \ + ignore_ll6, \ + ...) \ + _nmtstp_platform_ip_addresses_assert(__FILE__, \ + __LINE__, \ + (self), \ + (ifindex), \ + (force_exact_4), \ + (force_exact_6), \ + (ignore_ll6), \ + NM_NARG(__VA_ARGS__), \ + ((const char *const[]){"dummy", ##__VA_ARGS__, NULL}) \ + + 1) + +/*****************************************************************************/ + +static inline gboolean +_nmtstp_platform_routing_rules_get_all_predicate(const NMPObject *obj, gpointer user_data) +{ + int addr_family = GPOINTER_TO_INT(user_data); + + g_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_ROUTING_RULE); + + return addr_family == AF_UNSPEC + || NMP_OBJECT_CAST_ROUTING_RULE(obj)->addr_family == addr_family; +} + +static inline GPtrArray * +nmtstp_platform_routing_rules_get_all(NMPlatform *platform, int addr_family) +{ + NMPLookup lookup; + + g_assert(NM_IS_PLATFORM(platform)); + g_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + return nm_platform_lookup_clone(platform, + &lookup, + _nmtstp_platform_routing_rules_get_all_predicate, + GINT_TO_POINTER(addr_family)); +} + +static inline guint +nmtstp_platform_routing_rules_get_count(NMPlatform *platform, int addr_family) +{ + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * obj; + NMPLookup lookup; + guint n; + + g_assert(NM_IS_PLATFORM(platform)); + g_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6)); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + head_entry = nm_platform_lookup(platform, &lookup); + + n = 0; + nmp_cache_iter_for_each (&iter, head_entry, &obj) { + if (_nmtstp_platform_routing_rules_get_all_predicate(obj, GINT_TO_POINTER(addr_family))) + n++; + } + return n; +} + +gboolean nmtstp_platform_ip4_route_delete(NMPlatform *platform, + int ifindex, + in_addr_t network, + guint8 plen, + guint32 metric); +gboolean nmtstp_platform_ip6_route_delete(NMPlatform * platform, + int ifindex, + struct in6_addr network, + guint8 plen, + guint32 metric); + +const NMPlatformLink * +nmtstp_link_get_typed(NMPlatform *platform, int ifindex, const char *name, NMLinkType link_type); +const NMPlatformLink *nmtstp_link_get(NMPlatform *platform, int ifindex, const char *name); + +gboolean nmtstp_kernel_support_get(NMPlatformKernelSupportType type); + +void +nmtstp_link_set_updown(NMPlatform *platform, gboolean external_command, int ifindex, gboolean up); + +const NMPlatformLink *nmtstp_link_bridge_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkBridge *lnk); +const NMPlatformLink *nmtstp_link_veth_add(NMPlatform *platform, + gboolean external_command, + const char *name, + const char *peer); +const NMPlatformLink * +nmtstp_link_dummy_add(NMPlatform *platform, gboolean external_command, const char *name); +const NMPlatformLink *nmtstp_link_gre_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkGre *lnk); +const NMPlatformLink *nmtstp_link_ip6tnl_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIp6Tnl *lnk); +const NMPlatformLink *nmtstp_link_ip6gre_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIp6Tnl *lnk); +const NMPlatformLink *nmtstp_link_ipip_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkIpIp *lnk); +const NMPlatformLink *nmtstp_link_macvlan_add(NMPlatform * platform, + gboolean external_command, + const char * name, + int parent, + const NMPlatformLnkMacvlan *lnk); +const NMPlatformLink *nmtstp_link_sit_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkSit *lnk); +const NMPlatformLink *nmtstp_link_tun_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkTun *lnk, + int * out_fd); +const NMPlatformLink *nmtstp_link_vrf_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkVrf *lnk, + gboolean * out_not_supported); +const NMPlatformLink *nmtstp_link_vxlan_add(NMPlatform * platform, + gboolean external_command, + const char * name, + const NMPlatformLnkVxlan *lnk); + +void nmtstp_link_delete(NMPlatform *platform, + gboolean external_command, + int ifindex, + const char *name, + gboolean require_exist); + +/*****************************************************************************/ + +extern int NMTSTP_ENV1_IFINDEX; +extern int NMTSTP_ENV1_EX; + +static inline void +_nmtstp_env1_wrapper_setup(const NmtstTestData *test_data) +{ + int * p_ifindex; + gpointer p_ifup; + + nmtst_test_data_unpack(test_data, &p_ifindex, NULL, NULL, NULL, &p_ifup); + + g_assert(p_ifindex && *p_ifindex == -1); + + _LOGT("TEST[%s]: setup", test_data->testpath); + + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE); + + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(NM_PLATFORM_GET, DEVICE_NAME, NULL))); + + *p_ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert_cmpint(*p_ifindex, >, 0); + g_assert_cmpint(NMTSTP_ENV1_IFINDEX, ==, -1); + + if (GPOINTER_TO_INT(p_ifup)) + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, *p_ifindex, NULL)); + + nm_platform_process_events(NM_PLATFORM_GET); + + NMTSTP_ENV1_IFINDEX = *p_ifindex; + NMTSTP_ENV1_EX = nmtstp_run_command_check_external_global(); +} + +static inline void +_nmtstp_env1_wrapper_run(gconstpointer user_data) +{ + const NmtstTestData *test_data = user_data; + GTestDataFunc test_func_data; + GTestFunc test_func; + gconstpointer d; + + nmtst_test_data_unpack(test_data, NULL, &test_func, &test_func_data, &d, NULL); + + _LOGT("TEST[%s]: run", test_data->testpath); + if (test_func) + test_func(); + else + test_func_data(d); +} + +static inline void +_nmtstp_env1_wrapper_teardown(const NmtstTestData *test_data) +{ + int *p_ifindex; + + nmtst_test_data_unpack(test_data, &p_ifindex, NULL, NULL, NULL, NULL); + + g_assert_cmpint(NMTSTP_ENV1_IFINDEX, ==, *p_ifindex); + NMTSTP_ENV1_IFINDEX = -1; + + _LOGT("TEST[%s]: teardown", test_data->testpath); + + g_assert_cmpint(*p_ifindex, ==, nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME)); + g_assert(nm_platform_link_delete(NM_PLATFORM_GET, *p_ifindex)); + + nm_platform_process_events(NM_PLATFORM_GET); + + _LOGT("TEST[%s]: finished", test_data->testpath); + + *p_ifindex = -1; +} + +/* add test function, that set's up a particular environment, consisting + * of a dummy device with ifindex NMTSTP_ENV1_IFINDEX. */ +#define _nmtstp_env1_add_test_func_full(testpath, test_func, test_data_func, arg, ifup) \ + nmtst_add_test_func_full(testpath, \ + _nmtstp_env1_wrapper_run, \ + _nmtstp_env1_wrapper_setup, \ + _nmtstp_env1_wrapper_teardown, \ + ({ \ + static int _ifindex = -1; \ + &_ifindex; \ + }), \ + ({ \ + GTestFunc _test_func = (test_func); \ + _test_func; \ + }), \ + ({ \ + GTestDataFunc _test_func = (test_data_func); \ + _test_func; \ + }), \ + (arg), \ + ({ \ + gboolean _ifup = (ifup); \ + GINT_TO_POINTER(_ifup); \ + })) + +#define nmtstp_env1_add_test_func_data(testpath, test_func, arg, ifup) \ + _nmtstp_env1_add_test_func_full(testpath, NULL, test_func, arg, ifup) + +#define nmtstp_env1_add_test_func(testpath, test_func, ifup) \ + _nmtstp_env1_add_test_func_full(testpath, test_func, NULL, NULL, ifup) + +/*****************************************************************************/ + +typedef void (*NMTstpSetupFunc)(void); +extern NMTstpSetupFunc const _nmtstp_setup_platform_func; + +void nmtstp_setup_platform(void); + +/*****************************************************************************/ + +typedef struct _NMTstpAcdDefender NMTstpAcdDefender; + +NMTstpAcdDefender * +nmtstp_acd_defender_new(int ifindex, in_addr_t ip_addr, const NMEtherAddr *mac_addr); + +void nmtstp_acd_defender_destroy(NMTstpAcdDefender *defender); + +/*****************************************************************************/ + +void _nmtstp_init_tests(int *argc, char ***argv); +void _nmtstp_setup_tests(void); diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c new file mode 100644 index 00000000..3aaa3759 --- /dev/null +++ b/src/core/platform/tests/test-link.c @@ -0,0 +1,3823 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include <sched.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <linux/if_tun.h> + +#include "nm-glib-aux/nm-io-utils.h" +#include "nm-base/nm-ethtool-base.h" +#include "platform/nmp-object.h" +#include "nm-platform/nmp-netns.h" +#include "nm-platform/nm-platform-utils.h" + +#include "test-common.h" +#include "nm-test-utils-core.h" + +#define LO_INDEX 1 +#define LO_NAME "lo" +#define LO_TYPEDESC "loopback" + +#define DUMMY_TYPEDESC "dummy" +#define BOGUS_NAME "nm-bogus-device" +#define BOGUS_IFINDEX INT_MAX +#define SLAVE_NAME "nm-test-slave" +#define PARENT_NAME "nm-test-parent" +#define VLAN_ID 4077 +#define VLAN_FLAGS 0 +#define MTU 1357 + +#define _ADD_DUMMY(platform, name) \ + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add((platform), (name), NULL))) + +static void +test_bogus(void) +{ + size_t addrlen; + + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, BOGUS_NAME)); + g_assert(!nm_platform_link_delete(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_get_ifindex(NM_PLATFORM_GET, BOGUS_NAME)); + g_assert(!nm_platform_link_get_name(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_get_type(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_get_type_name(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_set_up(NM_PLATFORM_GET, BOGUS_IFINDEX, NULL)); + + g_assert(!nm_platform_link_set_down(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_set_arp(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_set_noarp(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_get_address(NM_PLATFORM_GET, BOGUS_IFINDEX, &addrlen)); + g_assert(!addrlen); + g_assert(!nm_platform_link_get_address(NM_PLATFORM_GET, BOGUS_IFINDEX, NULL)); + + g_assert(!NMTST_NM_ERR_SUCCESS(nm_platform_link_set_mtu(NM_PLATFORM_GET, BOGUS_IFINDEX, MTU))); + + g_assert(!nm_platform_link_get_mtu(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_supports_carrier_detect(NM_PLATFORM_GET, BOGUS_IFINDEX)); + g_assert(!nm_platform_link_supports_vlans(NM_PLATFORM_GET, BOGUS_IFINDEX)); + + g_assert(!nm_platform_link_get_lnk_vlan(NM_PLATFORM_GET, BOGUS_IFINDEX, NULL)); + g_assert(!nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0)); + g_assert(!nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, BOGUS_IFINDEX, 0, 0)); +} + +static void +test_loopback(void) +{ + g_assert(nm_platform_link_get_by_ifname(NM_PLATFORM_GET, LO_NAME)); + g_assert_cmpint(nm_platform_link_get_type(NM_PLATFORM_GET, LO_INDEX), + ==, + NM_LINK_TYPE_LOOPBACK); + g_assert_cmpint(nm_platform_link_get_ifindex(NM_PLATFORM_GET, LO_NAME), ==, LO_INDEX); + g_assert_cmpstr(nm_platform_link_get_name(NM_PLATFORM_GET, LO_INDEX), ==, LO_NAME); + g_assert_cmpstr(nm_platform_link_get_type_name(NM_PLATFORM_GET, LO_INDEX), ==, LO_TYPEDESC); + + g_assert(nm_platform_link_supports_carrier_detect(NM_PLATFORM_GET, LO_INDEX)); + g_assert(!nm_platform_link_supports_vlans(NM_PLATFORM_GET, LO_INDEX)); +} + +static gboolean +software_add(NMLinkType link_type, const char *name) +{ + switch (link_type) { + case NM_LINK_TYPE_DUMMY: + return NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(NM_PLATFORM_GET, name, NULL)); + case NM_LINK_TYPE_BRIDGE: + return NMTST_NM_ERR_SUCCESS(nm_platform_link_bridge_add(NM_PLATFORM_GET, + name, + NULL, + 0, + 0, + &nm_platform_lnk_bridge_default, + NULL)); + case NM_LINK_TYPE_BOND: + { + gboolean bond0_exists = !!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "bond0"); + int r; + + r = nm_platform_link_bond_add(NM_PLATFORM_GET, name, NULL); + + /* Check that bond0 is *not* automatically created. */ + if (!bond0_exists) + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "bond0")); + return r >= 0; + } + case NM_LINK_TYPE_TEAM: + return NMTST_NM_ERR_SUCCESS(nm_platform_link_team_add(NM_PLATFORM_GET, name, NULL)); + case NM_LINK_TYPE_VLAN: + { + SignalData *parent_added; + SignalData *parent_changed; + + /* Don't call link_callback for the bridge interface */ + parent_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + PARENT_NAME); + if (NMTST_NM_ERR_SUCCESS(nm_platform_link_bridge_add(NM_PLATFORM_GET, + PARENT_NAME, + NULL, + 0, + 0, + &nm_platform_lnk_bridge_default, + NULL))) + accept_signal(parent_added); + free_signal(parent_added); + + { + int parent_ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, PARENT_NAME); + gboolean was_up = nm_platform_link_is_up(NM_PLATFORM_GET, parent_ifindex); + + parent_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + link_callback, + parent_ifindex); + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, parent_ifindex, NULL)); + if (was_up) { + /* when NM is running in the background, it will mess with addrgenmode which might cause additional signals. */ + accept_signals(parent_changed, 0, 1); + } else + accept_signals(parent_changed, 1, 2); + free_signal(parent_changed); + + return NMTST_NM_ERR_SUCCESS( + nm_platform_link_vlan_add(NM_PLATFORM_GET, name, parent_ifindex, VLAN_ID, 0, NULL)); + } + } + default: + g_error("Link type %d unhandled.", link_type); + } + g_assert_not_reached(); +} + +static void +test_link_changed_signal_cb(NMPlatform * platform, + int obj_type_i, + int ifindex, + const NMPlatformIP4Route *route, + int change_type_i, + gboolean * p_test_link_changed_signal_arg) +{ + const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; + + /* test invocation of platform signals with multiple listeners + * connected to the signal. Platform signals have enum-typed + * arguments and there seem to be an issue with invoking such + * signals on s390x and ppc64 archs. + * https://bugzilla.redhat.com/show_bug.cgi?id=1260577 + * + * As the test shows, the failure is not reproducible for + * platform signals. + */ + g_assert(NM_IS_PLATFORM(platform)); + g_assert(platform == NM_PLATFORM_GET); + + g_assert(ifindex > 0); + g_assert(route); + + g_assert_cmpint(obj_type, ==, NMP_OBJECT_TYPE_LINK); + + g_assert_cmpint((gint64) change_type, !=, (gint64) 0); + g_assert_cmpint(change_type, !=, NM_PLATFORM_SIGNAL_NONE); + + *p_test_link_changed_signal_arg = TRUE; +} + +static void +test_slave(int master, int type, SignalData *master_changed) +{ + int ifindex; + SignalData *link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + SLAVE_NAME); + SignalData *link_changed, *link_removed; + char * value; + NMLinkType link_type = nm_platform_link_get_type(NM_PLATFORM_GET, master); + gboolean test_link_changed_signal_arg1; + gboolean test_link_changed_signal_arg2; + + g_assert(NM_IN_SET(link_type, NM_LINK_TYPE_TEAM, NM_LINK_TYPE_BOND, NM_LINK_TYPE_BRIDGE)); + + g_assert(software_add(type, SLAVE_NAME)); + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, SLAVE_NAME); + g_assert(ifindex > 0); + link_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + link_callback, + ifindex); + link_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + link_callback, + ifindex); + accept_signal(link_added); + + /* Set the slave up to see whether master's IFF_LOWER_UP is set correctly. + * + * See https://bugzilla.redhat.com/show_bug.cgi?id=910348 + */ + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_set_down(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + ensure_no_signal(link_changed); + + /* Enslave */ + link_changed->ifindex = ifindex; + g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, master, ifindex)); + g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex), ==, master); + + accept_signals(link_changed, 1, 3); + accept_signals(master_changed, 0, 2); + + /* enslaveing brings put the slave */ + if (NM_IN_SET(link_type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM)) + g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + else + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + + test_link_changed_signal_arg1 = FALSE; + test_link_changed_signal_arg2 = FALSE; + g_signal_connect(NM_PLATFORM_GET, + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(test_link_changed_signal_cb), + &test_link_changed_signal_arg1); + g_signal_connect(NM_PLATFORM_GET, + NM_PLATFORM_SIGNAL_LINK_CHANGED, + G_CALLBACK(test_link_changed_signal_cb), + &test_link_changed_signal_arg2); + + /* Set master up */ + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, master, NULL)); + g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, master)); + accept_signals(master_changed, 1, 3); + + g_signal_handlers_disconnect_by_func(NM_PLATFORM_GET, + G_CALLBACK(test_link_changed_signal_cb), + &test_link_changed_signal_arg1); + g_signal_handlers_disconnect_by_func(NM_PLATFORM_GET, + G_CALLBACK(test_link_changed_signal_cb), + &test_link_changed_signal_arg2); + g_assert(test_link_changed_signal_arg1); + g_assert(test_link_changed_signal_arg2); + + /* Master with a disconnected slave is disconnected + * + * For some reason, bonding and teaming slaves are automatically set up. We + * need to set them back down for this test. + */ + switch (nm_platform_link_get_type(NM_PLATFORM_GET, master)) { + case NM_LINK_TYPE_BOND: + case NM_LINK_TYPE_TEAM: + g_assert(nm_platform_link_set_down(NM_PLATFORM_GET, ifindex)); + accept_signal(link_changed); + accept_signals(master_changed, 0, 3); + break; + default: + break; + } + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + if (nmtstp_is_root_test() && nm_platform_link_is_connected(NM_PLATFORM_GET, master)) { + if (nm_platform_link_get_type(NM_PLATFORM_GET, master) == NM_LINK_TYPE_TEAM) { + /* Older team versions (e.g. Fedora 17) have a bug that team master stays + * IFF_LOWER_UP even if its slave is down. Double check it with iproute2 and if + * `ip link` also claims master to be up, accept it. */ + char *stdout_str = NULL; + + nmtst_spawn_sync(NULL, + &stdout_str, + NULL, + 0, + "/sbin/ip", + "link", + "show", + "dev", + nm_platform_link_get_name(NM_PLATFORM_GET, master)); + + g_assert(strstr(stdout_str, "LOWER_UP")); + g_free(stdout_str); + } else + g_assert_not_reached(); + } + + /* Set slave up and see if master gets up too */ + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL)); + g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, master)); + accept_signals(link_changed, 1, 3); + /* NM running, can cause additional change of addrgenmode */ + accept_signals(master_changed, 0, 3); + + /* Enslave again + * + * Gracefully succeed if already enslaved. + */ + ensure_no_signal(link_changed); + g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, master, ifindex)); + accept_signals(link_changed, 0, 2); + accept_signals(master_changed, 0, 2); + + /* Set slave option */ + switch (type) { + case NM_LINK_TYPE_BRIDGE: + if (nmtstp_is_sysfs_writable()) { + g_assert( + nm_platform_sysctl_slave_set_option(NM_PLATFORM_GET, ifindex, "priority", "614")); + value = nm_platform_sysctl_slave_get_option(NM_PLATFORM_GET, ifindex, "priority"); + g_assert_cmpstr(value, ==, "614"); + g_free(value); + } + break; + default: + break; + } + + /* Release */ + ensure_no_signal(link_added); + ensure_no_signal(link_changed); + ensure_no_signal(link_removed); + g_assert(nm_platform_link_release(NM_PLATFORM_GET, master, ifindex)); + g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex), ==, 0); + if (link_changed->received_count > 0) { + accept_signals(link_added, 0, 1); + accept_signals(link_changed, 1, 5); + accept_signals(link_removed, 0, 1); + } else { + /* Due to https://bugzilla.redhat.com/show_bug.cgi?id=1285719 , kernel might send a + * wrong RTM_DELLINK message so that we instead see an removed+added signal. */ + accept_signal(link_added); + ensure_no_signal(link_changed); + accept_signal(link_removed); + } + accept_signals(master_changed, 0, 3); + + ensure_no_signal(master_changed); + + /* Release again */ + ensure_no_signal(link_changed); + g_assert(!nm_platform_link_release(NM_PLATFORM_GET, master, ifindex)); + + ensure_no_signal(master_changed); + + /* Remove */ + ensure_no_signal(link_added); + ensure_no_signal(link_changed); + ensure_no_signal(link_removed); + nmtstp_link_delete(NULL, -1, ifindex, NULL, TRUE); + accept_signals(master_changed, 0, 1); + accept_signals(link_changed, 0, 1); + accept_signal(link_removed); + + free_signal(link_added); + free_signal(link_changed); + free_signal(link_removed); +} + +static void +test_software(NMLinkType link_type, const char *link_typename) +{ + int ifindex; + char *value; + int vlan_parent = -1, vlan_id; + + SignalData *link_added, *link_changed, *link_removed; + + /* Add */ + link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + DEVICE_NAME); + g_assert(software_add(link_type, DEVICE_NAME)); + accept_signal(link_added); + g_assert(nm_platform_link_get_by_ifname(NM_PLATFORM_GET, DEVICE_NAME)); + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert(ifindex >= 0); + g_assert_cmpint(nm_platform_link_get_type(NM_PLATFORM_GET, ifindex), ==, link_type); + g_assert_cmpstr(nm_platform_link_get_type_name(NM_PLATFORM_GET, ifindex), ==, link_typename); + link_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + link_callback, + ifindex); + link_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + link_callback, + ifindex); + if (link_type == NM_LINK_TYPE_VLAN) { + const NMPlatformLink * plink; + const NMPlatformLnkVlan *plnk; + + plnk = nm_platform_link_get_lnk_vlan(NM_PLATFORM_GET, ifindex, &plink); + g_assert(plnk); + g_assert(plink); + + vlan_parent = plink->parent; + vlan_id = plnk->id; + g_assert_cmpint(vlan_parent, + ==, + nm_platform_link_get_ifindex(NM_PLATFORM_GET, PARENT_NAME)); + g_assert_cmpint(vlan_id, ==, VLAN_ID); + } + + /* Add again */ + g_assert(!software_add(link_type, DEVICE_NAME)); + + /* Set ARP/NOARP */ + g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_set_noarp(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + accept_signals(link_changed, 1, 2); + g_assert(nm_platform_link_set_arp(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + accept_signal(link_changed); + + /* Set master option */ + if (nmtstp_is_root_test()) { + switch (link_type) { + case NM_LINK_TYPE_BRIDGE: + if (nmtstp_is_sysfs_writable()) { + g_assert(nm_platform_sysctl_master_set_option(NM_PLATFORM_GET, + ifindex, + "forward_delay", + "628")); + value = + nm_platform_sysctl_master_get_option(NM_PLATFORM_GET, ifindex, "forward_delay"); + g_assert_cmpstr(value, ==, "628"); + g_free(value); + } + break; + case NM_LINK_TYPE_BOND: + if (nmtstp_is_sysfs_writable()) { + g_assert(nm_platform_sysctl_master_set_option(NM_PLATFORM_GET, + ifindex, + "mode", + "active-backup")); + value = nm_platform_sysctl_master_get_option(NM_PLATFORM_GET, ifindex, "mode"); + /* When reading back, the output looks slightly different. */ + g_assert(g_str_has_prefix(value, "active-backup")); + g_free(value); + } + break; + default: + break; + } + } + + /* Enslave and release */ + switch (link_type) { + case NM_LINK_TYPE_BRIDGE: + case NM_LINK_TYPE_BOND: + case NM_LINK_TYPE_TEAM: + link_changed->ifindex = ifindex; + test_slave(ifindex, NM_LINK_TYPE_DUMMY, link_changed); + link_changed->ifindex = 0; + break; + default: + break; + } + free_signal(link_changed); + + /* Delete */ + nmtstp_link_delete(NULL, -1, ifindex, DEVICE_NAME, TRUE); + accept_signal(link_removed); + + /* Delete again */ + g_assert(nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME) <= 0); + g_assert(!nm_platform_link_delete(NM_PLATFORM_GET, ifindex)); + + /* VLAN: Delete parent */ + if (link_type == NM_LINK_TYPE_VLAN) { + SignalData *link_removed_parent = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + link_callback, + vlan_parent); + + nmtstp_link_delete(NULL, -1, vlan_parent, NULL, TRUE); + accept_signal(link_removed_parent); + free_signal(link_removed_parent); + } + + /* No pending signal */ + free_signal(link_added); + free_signal(link_removed); +} + +static void +test_bridge(void) +{ + test_software(NM_LINK_TYPE_BRIDGE, "bridge"); +} + +static int +_system(const char *cmd) +{ + /* some gcc version really want to warn on -Werror=unused-result. Add a bogus wrapper + * function. */ + return system(cmd); +} + +static void +test_bond(void) +{ + if (nmtstp_is_root_test() && !g_file_test("/proc/1/net/bonding", G_FILE_TEST_IS_DIR) + && _system("modprobe --show bonding") != 0) { + g_test_skip("Skipping test for bonding: bonding module not available"); + return; + } + + test_software(NM_LINK_TYPE_BOND, "bond"); +} + +static void +test_team(void) +{ + int r; + + if (nmtstp_is_root_test()) { + r = nm_platform_link_team_add(NM_PLATFORM_GET, "nm-team-check", NULL); + + if (r < 0) { + g_assert_cmpint(r, ==, -EOPNOTSUPP); + g_test_skip("Skipping test for teaming: team module not functioning"); + return; + } + + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, "nm-team-check", FALSE); + } + + test_software(NM_LINK_TYPE_TEAM, "team"); +} + +static void +test_vlan(void) +{ + test_software(NM_LINK_TYPE_VLAN, "vlan"); +} + +/*****************************************************************************/ + +static void +test_bridge_addr(void) +{ + char addr[ETH_ALEN]; + NMPlatformLink link; + const NMPlatformLink *plink = NULL; + + nm_utils_hwaddr_aton("de:ad:be:ef:00:11", addr, sizeof(addr)); + + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_bridge_add(NM_PLATFORM_GET, + DEVICE_NAME, + addr, + sizeof(addr), + 0, + &nm_platform_lnk_bridge_default, + &plink))); + g_assert(plink); + link = *plink; + g_assert_cmpstr(link.name, ==, DEVICE_NAME); + + g_assert_cmpint(link.l_address.len, ==, sizeof(addr)); + g_assert(!memcmp(link.l_address.data, addr, sizeof(addr))); + + plink = nm_platform_link_get(NM_PLATFORM_GET, link.ifindex); + g_assert(plink); + + if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) { + g_assert(!nm_platform_link_get_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex)); + g_assert_cmpint(_nm_platform_uint8_inv(plink->inet6_addr_gen_mode_inv), + ==, + NM_IN6_ADDR_GEN_MODE_EUI64); + + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_link_set_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex, TRUE))); + g_assert(nm_platform_link_get_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex)); + plink = nm_platform_link_get(NM_PLATFORM_GET, link.ifindex); + g_assert(plink); + g_assert_cmpint(_nm_platform_uint8_inv(plink->inet6_addr_gen_mode_inv), + ==, + NM_IN6_ADDR_GEN_MODE_NONE); + + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_link_set_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex, FALSE))); + g_assert(!nm_platform_link_get_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex)); + plink = nm_platform_link_get(NM_PLATFORM_GET, link.ifindex); + g_assert(plink); + g_assert_cmpint(_nm_platform_uint8_inv(plink->inet6_addr_gen_mode_inv), + ==, + NM_IN6_ADDR_GEN_MODE_EUI64); + } + + g_assert_cmpint(plink->l_address.len, ==, sizeof(addr)); + g_assert(!memcmp(plink->l_address.data, addr, sizeof(addr))); + + nmtstp_link_delete(NULL, -1, link.ifindex, link.name, TRUE); +} + +/*****************************************************************************/ + +static void +test_internal(void) +{ + SignalData *link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + DEVICE_NAME); + SignalData *link_changed, *link_removed; + const char mac[6] = {0x00, 0xff, 0x11, 0xee, 0x22, 0xdd}; + const char *address; + size_t addrlen; + int ifindex; + + /* Check the functions for non-existent devices */ + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, DEVICE_NAME)); + g_assert(!nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME)); + + /* Add device */ + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add(NM_PLATFORM_GET, DEVICE_NAME, NULL))); + accept_signal(link_added); + + /* Try to add again */ + g_assert(nm_platform_link_dummy_add(NM_PLATFORM_GET, DEVICE_NAME, NULL) == -NME_PL_EXISTS); + + /* Check device index, name and type */ + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert(ifindex > 0); + g_assert_cmpstr(nm_platform_link_get_name(NM_PLATFORM_GET, ifindex), ==, DEVICE_NAME); + g_assert_cmpint(nm_platform_link_get_type(NM_PLATFORM_GET, ifindex), ==, NM_LINK_TYPE_DUMMY); + g_assert_cmpstr(nm_platform_link_get_type_name(NM_PLATFORM_GET, ifindex), ==, DUMMY_TYPEDESC); + link_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + link_callback, + ifindex); + link_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + link_callback, + ifindex); + + /* Up/connected */ + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_set_up(NM_PLATFORM_GET, ifindex, NULL)); + g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + accept_signals(link_changed, 1, 2); + g_assert(nm_platform_link_set_down(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + accept_signal(link_changed); + + /* arp/noarp */ + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_set_arp(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + accept_signal(link_changed); + g_assert(nm_platform_link_set_noarp(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + accept_signal(link_changed); + + /* Features */ + g_assert(!nm_platform_link_supports_carrier_detect(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_supports_vlans(NM_PLATFORM_GET, ifindex)); + + /* Set MAC address */ + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_link_set_address(NM_PLATFORM_GET, ifindex, mac, sizeof(mac)))); + address = nm_platform_link_get_address(NM_PLATFORM_GET, ifindex, &addrlen); + g_assert(addrlen == sizeof(mac)); + g_assert(!memcmp(address, mac, addrlen)); + address = nm_platform_link_get_address(NM_PLATFORM_GET, ifindex, NULL); + g_assert(!memcmp(address, mac, addrlen)); + accept_signal(link_changed); + + /* Set MTU */ + g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_set_mtu(NM_PLATFORM_GET, ifindex, MTU))); + g_assert_cmpint(nm_platform_link_get_mtu(NM_PLATFORM_GET, ifindex), ==, MTU); + accept_signal(link_changed); + + /* Delete device */ + nmtstp_link_delete(NULL, -1, ifindex, DEVICE_NAME, TRUE); + accept_signal(link_removed); + + /* Try to delete again */ + g_assert(!nm_platform_link_delete(NM_PLATFORM_GET, ifindex)); + + free_signal(link_added); + free_signal(link_changed); + free_signal(link_removed); +} + +/*****************************************************************************/ + +static void +test_external(void) +{ + const NMPlatformLink *pllink; + SignalData * link_added, *link_changed, *link_removed; + int ifindex; + + link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + link_callback, + DEVICE_NAME); + + nmtstp_run_command_check("ip link add %s type %s", DEVICE_NAME, "dummy"); + wait_signal(link_added); + + g_assert(nm_platform_link_get_by_ifname(NM_PLATFORM_GET, DEVICE_NAME)); + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert(ifindex > 0); + g_assert_cmpstr(nm_platform_link_get_name(NM_PLATFORM_GET, ifindex), ==, DEVICE_NAME); + g_assert_cmpint(nm_platform_link_get_type(NM_PLATFORM_GET, ifindex), ==, NM_LINK_TYPE_DUMMY); + g_assert_cmpstr(nm_platform_link_get_type_name(NM_PLATFORM_GET, ifindex), ==, DUMMY_TYPEDESC); + link_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + link_callback, + ifindex); + link_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + link_callback, + ifindex); + + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex); + g_assert(pllink); + if (!pllink->initialized) { + /* we still lack the notification via UDEV. Expect another link changed signal. */ + wait_signal(link_changed); + } + + /* Up/connected/arp */ + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + + nmtstp_run_command_check("ip link set %s up", DEVICE_NAME); + wait_signal(link_changed); + + g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + nmtstp_run_command_check("ip link set %s down", DEVICE_NAME); + wait_signal(link_changed); + g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex)); + g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex)); + + nmtstp_run_command_check("ip link set %s arp on", DEVICE_NAME); + wait_signal(link_changed); + g_assert(nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + nmtstp_run_command_check("ip link set %s arp off", DEVICE_NAME); + wait_signal(link_changed); + g_assert(!nm_platform_link_uses_arp(NM_PLATFORM_GET, ifindex)); + + nmtstp_run_command_check("ip link del %s", DEVICE_NAME); + wait_signal(link_removed); + accept_signals(link_changed, 0, 1); + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, DEVICE_NAME)); + + free_signal(link_added); + free_signal(link_changed); + free_signal(link_removed); +} + +/*****************************************************************************/ + +static guint8 * +_copy_base64(guint8 *dst, gsize dst_len, const char *base64_src) +{ + g_assert(dst); + g_assert(dst_len > 0); + + if (!base64_src) + memset(dst, 0, dst_len); + else { + gs_free guint8 *b = NULL; + gsize l; + + b = g_base64_decode(base64_src, &l); + g_assert(b); + g_assert(l == dst_len); + + memcpy(dst, b, dst_len); + } + return dst; +} + +typedef struct { + const char *pri; + const char *pub; + const char *pre; +} KeyPair; + +static void +_test_wireguard_change(NMPlatform *platform, int ifindex, int test_mode) +{ + const KeyPair self_key = {"yOWEsaXFxX9/DOkQPzqB9RufZOpfSP4LZZCErP0N0Xo=", + "s6pVT2xPwktor9O5bVOSzcPqBu9uzQOUzPQHXLU2jmk="}; + const KeyPair keys[100] = { + {"+BDHMh11bkheGfvlQpqt8P/H7N1sPXtVi05XraZS0E8=", + "QItu7PJadBVXFXGv55CMtVnbRHdrI6E2CGlu2N5oGx4=", + "2IvZnKTzbF1UlChznWSsEYtGbPjhYSTT41GXO6zLxvk="}, + {"qGZyV2BO1nyY/FGYd6elBPirwJC9QyZwqbm2OJAgLkY=", + "v8L1FEitO0xo+wW/CVVUnALlw0zGveApSFdlITi/5lI=", + "/R2c0JmBNGJzT594NQ0mBJ2XJjxt2QUSo+ZiqeY0EQA="}, + {"YDgsIb0oe+9NcxIx2r0HEEPQpRMxmRN0ALoLm9Sh40Q=", + "nFPs1HaU7uFBvE9xZCMF8oOAjzLpZ49AzDHOluY1O2E=", + "zsYED2Ef7zIHJoRBPcen+w4ktrRsLPEfYwZhWIuXfds="}, + {"kHkosM503LWu43tdYXbNwVOpRrtPgd9XFqcN7k4t6G4=", + "b8e092WT+eNmnxCr5WE2QC/MXAjDagfG1g03cs2mBC0=", + "VXz0ShGWT7H0CBCg2awfatmOJF15ZtaSMPhMsp+hc3A="}, + {"4C2w5CEnxH59Y2aa6CJXgLdDtWoNMS2UJounRCM1Jkk=", + "gC/R9umlnEQL+Qsz/Y64AlsdMge4ECe5/u/JHZCMWSs=", + "2bmL5ISr+V5a7l7xFJ695BLIJyBgx8xnxzybkxiRHOg="}, + {"KHJSzFGkXcZf/wbH2rr99SYtGKWbL680wA2AcDE94lo=", + "BsN23h4aOi458Q3EgMQsodsWQxd9h/RxqskAUpsXfgg=", + "nK4Zv34YKEhjuexhq1SgK4oTd4MZJT5gcpYvEuPjc7Q="}, + {"QGMulXJ9e3AVxtpi8+UUVqPOr/YBWvCNFVsWS9IUnUA=", + "kjVclP5Ifi6og2BBCEHKS/aa/WktArB4+ig06lYaVlg=", + "0+mmceDPcSRK3vFnYqHd9iAfY+Nyjzf/1KgDeYGlRkQ="}, + {"AOJiDD4y6GA7P7gugjjQG9Cctvc5Y27fajHz6aU3gU4=", + "gEnHn6euHtcMEwZBlX6HANPeN9Of+voBDtltS38xDUw=", + "wIH1OxgX6GLxx/bnR+t3fbmjGZDTU3WMxp7t1XGezqM="}, + {"COsls2BlCltaIrrq1+FU51cWddlmoPPppSeIDunOxGA=", + "+n6WuV8Tb1/iZArTrHsyNqkRHABbavBQt9Me72K2KEc=", + "t4baiprSO9ZbKD2/RutOY9cr+yCajQWZGCTnQdrFQj0="}, + {"uHawQq2BRyJlsTPoCa+MfBVnv4MwtRoS+S9FEpFOEVg=", + "8lcmr27afeb6iI3BQCaDtvalF2Cl7gxRZgs+nyJ/fEg=", + "Eh9o/6W60iujBLIHuRfNrPhOWAn7PambT2JRln9iwA0="}, + {"yL7hmoE/JfRGAotRzx9xOpjfrDA3BFlPEemFiQw40Wk=", + "BHK0PHi5kp7rOfQ46oc9xlVpB+pZeZYXTtH7EXr5TwU=", + "BS2h2ZZyW0BlYMmLR29xyHEcZ4jtO7rgj1jkz/EEaxU="}, + {"ON8YrTHQgoC5e0mAR9FakZ8hZ/9I7ysuE21sG546J1Y=", + "Bm3l5I6iH1tDrv6EgdZU9PzHqp0H26Z6ggmmIypwxy8=", + "qKVfbCnK1EI3eC28SsL+jyBnEnIF/nRJQJzDevBFdNQ="}, + {"KGLO0RI0VlQFCG+ETUqk25HdaKUnUPXKOKkTh/w8BXU=", + "sBDBwQFC7k8UMdMzuavKBkBjYYKigzvYmGF5rm6muQc=", + "BNfZF9d7540pUGGOQCh3iDxAZcPBqX4qqM00GSLPBek="}, + {"KGdWyEodIn7KKI2e4EFK9tT6Bt3bNMFBRFVTKjjJm2E=", + "lrbjU/Xn9wDCZQiU8E5fY6igTSozghvo47QeVIIWaUk=", + "LczqW48MW8qDQIZYRELsz/VCzVDc5niROvk7OqrTxR0="}, + {"wO3xinUGABgEmX+RJZbBbOtAmBuPPFG6oVdimvoo90w=", + "dCIvTzR6EerOOsRKnWly1a9WGzbJ4qc+6t3SSzLgWFk=", + "wFj0zpr5PadBoBy0couLuZ1qudZbXLbV/j3UT+AyKeo="}, + {"+JNOBlO4tp9vvQk6UO4r3sILyEgjl+BBoWketZufyn0=", + "Q6LSv9y7YQkJEzQ/1mpjJEgOrO8GYPUTgcizjh7Cm34=", + "kg7AG9MuN04xPJ5Z0IcNZ4a8d1b/n4GsGIeyA4FaaSE="}, + {"+EJcThLRwjZ+h1CNNFu15HWzznf4u/lPVw8hifTm2Ec=", + "Kkn2jFqwBzyIFQfD0OpmePFSmBYmhKagv4pGgkqsWgE=", + "jYpxojj8WKYe/XIXMP+uv1Fv0+TKrs83tqfzP0AGdcI="}, + {"+DKFqSMNFmxriEFj3qatuzYeTJ9+xWYspZ4ydL3eC0Q=", + "3o37bsg6HhRg/M9+fTlLcFYc2w/Bz9rLQySvvYCKbRE=", + "Jb9qoDIBat1EexlgfpRbXa7OflptME8/zt93bldkiVE="}, + {"EH3MjFOMqRoDFQz+hSlJpntWBeH3lTk6WPjTIQjr42o=", + "PbPewED/nxSBLdM7AXMj7uS3bCgAAg8M6F4iPLd0b1U=", + "pj4+UgOGkpJwlRvX5BRXZzmzAUnDtUtJsS7LzbcJWzw="}, + {"kL6M2KvO+vPBLEc/a0DpEHTibQ1bwaMRT9b9SkzeP0Y=", + "pS3G2bHHlkOE6UHP0qVitDuxXgjEaZTviTjNc55RbVs=", + "ZVZpWOtYqhX3CpF1kATg/38J6pvUJo8AS1sVYjs3rUE="}, + {"sFlcRLDn36fnew2Ld92IHJwnKifdS3aF1MWRPs6K3Wg=", + "OpjUOTiWExaDYULTINB4yQFqc3mnU3RjQzGRV+KtdFY=", + "of0V/uoFRNljv/XTt/tXgoquLRH93Ty0KNiaPUpEi2w="}, + {"UJ8hjDsg3jfsnnfPH8Gw9FnCb6taTuviurAfZu+kEFg=", + "3byjHksUOv8CNjGGKvHvvrJDURBhCIL5UtfZbgyVWCE=", + "9f7dbWif51gGrE7R9LeuewQSrvGFGTOB3ceJC67jSkI="}, + {"iFFPKGIfqeUKY/w72KAZSjd/PGTqCakHYBV10xMDfnI=", + "ehneHATNSXtsJTOiPjVSc0QARkihgcfcvoXKFWKfQnI=", + "yKdqDBcRwA7RCg4GiY/b5IsWcExPleOBde/hjxc36a4="}, + {"GDGocdPJTPUAllxQo7SpXZKqMPn7lpxQELQUX9ETHmE=", + "n0ScNEou4ekfrXRRXvcADLu2Afj8g5D3TuDP/I4KrnY=", + "8QhswqAhi/ehhcmCwQF5aSh80TvIGC/gRL5jBn5wOH8="}, + {"UCcrlN8fX2ZdNdhaEBNwktwL+H0ZO7fhaj7rgdUutmw=", + "LF8J728ilXs4TphnrgR6r0p7W3912DYnsXkGMEPQnRE=", + "cYfMjxl2REYir7frGeB0u+NHAaFYF02ysgpBhOL5ygc="}, + {"SH0657XuIiHidVmViXZF30RUWtkuWXcWWHmKZHTiOG8=", + "7k6j49W1u5qgLE5MQUc1osPVW1oPPhzjrGvJ7o9YamY=", + "dV2+7rNk/3LR2IcwYg/c+Wvzep1yjY7/u1I+nnlTQ00="}, + {"qFQWs6jzrscV42pGISQyA7JDvFFAvEQCWJi584VHD2g=", + "AT63nHKLC17yUvkR4lOVPxCr4DD3QhXmcmecOTn+Amc=", + "2Qe2fwJbFcu1CmKpktElOFkSQMqlyvlV3ZUIAd/Dcts="}, + {"6J+yLxgPwWtUbk9I3zbeD8RuK6XkQjJ0wTJ1zSVhflQ=", + "NJzMBYyPZjk3eLmgdKaOeWyNER5YZF1mR8Umeiu9f28=", + "pp+4+XHw5ZmHGJ7WbZ1xLYRsnTI17QIbb0bzHzYZrBs="}, + {"KJGoWYNVDUWcEMg+E4tljv1LiWAbdRw2QVapYqdFa1Y=", + "M2SGk9WVnzYNGnT777G/JE8uUsY2f7mszTwlue73UDE=", + "Jg8N7GbhbYB400foFP+OH0v+hCaL1jW61bajSA6EZqc="}, + {"uAgrgppPyIvk8S1CHUmaCaORsgFFfBreB8pxXmbSdXw=", + "dJ22bER4fD3qF2/yIGWQ7SgmZ990sy/SbANjmUMkzws=", + "mKkd0OoAR9sClmD+k3fL9weBsoCy5GQGz9BP0kAQIjc="}, + {"EI3Q8gePNPrtFyoMcv7hOihQgroF/dfjzPn8yvpGPWQ=", + "Y9QhJFeiyuuIZNPU56B6U//ZK+XTTe4EP7h/p3Q+dE8=", + "qBVUYw9rTWaxn55nUd55NpCWtxOUSWLt4WJGusiDS8Q="}, + {"oEQvdRm/yHkx+JvlhHGT5RUFrFEUleKb9DCT55EqZ30=", + "8hsh/UHwWADTHntOJq4dy0o7ahcNAAlo2rDpjzzrVXk=", + "/GF2inW2mPtA26IgFdgOEBbBEerT740wWuP/8NyANdc="}, + {"6OWrGKZKNsgfRezSUw29EnHymcgKyEKvX5/pZQlLmWs=", + "oaJeO6YSS2dodNEf97DvgWrYnFFelG5daEdN84jVVGw=", + "T5wvTdyVxK0LY96kouLjs06oUfhGChfty8OUL1Mddro="}, + {"mPmzQbh2R+r1DC5hSquzKM1SDrxUdfnBPRPJrpqrgkE=", + "BtUHAnYWjDjBI42qBf9dJqezTUikYsF96o6PKEWPrVo=", + "MxU8EMmq+vVHpuK/AkFZrZDF2b+VbSqukZLPbNsCcgo="}, + {"8GVxuoo1Veyr+nqxr5Q4vmsMf5qfiXwSlQ4q3+BU60g=", + "uSOLe/E9/OjIgUOk0NMBHB45E0+q4Rd+IUO2UOxmKlM=", + "+30F+56OE0Sr3wY2clKw4kgE+2XiucMg7xjK6EemXuk="}, + {"qORPKb+qFuU/9TpFbRUupHsqm9iyk9pa6cpik+EVDkg=", + "bMZxxd+Z9I0XA1h9U8JEY+/mRxWGnvbXDZ5Dxz7YzS0=", + "SmkkqOz4OhHuSL6cxuRm9+Mlt50Sfd0sMDFTC78gqOE="}, + {"2Ko3IYhXKcdOMIJGNpASk9saNSZsI64lyJPOoxpQ2kI=", + "xVOc9PxY1VFaZfemmKi+Ei2liHhmeTu+JMa+rS00gnA=", + "+398DlW8kWeI2aRaC4QfcrEjwqPKCohyDeWdaI1wvv0="}, + {"CPioGCVpxnym62nH/QoCt1RiiaaxUcuFjvh5kRhjqHA=", + "W0XxlBLrZgFKhggMvvv6oFf/RJbfs92qv8JK9e+5i2o=", + "bsK2U6CRAUv1uVgYQ7NpqjWWswFIDiDPDEtU1XQygSA="}, + {"AF17siKaeiO85hikYN0IWCMGWqPm1UOoCkXMltJGUVk=", + "B+PFos9aN2S5bLxzGZHljRZj41j3rIx8RWu0vDUzq1w=", + "Qb8d6iDYv3m1h7PE8j0Exl2cSwpHkim/fJ1S4P7MYvY="}, + {"wMFDBTJzx5tDCBhMkrptYJ8w9EeURjc4xeDQpevxAWo=", + "4ec439EXE5WQzvtV9reSX+aMmdq5k7o9Ayt8oQp+RhQ=", + "jwQlvdNH5WtSSU10H+fh/JisOlaBaohDPEp/BYnTt1Q="}, + {"4GaJpIFigNDwd31O84pLIMM/o2qhp0ydlI/ydD/2a2Q=", + "r/LdkCoK5/BPGdq2+XJO8sCRhI+8ULFmg0887V43PAI=", + "Da+3ZZvEJdx4TYFMIDUlbkmytILnSTNxTKX+sQdjMd4="}, + {"gMnojGqCLmMfGp2m31xlKZ/rIV2b8ockw9DPahRyu0c=", + "H6tKCTosnM4BXKqflXrkTdJyNlCIZhQ3ZRxfrvSdrDk=", + "4Z6K3LKIMV89plcjMb9CzSzJl03SWRe/++geBMZcOtY="}, + {"wKCg22aNNoHnDJ0oAKE46FcSSsREW4AaGn5WxCSeXUs=", + "9NDTFC0iPt4HbbWepLHhN5poNTN2fdxJKNadsNT7qzY=", + "GSVTOCnfLpJ1VCOLHaKSjCMv7/OlcnQiP5+5woqkud8="}, + {"oCoykq7pcJcg2X2V5TBRzGwn8hzzHC05WUreuotdznY=", + "DxfwnbMqr5Wn5SAyFolfEmNQT6l84Oq69ngpg6H6Iio=", + "p1RHBuqhuDa1MAQ2lbqmUQFu0CTwYlf73fWZSj9tQhA="}, + {"UO7YVyRUVkcKr7c63VWWV2zj36XD3HyDfLZCqvrZmFc=", + "360lzYtIyHq5lv/QXSCe4bL4G2J1jBXFJ8yS+Ycr7Bc=", + "RRPQ1XWF1HN8Us4dtfn2eemdjgtWm7U8r7mM3y00NOk="}, + {"eCGFYV/NuGP4H552E8Of1xU+3IvZxGyX+p5UFGW8iHI=", + "LqhZ4AS9dQ/MhsQnE5Oy7Q8INXY+P+mrfGY5dtg9SlE=", + "SICfqs8T5wP6IzATDCT4ovamBKPdkZ7JP4Cfsb3izec="}, + {"oI3HBZknoIMMZw1BuYMkTBylt25reX7AbCqtWQv8cno=", + "B7dUgLgvQhi0RGmvaMrmf26WdjEVrhaiBclVkCKd4AA=", + "5O9K9pLXwxFAt5lfMWh4qGbwX1BM7sz0QGYxAnR77dk="}, + {"sBfYn14EFVIS2M/E1aahP7mOmRNbNtyDChDMS1s6aGs=", + "FLYv0ZvzxMkc9A7OzhC4P1ZRu1aKIQd7u6gqfdekC3c=", + "kaYLcNCXnCLgiB8fleMQuboUJsj5u3YAmXL9x3ywV0M="}, + {"qFwZESU/XYZXUtxwrGsFU9qPAFTzjm7EhTS1Q6ajGlM=", + "hraQQaqJCkS2yQXv+ccMOVh9V9a/qgSZJgdMAhrt8ms=", + "72oDfnWOn39gdk/ncw8Lv267I0I+m73SwxrpYojpWYk="}, + {"YHhp6Zf/miuc2QXeI2lTezy0lL0pTv2b+nWNkmjYQWI=", + "UhNO5arLzF0WlZSgNOx7+IjWN+GSxDdQxZRp8uIwsyI=", + "1Q39Nzv2NGI9zWKWMpYLURAMZUg+FP+OboHHzFU8Anc="}, + {"mLBBXKaCJ+7qeBZpS3wxGi/SQ4kLzun+K+QwwdwfJVQ=", + "gIq/nh7NwCJ36MvRnyrWHaRWu8lTmwfN2NvsjVl6SXQ=", + "AahcNR91GDyJBIP+vC8ZuIV8ukqjSGtd8s+cmjVC2Ao="}, + {"OJG4LZlNNngFtAEQdbVVWVm6QAjOOauGcMZGbQrb40M=", + "pFHAC6HaWAOtvTRRVfSHvzG05mp4SJZXKsN/tkSF0kM=", + "IoXT3wIqWNxQhYuHWl12ODq/P7RM9LwaqglhmjKg+0g="}, + {"OOwBFOQNhepiqDf04DehQLh1gpBNOluDF1ia752Yfng=", + "u715uJ/XhdjXjThCTJ9w6zzXnIhp3VCxhtso1wk+oBQ=", + "5x1Ip3Ym0KzDjGhiYjmpeWWr+dgrZlYwfr02GngPOTM="}, + {"eNPFnwkQy1qw+IjFAlrDA6+sIsxbWDlzbNSsBW8R1UA=", + "OaOXaAfPb1MRpWadawFje8YZ0oxJgdCIDIP8c5X+r1U=", + "NtfaRRD0GqujnaQQoNoBbtovgO4dfVwEmEQx/YgnDpw="}, + {"OLdaZItbtxH3mGqItkibIJp7KV27FrQavjhd5zq6s3Q=", + "SLSmAYxkMCGj0DO35cMLkC3NVAqK2VmVFndbOZEdA24=", + "SnBO68XQTDjxYbmYaAeEHgLwD2u4D+BPT86raRuUQZM="}, + {"UBQOEz08izwr4eEK/SnQUpkt+TxCjo6Sya/XOGMLOE0=", + "wQwrwezI9LzKevGsJJCBHDG8noR0yIEtOK5Rig97SSo=", + "DpyS+0d7lrlFWkztsniG2v/j44vcuvWz3sPeghRyb5A="}, + {"mN98iuqUKh67ggUdq9ZIQNZCZM90fgycTVqYKEo+DkY=", + "GYdXVW1jpS0dN1q9zMehubP7LfYqs34kszN0bXQqxxA=", + "AJPIHffB4uvvJJki4xCG0VORVBbF6bc2mZQqUx+idPc="}, + {"OEd/1it8C3o+NOWxDI3DfLMXVBHJQg15N3E8F8d99l8=", + "QL1NcuUkoXxDy7M9VjGslCejcUlnUDHRghFVnr+8fmA=", + "nven9Dicl8U6QXuDO8rRNtjd4NYaa90SU+Gmv435XKY="}, + {"AFMCGDu2oAP68miucsi4fmYX2KeRZnsEGv8tQm8JEng=", + "1sNxvk8uZhFsBUgxOXmuCMjDAgBbjVeWe9oaFk5Osy0=", + "t5iI5XXd56S5q0Y9HC91gzgF9uGjL9FIy6NUaKqkydo="}, + {"CIAwfJghQHHr4YlztN9at6/iWkrEVCGFAxNVuQCuT3I=", + "zpUOF1h17g7RpBzrVlN7oTRz6e+dxcDL8OsAtHwgLC8=", + "kOSwC1p6Uoti9E9Eg7ViPZwCytuvp5Fr5Buw677aogU="}, + {"sC8vrAVBU0zvhWDRfzfySjvopXm2/cTMkTLmioyO3Es=", + "p6H7GWm8NfgyO5OCX/COjvVT4MAnTs9ZUj4uZMK8XHA=", + "9Tzqo57V/h7+6nSNAHSBKdmU7ultlvZbAnNKSRlrLi4="}, + {"eI63gjxCZGnzqZxPEi/ifYphXhxIRI2ZxK8jzqo3mmU=", + "XyNzEuU9x37fxFCnrZH89Krs5/UqGVx5wNkGfQCAYz0=", + "vZ2fTlRPnJQ+q33YdS5p1aweqPGj/kTMc4Uq80FtFjI="}, + {"aNJlGtm79/RS4SQ/PC4YM6LFo9zAqDr2/RjLqk/z/1A=", + "lgZ9akPrABmfHQMlfNFnnpAJzGtcsaU9mUjEYKfzZHc=", + "d0Xt1Bcgphd1HMI0RneA4VdBbMZL1qNGJAvFhb080eA="}, + {"oONSnHirNh3cuH93Ty0C9AXKebGY+cdF3R0DtPzIQlo=", + "TuREKfA8EVQiYWsPx8veUzjN2cz/b72limSLWlrCWxw=", + "vEqqKbpZf0EM6EApMUaUH65r3Zr81Y/DSODhE4H7U3Y="}, + {"+D6RyLEaHJ9YF9WDyOlwh87KaNJcc6lqX8Arp6yqHF0=", + "EpecjfIo1/EEbmsgUtzEDqLu2ut+SMmzqaBL9Z/MlCA=", + "oYfO6/7XQgEYT9zmr4sqFrk0muK/fEv3FfD8MzZzjkE="}, + {"OCmW5KQql2PRMJnsMYQjXlr6TSYUbxJBknqZtXJPSHM=", + "ZZR2ghHlCwAJu/XlsEZuNS6XiGPwuXzyMPPywYFapVM=", + "fqSCXq+pKJJ6yNvlOi+tyQ9E4Y6kc4kblGrVqN2WuXA="}, + {"APWXDAe8d2ia1CUbf/IzSPXOUjR8TVuJgmISiWw0/EY=", + "jrT5P5YCkG+U7cfNTvCKy0GSEgsjwmJtHg+8HBP6ZCA=", + "t75aUjZXMPir8Ao0yhVClh9/BdWxSL+11CjK3iELNWk="}, + {"8Nw4sRis7M/6Om+3w5YHXthyMzLGuP48teqdzbHNPlA=", + "lj3q3ZYij3ZJ/QunK8n9I00cv/Z+O1TU1kFFl8x3DTE=", + "adqB79P7gbXEYYnSd1/UPCwFffTAPXa9kHWynRBYcGo="}, + {"yOupps5XbjV0fIZKnGhrpcxB7yDQzbBILC0UMJyVS1c=", + "+MDV/t9UCIdgm3IkH3BZlxaRPJ3lejRmrm4UPApq4mk=", + "AOJPmQxsU6hjOd+9mHnF0VL7Afih3P1Fr625xFT4FtY="}, + {"cD2DEl4MBwONuTV0db5XreoVjQAUZFNXqIeFEU3KFkE=", + "2CeHrjN7tBX48k4Sgv5fIHG06e57q/ucCL+8DIRmfXw=", + "3EUo6MRzs6rSoY/7AFs8wiBiTXPcHzerLh6Xp3aMGKo="}, + {"EA+S3a9ZeOLiRbhTxaT2wkpyDheAmai+UJa6SFGzSm8=", + "GGByUKZx/FPa2OkJoqVTHXx+6jrIpIw5rf0rp43MHko=", + "BXoDA3yn0JcMV7hHVzEqhlwAORvhToFO1qG00nas92A="}, + {"eBeJi/imBiV52WEqrwAprUQggqdQmvTTmWtLq8pDDkM=", + "zCX26ZTOZHLpq5x5aIUL1XhIVoXJLp/zcXwnmFA3jBo=", + "Dm/DCxXWYXEsmQgxAD3KREK2PF0bUSnV5WRAaya8s1I="}, + {"8C7p+EQO+CnWUSjHVu3PpeWpUIbLy48zpftZu021plM=", + "DxpnF/IbKAh6kmWC5Jpj8iw387EDkrvjsjOb9fbTSng=", + "bGrk0OshJB+0oQOK0QGKU8+lotnIDz3oeUnMZGienyM="}, + {"COIez7YcBJiOJCLxxWV5UGLW5/o009YI0aszlD/PiUc=", + "eD9USWV37LFIOxlDSHyOmfFqNJFpORRlzEI+HoF/czI=", + "n+/ra86gUSF2pNZS51nt2JgrzXnQJl+dWswOq/Ahs94="}, + {"iBBSTG9VLC+T9+ahNaQ4umZoig8o7w1DaeOw+cD2BGc=", + "XnAxqDlvGnQ6aakv8ABGHVj07qVQfk4NChZbstTMBxg=", + "7KKSwu/4yWr1UzFmNMGtiaSwdYMhP/HKbrQLlABL4UE="}, + {"eNmwattflehr9+KsVqTuwt1YaAc5ONkaIaTQt9Gkhn8=", + "1NNVvm++YTTGMKyAXfGOCZ4aDDdFFH5Um3vAg4XimDo=", + "bXNrnDTP0pBay9ytZe7xpiKoSi12F7WUXqoIeI++Xvo="}, + {"QJotpmZINx9eptKpkh9j3JlEDcHdWnjEbicdBS7gPXM=", + "3gLYKeoruVZ/AYjym0gciDvRHj45UIWyHhNjWj2Wj28=", + "NwuUkkE5yOWT7wed7bltgAk71miz3cSooiIDAdv6kKw="}, + {"OAYGuxH70OPQvhVIX4BhSCWUyzAI5H2IkYxKgC/AO0M=", + "Rj9iNF/FagkXfdLPqc9LHfaoGR8GlvY3gun2FilE508=", + "hkNXLVMlBRsMEaQKkSzevcEK2sMu0AShGKQJMNqdzWM="}, + {"uBrgZ7wLHrOV/0dNiEqo7FjY9VnJqL5eUDHJWAc9QXg=", + "3Hnln8ZHfSaK4OzESJe5U6NcLaW56wzfZICzvzefnSo=", + "DhXsehe5FAmbUidXT5ZpZIAuu1eF9rkU6cF3FBoKwOE="}, + {"sJknn3CHvx/812EWU3ddLdLLZFBKsc+wx35GXyiRsHY=", + "qqa2dNSt0jWozGyqpokP392H5/DOAUUZmUpyZDaUEEE=", + "1Dyz8CvmF17oKT/wG2fu3vRzPzgQv8/OY9GJYew4FG8="}, + {"yOumS9HN68ZwIm+5hZol3jFQ0DB4SKuW/ld3y8wioGk=", + "6PowsbKj/fKSzXZMAfaSkP3fE+4AThL9xm6ysQzMDxg=", + "vF8cKu9X9FxgCjyVZ5RG7nuue8RelF5Qsb8Efme4M4A="}, + {"mISm5vQfPdK72SsaHh6O1/ARvaWCtm+KZNcpTsyt500=", + "YCORDQDpb1U8vADdstBgXkg0N7QaAc5VoXJ4QFuA/UY=", + "JlrBmfaCgbfEVD9YQq3c03WwwsHWc1nBwp1JkFORC3A="}, + {"cPyu3Qry6qbsiOJKFGRziZ9LJWJ47k3ZSXiGkQXuQm0=", + "/cmBZTbqEp8sababPAxGb3OvDAEE7MlwPOwEFHE+7yM=", + "lp0Hhc/rVtpT5FtLLccChqDl3El48XtP6Wm6JwjI7jo="}, + {"YKsMYU0SINbPwWw4RDCJV6GnzDlSp1ZNwUw2euGWi0A=", + "/KmBReATQbFnLg8YKV0jwhqKeishRoWvlVtMX3550Vk=", + "7fXpPSMo1Fw2sOXtjTtvFU+DbZvS/FWB9wAsywWx6R4="}, + {"WD0YI3y71eIp/GXw9i+7scEiQKSBkGihZWE+s6fGmWo=", + "RdthAL/qPnAZFb3xBgRMiAtGHNjgokzoKX9iO2K5qhc=", + "4dk4HGkT9dBmomGNorDE/hLr/HEFhljtl4zz3M4sG58="}, + {"oD8KWhJYZVhutJRb0kZlZnB22QUXzi2FfPRD0ll65UM=", + "MYTBGHh4Ukj97pKj6qcfWmxGNQzmU3/aBOX2f1tfhG0=", + "VT1gC+a9nRJzYMi/TPvRVnn3IQlaop/jKmmxZePEME0="}, + {"0Ns/1SOiqR2CpHRG03QNzJJd5gxTm1XJmSkFlugjQ3k=", + "KvQAI+ekNOa2xfEvfyc9JGcS+CTUrnnhsKrlyJGJixg=", + "J8LmSX6zElX3S9q4PNvh2NKUtAiQ3oHiYjSJ7yErPlY="}, + {"mD6TeF4ezSPXN/csN1OhoAREFSXllI+zl4DUOInVq2Q=", + "WmLJ9ep2EqFcSftnYFJsmWyUxqL0zzuSzVEv94PcISM=", + "U2+ILy2NDmmfgSW78C8dl8GyHESUc1lXPHPpg5F+gr8="}, + {"SMJoXOYgHz8HSzY+ByeWLcSP5qFwv7YjRe0bcKesRnM=", + "DEsNSOY3TEs9J2YgqroQ4xKq8T8xNJQjvvE4UrTItQw=", + "Bws1Hk2+lO+JQ7ME16EbwAdsBkWsGvti0Gb6LY2Lrms="}, + {"iGhXF0Hg0tqZmpwAMiolxvbvTPClQ7LlBAspSSyFEHE=", + "7Xxzpwl7yRWehHNWTYVtFkdChJdXhtY4Mtw1fA9QcCg=", + "Swjfv0PjuaE8Oq3a17BVno5I+q49dZlPwKK1bPUoKNI="}, + {"MIazjx3qTi6Qz3WzhtCPw3i4Q2uZBHcuMoh++ZGFYUk=", + "oni8pbFqk9Ya+Fx+911Nl1SN0FD/hR1jwb2RH3t/pRk=", + "ZYAFcj67LkbNURYbSnCCWGxAG8QLDGWwbl968mA6ZA4="}, + {"MAadYdiFM2cPuJF19q20Yoo5KJabuR9TUQ9jG5nvA1w=", + "5OcE8XV+UPoBVbgqBQdVF62GZCW9DOQEdxrQsktPPBA=", + "FCZsEFXouy+xtxv8X7VroXtvPG1Z1HFHL724tz1jcUI="}, + {"+GwxMmD2dee5+QmvXNI0NdP+rNWoSXTN42otbp0aZ24=", + "Y0N44baz9ihclCUnv6rRbDqCYu4BxQlBfNnTz3NNe2A=", + "/LqSgkVQNkQ/oBiZSgpM9Rw7BJv0RvRpEQpvlizvHy0="}, + {"8FREpCtncOcT7+W2nW4aYSjmSbADtVSH9rIliQZZUH4=", + "fTNSd0JeREhXmPfjrmrAu6Lu/yHkB9GyxR3SyO4kZ28=", + "262KN/iG/iJEaZeerFm1yVtvhFVGgQFwSvtxTcjZzeg="}, + {"EDhaRQGtscjoSE9wJOnSXoQVtVruIqyzknty+x/vDWo=", + "eMmMgws6ZxDIxZ6QSwGjZO1Mx/r5T+fJjSTKGMBk/BU=", + "0CyaJV6AG9bZ0C4yeZ/RDsOs9BdNqZpUxAsD30WmJO4="}, + {"CJV0UB2YdvVDG1cs4oiJgHAS+f1FocGr/vGCfiovsWQ=", + "9/O9GWZEOXVm7On8lftL27PffRORju8OKl6gZd/74CA=", + "A+kXRVNOwIrA5DUa+3v7dpRC+Gbxm23LTiYmOUAXUyY="}, + {"gCjDsJUwZGA7BjYVoCQsvdIgN9Q4lBHlSyKwUrl751A=", + "HRwS8T9y2qPYk7JVU/8Y+6cS+Bk8XCLCXxwN/ttbQiI=", + "iFotjA6rhUfkDv4S/wspJgEWunEmrlGSGsXcJ0+8laQ="}, + {"6N5pL4gsuK+shHpDxirTnAGdyKXIlYHyfIhtB0njJGA=", + "CVZvW7NaN2XMEEKHodghBA9hLCwee/jrmttiWh/CmEg=", + "OpPEd3Sp8r6KdjNDTN4bVHETlGJ92BCK74FCdEaDe9g="}, + {"UIPPTUdvhlg8qEDv6JRxM4/8F5ORjJz4ud82QZrgeEY=", + "7Nd13z5EpB3ChytvQC1CxvDY7n0H8r2Y7lzLEY8hdEk=", + "b22PvgU0M2QfNC7ZGN+RXNe5fjOzMsY32IcHTwLNIqw="}, + {"oBn53Q5fmxKX02PgI6F47Rb+XoLeFQO07ok2tYhk0lE=", + "e0gtPDKXCZSoNW1uHqBPQXLfiYgyeqPMU2zZJgPXACI=", + "wmjW2wDT2EzFkyaGui7YWNLTRu8Q4eD/GVKM2utZkEs="}, + }; + gs_unref_ptrarray GPtrArray *allowed_ips_keep_alive = NULL; + gs_unref_array GArray *peers = NULL; + NMPlatformLnkWireGuard lnk_wireguard; + int r; + guint i; + + allowed_ips_keep_alive = g_ptr_array_new_with_free_func(g_free); + + peers = g_array_new(FALSE, TRUE, sizeof(NMPWireGuardPeer)); + + lnk_wireguard = (NMPlatformLnkWireGuard){ + .listen_port = 50754, + .fwmark = 0x1102, + }; + _copy_base64(lnk_wireguard.private_key, sizeof(lnk_wireguard.private_key), self_key.pri); + _copy_base64(lnk_wireguard.public_key, sizeof(lnk_wireguard.public_key), self_key.pub); + + if (test_mode == 0) { + /* no peers. */ + } else if (NM_IN_SET(test_mode, 1, 2)) { + guint num_peers = (test_mode == 1) ? 1 : G_N_ELEMENTS(keys); + + for (i = 0; i < num_peers; i++) { + NMPWireGuardPeer peer; + char s_addr[NM_UTILS_INET_ADDRSTRLEN]; + NMSockAddrUnion endpoint; + guint i_allowed_ips, n_allowed_ips; + NMPWireGuardAllowedIP *allowed_ips; + + if ((i % 2) == 1) { + endpoint = (NMSockAddrUnion){ + .in = + { + .sin_family = AF_INET, + .sin_addr.s_addr = + nmtst_inet4_from_string(nm_sprintf_buf(s_addr, "192.168.7.%d", i)), + .sin_port = htons(14000 + i), + }, + }; + } else { + endpoint = (NMSockAddrUnion){ + .in6 = + { + .sin6_family = AF_INET6, + .sin6_addr = *nmtst_inet6_from_string( + nm_sprintf_buf(s_addr, "a:b:c:e::1:%d", i)), + .sin6_port = htons(16000 + i), + }, + }; + } + + if (test_mode == 1) + n_allowed_ips = 1; + else + n_allowed_ips = i % 10; + allowed_ips = g_new0(NMPWireGuardAllowedIP, n_allowed_ips); + g_ptr_array_add(allowed_ips_keep_alive, allowed_ips); + for (i_allowed_ips = 0; i_allowed_ips < n_allowed_ips; i_allowed_ips++) { + NMPWireGuardAllowedIP *aip = &allowed_ips[i_allowed_ips]; + + aip->family = (i_allowed_ips % 2) ? AF_INET : AF_INET6; + if (aip->family == AF_INET) { + aip->addr.addr4 = nmtst_inet4_from_string( + nm_sprintf_buf(s_addr, "10.%u.%u.0", i, i_allowed_ips)); + aip->mask = 32 - (i_allowed_ips % 8); + } else { + aip->addr.addr6 = *nmtst_inet6_from_string( + nm_sprintf_buf(s_addr, "a:d:f:%02x:%02x::", i, i_allowed_ips)); + aip->mask = 128 - (i_allowed_ips % 10); + } + } + + peer = (NMPWireGuardPeer){ + .persistent_keepalive_interval = 60 + i, + .endpoint = endpoint, + .allowed_ips = n_allowed_ips > 0 ? allowed_ips : NULL, + .allowed_ips_len = n_allowed_ips, + }; + _copy_base64(peer.public_key, sizeof(peer.public_key), keys[i].pub); + _copy_base64(peer.preshared_key, + sizeof(peer.preshared_key), + (i % 3) ? NULL : keys[i].pre); + + g_array_append_val(peers, peer); + } + } else + g_assert_not_reached(); + + r = nm_platform_link_wireguard_change(platform, + ifindex, + &lnk_wireguard, + (const NMPWireGuardPeer *) peers->data, + NULL, + peers->len, + NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_PRIVATE_KEY + | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_LISTEN_PORT + | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_HAS_FWMARK + | NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS); + g_assert(NMTST_NM_ERR_SUCCESS(r)); +} + +/*****************************************************************************/ + +typedef struct { + NMLinkType link_type; + int test_mode; + gboolean external_command; +} TestAddSoftwareDetectData; + +static void +test_software_detect(gconstpointer user_data) +{ + const TestAddSoftwareDetectData *test_data = user_data; + int ifindex, ifindex_parent; + const NMPlatformLink * plink; + const NMPObject * lnk; + int r; + guint i_step; + const gboolean ext = test_data->external_command; + NMPlatformLnkBridge lnk_bridge = {}; + NMPlatformLnkTun lnk_tun; + NMPlatformLnkGre lnk_gre = {}; + nm_auto_close int tun_fd = -1; + + nmtstp_run_command_check("ip link add %s type dummy", PARENT_NAME); + ifindex_parent = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex; + + switch (test_data->link_type) { + case NM_LINK_TYPE_BRIDGE: + + lnk_bridge.forward_delay = 1560; + lnk_bridge.hello_time = 150; + lnk_bridge.max_age = 2100; + lnk_bridge.ageing_time = 2200; + lnk_bridge.stp_state = TRUE; + lnk_bridge.priority = 22; + lnk_bridge.vlan_protocol = 0x8100; + lnk_bridge.vlan_stats_enabled = + nmtstp_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_IFLA_BR_VLAN_STATS_ENABLED) + ? TRUE + : FALSE; + lnk_bridge.group_fwd_mask = 8; + lnk_bridge.group_addr = (NMEtherAddr){{0x01, 0x80, 0xC2, 0x00, 0x00, 0x08}}; + lnk_bridge.mcast_snooping = TRUE; + lnk_bridge.mcast_router = 1; + lnk_bridge.mcast_query_use_ifaddr = TRUE; + lnk_bridge.mcast_querier = TRUE; + lnk_bridge.mcast_hash_max = 1024; + lnk_bridge.mcast_last_member_count = 2; + lnk_bridge.mcast_startup_query_count = 3; + lnk_bridge.mcast_last_member_interval = 5000; + lnk_bridge.mcast_membership_interval = 25000; + lnk_bridge.mcast_querier_interval = 26000; + lnk_bridge.mcast_query_interval = 12000; + lnk_bridge.mcast_query_response_interval = 5200; + lnk_bridge.mcast_startup_query_interval = 3000; + + if (!nmtstp_link_bridge_add(NULL, ext, DEVICE_NAME, &lnk_bridge)) + g_error("Failed adding Bridge interface"); + break; + + case NM_LINK_TYPE_GRE: + { + gboolean gracefully_skip = FALSE; + + lnk_gre.local = nmtst_inet4_from_string("192.168.233.204"); + lnk_gre.remote = nmtst_inet4_from_string("172.168.10.25"); + lnk_gre.parent_ifindex = ifindex_parent; + lnk_gre.ttl = 174; + lnk_gre.tos = 37; + lnk_gre.path_mtu_discovery = TRUE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "gre0")) { + /* Seems that the ip_gre module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip_gre", NULL) != 0; + } + + if (!nmtstp_link_gre_add(NULL, ext, DEVICE_NAME, &lnk_gre)) { + if (gracefully_skip) { + g_test_skip( + "Cannot create gre tunnel because of missing ip_gre module (modprobe ip_gre)"); + goto out_delete_parent; + } + g_error("Failed adding GRE tunnel"); + } + break; + } + case NM_LINK_TYPE_GRETAP: + { + gboolean gracefully_skip = FALSE; + + lnk_gre.local = nmtst_inet4_from_string("192.168.1.133"); + lnk_gre.remote = nmtst_inet4_from_string("172.168.101.2"); + lnk_gre.parent_ifindex = ifindex_parent; + lnk_gre.ttl = 39; + lnk_gre.tos = 12; + lnk_gre.path_mtu_discovery = FALSE; + lnk_gre.is_tap = TRUE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "gretap0")) { + /* Seems that the ip_gre module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip_gre", NULL) != 0; + } + + if (!nmtstp_link_gre_add(NULL, ext, DEVICE_NAME, &lnk_gre)) { + if (gracefully_skip) { + g_test_skip("Cannot create gretap tunnel because of missing ip_gre module " + "(modprobe ip_gre)"); + goto out_delete_parent; + } + g_error("Failed adding GRETAP tunnel"); + } + break; + } + case NM_LINK_TYPE_IPIP: + { + NMPlatformLnkIpIp lnk_ipip = {}; + gboolean gracefully_skip = FALSE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "tunl0")) { + /* Seems that the ipip module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ipip", NULL) != 0; + } + + lnk_ipip.local = nmtst_inet4_from_string("1.2.3.4"); + lnk_ipip.remote = nmtst_inet4_from_string("5.6.7.8"); + lnk_ipip.parent_ifindex = ifindex_parent; + lnk_ipip.tos = 32; + lnk_ipip.path_mtu_discovery = FALSE; + + if (!nmtstp_link_ipip_add(NULL, ext, DEVICE_NAME, &lnk_ipip)) { + if (gracefully_skip) { + g_test_skip( + "Cannot create ipip tunnel because of missing ipip module (modprobe ipip)"); + goto out_delete_parent; + } + g_error("Failed adding IPIP tunnel"); + } + break; + } + case NM_LINK_TYPE_IP6TNL: + { + NMPlatformLnkIp6Tnl lnk_ip6tnl = {}; + gboolean gracefully_skip = FALSE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "ip6tnl0")) { + /* Seems that the ip6_tunnel module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip6_tunnel", NULL) != 0; + } + + switch (test_data->test_mode) { + case 0: + lnk_ip6tnl.local = *nmtst_inet6_from_string("fd01::15"); + lnk_ip6tnl.remote = *nmtst_inet6_from_string("fd01::16"); + lnk_ip6tnl.parent_ifindex = ifindex_parent; + lnk_ip6tnl.tclass = 20; + lnk_ip6tnl.encap_limit = 6; + lnk_ip6tnl.flow_label = 1337; + lnk_ip6tnl.proto = IPPROTO_IPV6; + break; + case 1: + lnk_ip6tnl.local = *nmtst_inet6_from_string("fd01::17"); + lnk_ip6tnl.remote = *nmtst_inet6_from_string("fd01::18"); + lnk_ip6tnl.parent_ifindex = ifindex_parent; + lnk_ip6tnl.tclass = 0; + lnk_ip6tnl.encap_limit = 0; + lnk_ip6tnl.flow_label = 1338; + lnk_ip6tnl.proto = IPPROTO_IPV6; + lnk_ip6tnl.flags = IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS; + break; + } + + if (!nmtstp_link_ip6tnl_add(NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) { + if (gracefully_skip) { + g_test_skip("Cannot create ip6tnl tunnel because of missing ip6_tunnel module " + "(modprobe ip6_tunnel)"); + goto out_delete_parent; + } + g_error("Failed adding IP6TNL tunnel"); + } + break; + } + case NM_LINK_TYPE_IP6GRE: + { + NMPlatformLnkIp6Tnl lnk_ip6tnl = {}; + gboolean gracefully_skip = FALSE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "ip6gre0")) { + /* Seems that the ip6_tunnel module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip6_gre", NULL) != 0; + } + + lnk_ip6tnl.local = *nmtst_inet6_from_string("fd01::42"); + lnk_ip6tnl.remote = *nmtst_inet6_from_string("fd01::aaaa"); + lnk_ip6tnl.parent_ifindex = ifindex_parent; + lnk_ip6tnl.tclass = 21; + lnk_ip6tnl.flow_label = 1338; + lnk_ip6tnl.is_gre = TRUE; + + if (!nmtstp_link_ip6gre_add(NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) { + if (gracefully_skip) { + g_test_skip("Cannot create ip6gre tunnel because of missing ip6_gre module " + "(modprobe ip6_gre)"); + goto out_delete_parent; + } + g_error("Failed adding IP6GRE tunnel"); + } + break; + } + case NM_LINK_TYPE_IP6GRETAP: + { + NMPlatformLnkIp6Tnl lnk_ip6tnl = {}; + gboolean gracefully_skip = FALSE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "ip6gre0")) { + /* Seems that the ip6_tunnel module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "ip6_gre", NULL) != 0; + } + + lnk_ip6tnl.local = *nmtst_inet6_from_string("fe80::abcd"); + lnk_ip6tnl.remote = *nmtst_inet6_from_string("fc01::bbbb"); + lnk_ip6tnl.parent_ifindex = ifindex_parent; + lnk_ip6tnl.ttl = 10; + lnk_ip6tnl.tclass = 22; + lnk_ip6tnl.flow_label = 1339; + lnk_ip6tnl.is_gre = TRUE; + lnk_ip6tnl.is_tap = TRUE; + + if (!nmtstp_link_ip6gre_add(NULL, ext, DEVICE_NAME, &lnk_ip6tnl)) { + if (gracefully_skip) { + g_test_skip("Cannot create ip6gretap tunnel because of missing ip6_gre module " + "(modprobe ip6_gre)"); + goto out_delete_parent; + } + g_error("Failed adding IP6GRETAP tunnel"); + } + break; + } + case NM_LINK_TYPE_MACVLAN: + { + NMPlatformLnkMacvlan lnk_macvlan = {}; + const NMPlatformLink *dummy; + char buf[256]; + int i; + + lnk_macvlan.mode = MACVLAN_MODE_BRIDGE; + lnk_macvlan.no_promisc = FALSE; + lnk_macvlan.tap = FALSE; + + /* Since in old kernel versions sysfs files for macvtaps are not + * namespaced, the creation can fail if a macvtap in another namespace + * has the same index. Try to detect this situation and skip already + * used indexes. + * The fix (17af2bce) is included kernel 4.7, dated 24 July, 2016. + */ + for (i = ifindex_parent + 1; i < ifindex_parent + 100; i++) { + snprintf(buf, sizeof(buf), "/sys/class/macvtap/tap%d", i); + if (!g_file_test(buf, G_FILE_TEST_IS_SYMLINK)) + break; + + _LOGD("skipping ifindex %d as already used by a macvtap", i); + + dummy = nmtstp_link_dummy_add(NM_PLATFORM_GET, FALSE, "dummy-tmp"); + g_assert_cmpint(dummy->ifindex, ==, i); + nmtstp_link_delete(NM_PLATFORM_GET, FALSE, dummy->ifindex, NULL, TRUE); + } + + if (!nmtstp_link_macvlan_add(NULL, ext, DEVICE_NAME, ifindex_parent, &lnk_macvlan)) + g_error("Failed adding MACVLAN interface"); + break; + } + case NM_LINK_TYPE_MACVTAP: + { + NMPlatformLnkMacvlan lnk_macvtap = {}; + + lnk_macvtap.mode = MACVLAN_MODE_PRIVATE; + lnk_macvtap.no_promisc = FALSE; + lnk_macvtap.tap = TRUE; + + if (!nmtstp_link_macvlan_add(NULL, ext, DEVICE_NAME, ifindex_parent, &lnk_macvtap)) + g_error("Failed adding MACVTAP interface"); + break; + } + case NM_LINK_TYPE_SIT: + { + NMPlatformLnkSit lnk_sit = {}; + gboolean gracefully_skip = FALSE; + + lnk_sit.local = nmtst_inet4_from_string("192.168.200.1"); + lnk_sit.remote = nmtst_inet4_from_string("172.25.100.14"); + lnk_sit.parent_ifindex = ifindex_parent; + lnk_sit.ttl = 0; + lnk_sit.tos = 31; + lnk_sit.path_mtu_discovery = FALSE; + + if (!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "sit0")) { + /* Seems that the sit module is not loaded... try to load it. */ + gracefully_skip = nm_utils_modprobe(NULL, TRUE, "sit", NULL) != 0; + } + + if (!nmtstp_link_sit_add(NULL, ext, DEVICE_NAME, &lnk_sit)) { + if (gracefully_skip) { + g_test_skip( + "Cannot create sit tunnel because of missing sit module (modprobe sit)"); + goto out_delete_parent; + } + g_error("Failed adding SIT tunnel"); + } + break; + } + case NM_LINK_TYPE_VLAN: + nmtstp_run_command_check("ip link add name %s link %s type vlan id 1242", + DEVICE_NAME, + PARENT_NAME); + break; + case NM_LINK_TYPE_VRF: + { + NMPlatformLnkVrf lnk_vrf = {}; + gboolean not_supported; + + lnk_vrf.table = 9876; + + if (!nmtstp_link_vrf_add(NULL, ext, DEVICE_NAME, &lnk_vrf, ¬_supported)) { + if (not_supported) { + g_test_skip("Cannot create VRF interface because of missing kernel support"); + goto out_delete_parent; + } + g_error("Failed adding VRF interface"); + } + break; + } + case NM_LINK_TYPE_VXLAN: + { + NMPlatformLnkVxlan lnk_vxlan = {}; + + switch (test_data->test_mode) { + case 0: + lnk_vxlan.parent_ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, PARENT_NAME); + lnk_vxlan.id = 42; + lnk_vxlan.local = nmtst_inet4_from_string("23.1.2.164"); + lnk_vxlan.group = nmtst_inet4_from_string("239.1.2.134"); + lnk_vxlan.dst_port = 4789; + lnk_vxlan.learning = TRUE; + lnk_vxlan.ageing = 1245; + break; + case 1: + lnk_vxlan.parent_ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, PARENT_NAME); + lnk_vxlan.id = 11214423; + lnk_vxlan.local6 = *nmtst_inet6_from_string("1:2:3:4:334:23::23"); + lnk_vxlan.group6 = *nmtst_inet6_from_string("ff0e::115"); + lnk_vxlan.ttl = 32; + lnk_vxlan.dst_port = 57412; + lnk_vxlan.src_port_min = 1000; + lnk_vxlan.src_port_max = 1003; + lnk_vxlan.learning = TRUE; + lnk_vxlan.ageing = 3245; + break; + } + + g_assert(nmtstp_link_vxlan_add(NULL, ext, DEVICE_NAME, &lnk_vxlan)); + break; + } + case NM_LINK_TYPE_TUN: + { + gboolean owner_valid = nmtst_get_rand_bool(); + gboolean group_valid = nmtst_get_rand_bool(); + + switch (test_data->test_mode) { + case 0: + lnk_tun = (NMPlatformLnkTun){ + .type = nmtst_get_rand_bool() ? IFF_TUN : IFF_TAP, + .owner = owner_valid ? getuid() : 0, + .owner_valid = owner_valid, + .group = group_valid ? getgid() : 0, + .group_valid = group_valid, + .pi = nmtst_get_rand_bool(), + .vnet_hdr = nmtst_get_rand_bool(), + .multi_queue = nmtst_get_rand_bool(), + + /* if we add the device via iproute2 (external), we can only + * create persistent devices. */ + .persist = (ext == 1) ? TRUE : nmtst_get_rand_bool(), + }; + break; + default: + g_assert_not_reached(); + break; + } + + g_assert(nmtstp_link_tun_add(NULL, + ext, + DEVICE_NAME, + &lnk_tun, + (!lnk_tun.persist || nmtst_get_rand_bool()) ? &tun_fd : NULL)); + break; + } + case NM_LINK_TYPE_WIREGUARD: + { + const NMPlatformLink *link; + + r = nm_platform_link_wireguard_add(NM_PLATFORM_GET, DEVICE_NAME, &link); + if (r == -EOPNOTSUPP) { + g_test_skip("wireguard not supported (modprobe wireguard?)"); + goto out_delete_parent; + } + + g_assert(NMTST_NM_ERR_SUCCESS(r)); + g_assert(NMP_OBJECT_GET_TYPE(NMP_OBJECT_UP_CAST(link)) == NMP_OBJECT_TYPE_LINK); + break; + } + default: + g_assert_not_reached(); + } + + ifindex = nmtstp_assert_wait_for_link(NM_PLATFORM_GET, DEVICE_NAME, test_data->link_type, 100) + ->ifindex; + + nmtstp_link_set_updown(NULL, -1, ifindex_parent, TRUE); + + for (i_step = 0; i_step < 5; i_step++) { + _LOGD("test-software-detect: step %u", i_step); + if (nmtst_is_debug()) + nmtstp_run_command_check("ip -d link show %s", DEVICE_NAME); + + if (i_step > 0) { + gboolean set_up = (i_step % 2) == 1; + + if (test_data->link_type == NM_LINK_TYPE_VXLAN && set_up) { + /* On RHEL-7, we need to add a tiny sleep here, otherwise, + * upping the vxlan device fails with EADDRINUSE. + * https://bugzilla.redhat.com/show_bug.cgi?id=1277131 */ + g_usleep(1); + } + nmtstp_link_set_updown(NULL, -1, ifindex, set_up); + } + + lnk = nm_platform_link_get_lnk(NM_PLATFORM_GET, ifindex, test_data->link_type, &plink); + g_assert(plink); + g_assert_cmpint(plink->ifindex, ==, ifindex); + + if (!lnk && test_data->link_type == NM_LINK_TYPE_TUN) { + /* this is ok. Kernel apparently does not support tun properties via netlink. We + * fetch them from sysfs below. */ + } else + g_assert(lnk); + + switch (test_data->link_type) { + case NM_LINK_TYPE_BRIDGE: + { + const NMPlatformLnkBridge *plnk = &lnk->lnk_bridge; + + g_assert(plnk == nm_platform_link_get_lnk_bridge(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(nm_platform_lnk_bridge_cmp(&lnk_bridge, plnk), ==, 0); + g_assert_cmpint(plnk->forward_delay, ==, 1560); + g_assert_cmpint(plnk->hello_time, ==, 150); + g_assert_cmpint(plnk->max_age, ==, 2100); + g_assert_cmpint(plnk->ageing_time, ==, 2200); + g_assert_cmpint(plnk->stp_state, ==, TRUE); + g_assert_cmpint(plnk->priority, ==, 22); + g_assert_cmpint(plnk->vlan_protocol, ==, 0x8100); + g_assert_cmpint(plnk->vlan_stats_enabled, ==, lnk_bridge.vlan_stats_enabled); + g_assert_cmpint(plnk->group_fwd_mask, ==, 8); + g_assert_cmpint(plnk->mcast_snooping, ==, TRUE); + g_assert_cmpint(plnk->mcast_router, ==, 1); + g_assert_cmpint(plnk->mcast_query_use_ifaddr, ==, TRUE); + g_assert_cmpint(plnk->mcast_querier, ==, TRUE); + g_assert_cmpint(plnk->mcast_hash_max, ==, 1024); + g_assert_cmpint(plnk->mcast_last_member_count, ==, 2); + g_assert_cmpint(plnk->mcast_startup_query_count, ==, 3); + g_assert_cmpint(plnk->mcast_last_member_interval, ==, 5000); + g_assert_cmpint(plnk->mcast_membership_interval, ==, 25000); + g_assert_cmpint(plnk->mcast_querier_interval, ==, 26000); + g_assert_cmpint(plnk->mcast_query_interval, ==, 12000); + g_assert_cmpint(plnk->mcast_query_response_interval, ==, 5200); + g_assert_cmpint(plnk->mcast_startup_query_interval, ==, 3000); + g_assert_cmpint(nm_platform_lnk_bridge_cmp(&lnk_bridge, plnk), ==, 0); + break; + } + case NM_LINK_TYPE_GRE: + { + const NMPlatformLnkGre *plnk = &lnk->lnk_gre; + + g_assert(plnk == nm_platform_link_get_lnk_gre(NM_PLATFORM_GET, ifindex, NULL)); + g_assert(nm_platform_lnk_gre_cmp(plnk, &lnk_gre) == 0); + + break; + } + case NM_LINK_TYPE_GRETAP: + { + const NMPlatformLnkGre *plnk = &lnk->lnk_gre; + + g_assert(plnk == nm_platform_link_get_lnk_gretap(NM_PLATFORM_GET, ifindex, NULL)); + g_assert(nm_platform_lnk_gre_cmp(plnk, &lnk_gre) == 0); + break; + } + case NM_LINK_TYPE_IP6TNL: + { + const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl; + + switch (test_data->test_mode) { + case 0: + g_assert(plnk == nm_platform_link_get_lnk_ip6tnl(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip6_address(&plnk->local, "fd01::15"); + nmtst_assert_ip6_address(&plnk->remote, "fd01::16"); + g_assert_cmpint(plnk->ttl, ==, 0); + g_assert_cmpint(plnk->tclass, ==, 20); + g_assert_cmpint(plnk->encap_limit, ==, 6); + g_assert_cmpint(plnk->flow_label, ==, 1337); + g_assert_cmpint(plnk->proto, ==, IPPROTO_IPV6); + break; + case 1: + g_assert(plnk == nm_platform_link_get_lnk_ip6tnl(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip6_address(&plnk->local, "fd01::17"); + nmtst_assert_ip6_address(&plnk->remote, "fd01::18"); + g_assert_cmpint(plnk->ttl, ==, 0); + g_assert_cmpint(plnk->flow_label, ==, 1338); + g_assert_cmpint(plnk->proto, ==, IPPROTO_IPV6); + g_assert_cmpint(plnk->flags & 0xFFFF, /* ignore kernel internal flags */ + ==, + IP6_TNL_F_IGN_ENCAP_LIMIT | IP6_TNL_F_USE_ORIG_TCLASS); + break; + } + break; + } + case NM_LINK_TYPE_IP6GRE: + { + const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl; + + g_assert(plnk == nm_platform_link_get_lnk_ip6gre(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip6_address(&plnk->local, "fd01::42"); + nmtst_assert_ip6_address(&plnk->remote, "fd01::aaaa"); + g_assert_cmpint(plnk->tclass, ==, 21); + g_assert_cmpint(plnk->flow_label, ==, 1338); + g_assert_cmpint(plnk->is_gre, ==, TRUE); + g_assert_cmpint(plnk->is_tap, ==, FALSE); + break; + } + case NM_LINK_TYPE_IP6GRETAP: + { + const NMPlatformLnkIp6Tnl *plnk = &lnk->lnk_ip6tnl; + + g_assert(plnk == nm_platform_link_get_lnk_ip6gretap(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip6_address(&plnk->local, "fe80::abcd"); + nmtst_assert_ip6_address(&plnk->remote, "fc01::bbbb"); + g_assert_cmpint(plnk->ttl, ==, 10); + g_assert_cmpint(plnk->tclass, ==, 22); + g_assert_cmpint(plnk->flow_label, ==, 1339); + g_assert_cmpint(plnk->is_gre, ==, TRUE); + g_assert_cmpint(plnk->is_tap, ==, TRUE); + break; + } + case NM_LINK_TYPE_IPIP: + { + const NMPlatformLnkIpIp *plnk = &lnk->lnk_ipip; + + g_assert(plnk == nm_platform_link_get_lnk_ipip(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip4_address(plnk->local, "1.2.3.4"); + nmtst_assert_ip4_address(plnk->remote, "5.6.7.8"); + g_assert_cmpint(plnk->ttl, ==, 0); + g_assert_cmpint(plnk->tos, ==, 32); + g_assert_cmpint(plnk->path_mtu_discovery, ==, FALSE); + break; + } + case NM_LINK_TYPE_MACVLAN: + { + const NMPlatformLnkMacvlan *plnk = &lnk->lnk_macvlan; + + g_assert(plnk == nm_platform_link_get_lnk_macvlan(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->no_promisc, ==, FALSE); + g_assert_cmpint(plnk->mode, ==, MACVLAN_MODE_BRIDGE); + break; + } + case NM_LINK_TYPE_MACVTAP: + { + const NMPlatformLnkMacvlan *plnk = &lnk->lnk_macvlan; + + g_assert(plnk == nm_platform_link_get_lnk_macvtap(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->no_promisc, ==, FALSE); + g_assert_cmpint(plnk->mode, ==, MACVLAN_MODE_PRIVATE); + break; + } + case NM_LINK_TYPE_SIT: + { + const NMPlatformLnkSit *plnk = &lnk->lnk_sit; + + g_assert(plnk == nm_platform_link_get_lnk_sit(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, ==, ifindex_parent); + nmtst_assert_ip4_address(plnk->local, "192.168.200.1"); + nmtst_assert_ip4_address(plnk->remote, "172.25.100.14"); + g_assert_cmpint(plnk->ttl, ==, 0); + g_assert_cmpint(plnk->tos, ==, 31); + g_assert_cmpint(plnk->path_mtu_discovery, ==, FALSE); + break; + } + case NM_LINK_TYPE_TUN: + { + const NMPlatformLnkTun *plnk; + NMPlatformLnkTun lnk_tun2; + + g_assert((lnk ? &lnk->lnk_tun : NULL) + == nm_platform_link_get_lnk_tun(NM_PLATFORM_GET, ifindex, NULL)); + + /* kernel might not expose tun options via netlink. Either way, try + * to read them (either from platform cache, or fallback to sysfs). + * See also: rh#1547213. */ + if (!nm_platform_link_tun_get_properties(NM_PLATFORM_GET, ifindex, &lnk_tun2)) + g_assert_not_reached(); + + plnk = lnk ? &lnk->lnk_tun : &lnk_tun2; + if (lnk) + g_assert(memcmp(plnk, &lnk_tun2, sizeof(NMPlatformLnkTun)) == 0); + + if (i_step == 0) { + /* Before we upped the device for the first time the kernel didn't notify + * us of the owner set after the link creation: + * https://bugzilla.redhat.com/show_bug.cgi?id=1566062 + */ + break; + } + + g_assert(nm_platform_lnk_tun_cmp(plnk, &lnk_tun) == 0); + break; + } + case NM_LINK_TYPE_VLAN: + { + const NMPlatformLnkVlan *plnk = &lnk->lnk_vlan; + + g_assert(plnk == nm_platform_link_get_lnk_vlan(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->id, ==, 1242); + break; + } + case NM_LINK_TYPE_VRF: + { + const NMPlatformLnkVrf *plnk = &lnk->lnk_vrf; + + g_assert(plnk == nm_platform_link_get_lnk_vrf(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->table, ==, 9876); + break; + } + case NM_LINK_TYPE_VXLAN: + { + const NMPlatformLnkVxlan *plnk = &lnk->lnk_vxlan; + + g_assert(plnk == nm_platform_link_get_lnk_vxlan(NM_PLATFORM_GET, ifindex, NULL)); + g_assert_cmpint(plnk->parent_ifindex, !=, 0); + g_assert_cmpint(plnk->tos, ==, 0); + g_assert_cmpint(plnk->learning, ==, TRUE); + g_assert_cmpint(plnk->limit, ==, 0); + g_assert_cmpint(plnk->proxy, ==, FALSE); + g_assert_cmpint(plnk->rsc, ==, FALSE); + g_assert_cmpint(plnk->l2miss, ==, FALSE); + g_assert_cmpint(plnk->l3miss, ==, FALSE); + + switch (test_data->test_mode) { + case 0: + g_assert_cmpint(plnk->id, ==, 42); + nmtst_assert_ip4_address(plnk->local, "23.1.2.164"); + nmtst_assert_ip4_address(plnk->group, "239.1.2.134"); + nmtst_assert_ip6_address(&plnk->group6, "::"); + nmtst_assert_ip6_address(&plnk->local6, "::"); + g_assert_cmpint(plnk->ttl, ==, 0); + g_assert_cmpint(plnk->ageing, ==, 1245); + g_assert_cmpint(plnk->dst_port, ==, 4789); + if (plnk->src_port_min != 0 || plnk->src_port_max != 0) { + /* on some kernels, omitting the port range results in setting + * following default port range. */ + g_assert_cmpint(plnk->src_port_min, ==, 32768); + g_assert_cmpint(plnk->src_port_max, ==, 61000); + } + break; + case 1: + g_assert_cmpint(plnk->id, ==, 11214423); + nmtst_assert_ip4_address(plnk->local, "0.0.0.0"); + nmtst_assert_ip4_address(plnk->group, "0.0.0.0"); + nmtst_assert_ip6_address(&plnk->group6, "ff0e::115"); + nmtst_assert_ip6_address(&plnk->local6, "1:2:3:4:334:23::23"); + g_assert_cmpint(plnk->ageing, ==, 3245); + g_assert_cmpint(plnk->dst_port, ==, 57412); + g_assert_cmpint(plnk->ttl, ==, 32); + g_assert_cmpint(plnk->src_port_min, ==, 1000); + g_assert_cmpint(plnk->src_port_max, ==, 1003); + break; + } + break; + } + case NM_LINK_TYPE_WIREGUARD: + { + const NMPlatformLnkWireGuard *plnk = &lnk->lnk_wireguard; + + g_assert(plnk == nm_platform_link_get_lnk_wireguard(NM_PLATFORM_GET, ifindex, NULL)); + + if (plink->n_ifi_flags & IFF_UP) { + _test_wireguard_change(NM_PLATFORM_GET, plink->ifindex, test_data->test_mode); + if (_LOGD_ENABLED()) + _system("WG_HIDE_KEYS=never wg show all"); + } + break; + } + default: + g_assert_not_reached(); + } + } + + nmtstp_link_delete(NULL, -1, ifindex, DEVICE_NAME, TRUE); +out_delete_parent: + nmtstp_link_delete(NULL, -1, ifindex_parent, PARENT_NAME, TRUE); +} + +static void +test_software_detect_add(const char *testpath, NMLinkType link_type, int test_mode) +{ + TestAddSoftwareDetectData *test_data; + char * path; + + test_data = g_new0(TestAddSoftwareDetectData, 1); + test_data->link_type = link_type; + test_data->test_mode = test_mode; + test_data->external_command = TRUE; + + path = g_strdup_printf("%s/external", testpath); + g_test_add_data_func_full(path, test_data, test_software_detect, g_free); + g_free(path); + + test_data = g_new0(TestAddSoftwareDetectData, 1); + test_data->link_type = link_type; + test_data->test_mode = test_mode; + test_data->external_command = FALSE; + + path = g_strdup_printf("%s/platform", testpath); + g_test_add_data_func_full(path, test_data, test_software_detect, g_free); + g_free(path); + + test_data = g_new0(TestAddSoftwareDetectData, 1); + test_data->link_type = link_type; + test_data->test_mode = test_mode; + test_data->external_command = -1; + + path = g_strdup_printf("%s/random", testpath); + g_test_add_data_func_full(path, test_data, test_software_detect, g_free); + g_free(path); +} + +/*****************************************************************************/ + +static void +_assert_xgress_qos_mappings_impl(int ifindex, gboolean is_ingress_map, int n_entries, int n, ...) +{ + const NMPlatformLink * plink; + const NMPObject * lnk; + guint n_map; + const NMVlanQosMapping *map; + va_list ap; + guint i; + + lnk = nm_platform_link_get_lnk(NM_PLATFORM_GET, ifindex, NM_LINK_TYPE_VLAN, &plink); + + g_assert(plink); + g_assert_cmpint(plink->ifindex, ==, ifindex); + g_assert(lnk); + g_assert(&lnk->lnk_vlan == nm_platform_link_get_lnk_vlan(NM_PLATFORM_GET, ifindex, NULL)); + + if (nmtst_is_debug()) + nmtstp_run_command_check("ip -d link show %s", plink->name); + + if (is_ingress_map) { + map = lnk->_lnk_vlan.ingress_qos_map; + n_map = lnk->_lnk_vlan.n_ingress_qos_map; + } else { + map = lnk->_lnk_vlan.egress_qos_map; + n_map = lnk->_lnk_vlan.n_egress_qos_map; + } + + if (n_entries != -1) + g_assert_cmpint(n_map, ==, n_entries); + + for (i = 0; i < n_map; i++) { + if (is_ingress_map) { + g_assert_cmpint(map[i].from, >=, 0); + g_assert_cmpint(map[i].from, <=, 7); + } + if (i > 0) + g_assert_cmpint(map[i - 1].from, <, map[i].from); + } + + va_start(ap, n); + for (; n > 0; n--) { + gboolean found = FALSE; + guint from = va_arg(ap, guint); + guint to = va_arg(ap, guint); + + for (i = 0; i < n_map; i++) { + if (map[i].from == from) { + g_assert(!found); + found = TRUE; + + g_assert(map[i].to == to); + } + } + g_assert(found); + } + va_end(ap); +} +#define _assert_xgress_qos_mappings(ifindex, is_ingress_map, n_entries, ...) \ + _assert_xgress_qos_mappings_impl( \ + (ifindex), \ + (is_ingress_map), \ + (n_entries), \ + (G_STATIC_ASSERT_EXPR((NM_NARG(__VA_ARGS__) % 2) == 0), NM_NARG(__VA_ARGS__) / 2), \ + __VA_ARGS__) +#define _assert_ingress_qos_mappings(ifindex, n_entries, ...) \ + _assert_xgress_qos_mappings(ifindex, TRUE, n_entries, __VA_ARGS__) +#define _assert_egress_qos_mappings(ifindex, n_entries, ...) \ + _assert_xgress_qos_mappings(ifindex, FALSE, n_entries, __VA_ARGS__) + +static void +_assert_vlan_flags(int ifindex, NMVlanFlags flags) +{ + const NMPlatformLnkVlan *plnk; + + plnk = nm_platform_link_get_lnk_vlan(NM_PLATFORM_GET, ifindex, NULL); + g_assert(plnk); + g_assert_cmpint(plnk->flags, ==, flags); +} + +static void +test_vlan_set_xgress(void) +{ + int ifindex, ifindex_parent; + + nmtstp_run_command_check("ip link add %s type dummy", PARENT_NAME); + ifindex_parent = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, PARENT_NAME, NM_LINK_TYPE_DUMMY, 100)->ifindex; + + nmtstp_run_command_check("ip link add name %s link %s type vlan id 1245", + DEVICE_NAME, + PARENT_NAME); + ifindex = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, DEVICE_NAME, NM_LINK_TYPE_VLAN, 100)->ifindex; + + /* ingress-qos-map */ + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 4, 5)); + _assert_ingress_qos_mappings(ifindex, 1, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 3, 7)); + _assert_ingress_qos_mappings(ifindex, 2, 3, 7, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 3, 8)); + _assert_ingress_qos_mappings(ifindex, 2, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 0, 4)); + _assert_ingress_qos_mappings(ifindex, 3, 0, 4, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 0, G_MAXUINT32)); + _assert_ingress_qos_mappings(ifindex, 3, 0, G_MAXUINT32, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 0, G_MAXUINT32 - 1)); + _assert_ingress_qos_mappings(ifindex, 3, 0, G_MAXUINT32 - 1, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 0, 5)); + _assert_ingress_qos_mappings(ifindex, 3, 0, 5, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 0, 5)); + _assert_ingress_qos_mappings(ifindex, 3, 0, 5, 3, 8, 4, 5); + + /* Set invalid values: */ + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 8, 3)); + _assert_ingress_qos_mappings(ifindex, 3, 0, 5, 3, 8, 4, 5); + + g_assert(nm_platform_link_vlan_set_ingress_map(NM_PLATFORM_GET, ifindex, 9, 4)); + _assert_ingress_qos_mappings(ifindex, 3, 0, 5, 3, 8, 4, 5); + + /* egress-qos-map */ + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 7, 3)); + _assert_egress_qos_mappings(ifindex, 1, 7, 3); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 8, 4)); + _assert_egress_qos_mappings(ifindex, 2, 7, 3, 8, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 0, 4)); + _assert_egress_qos_mappings(ifindex, 3, 0, 4, 7, 3, 8, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 1, 4)); + _assert_egress_qos_mappings(ifindex, 4, 0, 4, 1, 4, 7, 3, 8, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 1, 5)); + _assert_egress_qos_mappings(ifindex, 4, 0, 4, 1, 5, 7, 3, 8, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 9, 5)); + _assert_egress_qos_mappings(ifindex, 5, 0, 4, 1, 5, 7, 3, 8, 4, 9, 5); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 8, 5)); + _assert_egress_qos_mappings(ifindex, 5, 0, 4, 1, 5, 7, 3, 8, 5, 9, 5); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 8, 0)); + _assert_egress_qos_mappings(ifindex, 4, 0, 4, 1, 5, 7, 3, 9, 5); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 0, 0)); + _assert_egress_qos_mappings(ifindex, 3, 1, 5, 7, 3, 9, 5); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 100, 4)); + _assert_egress_qos_mappings(ifindex, 4, 1, 5, 7, 3, 9, 5, 100, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, G_MAXUINT32, 4)); + _assert_egress_qos_mappings(ifindex, 5, 1, 5, 7, 3, 9, 5, 100, 4, G_MAXUINT32, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, G_MAXUINT32, 8)); + _assert_egress_qos_mappings(ifindex, 5, 1, 5, 7, 3, 9, 5, 100, 4, G_MAXUINT32, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, G_MAXUINT32, 0)); + _assert_egress_qos_mappings(ifindex, 4, 1, 5, 7, 3, 9, 5, 100, 4); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 100, 0)); + _assert_egress_qos_mappings(ifindex, 3, 1, 5, 7, 3, 9, 5); + + g_assert(nm_platform_link_vlan_set_egress_map(NM_PLATFORM_GET, ifindex, 1, 0)); + _assert_egress_qos_mappings(ifindex, 2, 7, 3, 9, 5); + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 1, .to = 5}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + TRUE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + NULL, + 0)); + _assert_ingress_qos_mappings(ifindex, 1, 1, 5); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 3, .to = 5}, + {.from = 7, .to = 1655}, + {.from = 7, .to = 17655}, + {.from = 5, .to = 754}, + {.from = 4, .to = 12}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + TRUE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + NULL, + 0)); + _assert_ingress_qos_mappings(ifindex, 4, 3, 5, 4, 12, 7, 17655, 5, 754); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 3, .to = 18}, + {.from = 6, .to = 121}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + NULL, + 0)); + _assert_ingress_qos_mappings(ifindex, 5, 3, 18, 4, 12, 6, 121, 7, 17655, 5, 754); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 3, .to = 0}, + {.from = 6, .to = 7}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + TRUE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + NULL, + 0)); + _assert_ingress_qos_mappings(ifindex, 1, 6, 7); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 1, .to = 5}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + TRUE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + NULL, + 0)); + _assert_ingress_qos_mappings(ifindex, 1, 1, 5); + } + + { + const NMVlanQosMapping egress_map[] = { + {.from = 5, .to = 1}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + NULL, + 0, + TRUE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_egress_qos_mappings(ifindex, 1, 5, 1); + } + + { + const NMVlanQosMapping egress_map[] = { + {.from = 5, .to = 3}, + {.from = 1655, .to = 5}, + {.from = 1655, .to = 7}, + {.from = G_MAXUINT32, .to = 6}, + {.from = G_MAXUINT32, .to = 8}, + {.from = 754, .to = 4}, + {.from = 3, .to = 2}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + NULL, + 0, + TRUE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_egress_qos_mappings(ifindex, 5, 3, 2, 5, 3, 754, 4, 1655, 7, G_MAXUINT32, 6); + } + + { + const NMVlanQosMapping egress_map[] = { + {.from = 754, .to = 3}, + {.from = 755, .to = 8}, + {.from = 1655, .to = 0}, + {.from = 6, .to = 1}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + NULL, + 0, + FALSE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_egress_qos_mappings(ifindex, 5, 3, 2, 5, 3, 6, 1, 754, 3, G_MAXUINT32, 6); + } + + { + const NMVlanQosMapping egress_map[] = { + {.from = 6, .to = 0}, + {.from = 3, .to = 4}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + NULL, + 0, + TRUE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_egress_qos_mappings(ifindex, 1, 3, 4); + } + + { + const NMVlanQosMapping egress_map[] = { + {.from = 1, .to = 5}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + 0, + 0, + FALSE, + NULL, + 0, + TRUE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_egress_qos_mappings(ifindex, 1, 1, 5); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 6, .to = 145}, + {.from = 4, .to = 1}, + {.from = 6, .to = 12}, + }; + const NMVlanQosMapping egress_map[] = { + {.from = 1, .to = 5}, + {.from = 3232, .to = 7}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP, + NM_VLAN_FLAG_REORDER_HEADERS, + TRUE, + ingress_map, + G_N_ELEMENTS(ingress_map), + TRUE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_ingress_qos_mappings(ifindex, 2, 4, 1, 6, 12); + _assert_egress_qos_mappings(ifindex, 2, 1, 5, 3232, 7); + _assert_vlan_flags(ifindex, NM_VLAN_FLAG_REORDER_HEADERS); + } + + { + const NMVlanQosMapping ingress_map[] = { + {.from = 6, .to = 145}, + {.from = 4, .to = 1}, + {.from = 6, .to = 12}, + }; + const NMVlanQosMapping egress_map[] = { + {.from = 1, .to = 7}, + {.from = 64, .to = 10}, + {.from = 64, .to = 10}, + {.from = 64, .to = 10}, + {.from = 64, .to = 10}, + {.from = 3232, .to = 0}, + {.from = 64, .to = 4}, + }; + + g_assert(nm_platform_link_vlan_change(NM_PLATFORM_GET, + ifindex, + NM_VLAN_FLAG_GVRP, + NM_VLAN_FLAG_GVRP, + FALSE, + ingress_map, + G_N_ELEMENTS(ingress_map), + FALSE, + egress_map, + G_N_ELEMENTS(egress_map))); + _assert_ingress_qos_mappings(ifindex, 2, 4, 1, 6, 12); + _assert_egress_qos_mappings(ifindex, 2, 1, 7, 64, 4); + _assert_vlan_flags(ifindex, NM_VLAN_FLAG_REORDER_HEADERS | NM_VLAN_FLAG_GVRP); + } + + nmtstp_link_delete(NULL, -1, ifindex, DEVICE_NAME, TRUE); + nmtstp_link_delete(NULL, -1, ifindex_parent, PARENT_NAME, TRUE); +} + +/*****************************************************************************/ + +static void +test_create_many_links_do(guint n_devices) +{ + gint64 time, start_time = nm_utils_get_monotonic_timestamp_nsec(); + guint i; + char name[64]; + const NMPlatformLink *pllink; + gs_unref_array GArray *ifindexes = g_array_sized_new(FALSE, FALSE, sizeof(int), n_devices); + const int EX = ((int) (nmtst_get_rand_uint32() % 4)) - 1; + + g_assert(EX >= -1 && EX <= 2); + + _LOGI(">>> create devices (EX=%d)...", EX); + + for (i = 0; i < n_devices; i++) { + nm_sprintf_buf(name, "t-%05u", i); + if (EX == 2) { + /* This mode is different from letting nmtstp_link_dummy_add() + * because in this case we don't process any platform events + * while adding all the links. */ + nmtstp_run_command_check("ip link add %s type dummy", name); + } else + nmtstp_link_dummy_add(NULL, EX, name); + } + + _LOGI(">>> process events after creating devices..."); + + nm_platform_process_events(NM_PLATFORM_GET); + + _LOGI(">>> check devices..."); + + for (i = 0; i < n_devices; i++) { + nm_sprintf_buf(name, "t-%05u", i); + + pllink = nm_platform_link_get_by_ifname(NM_PLATFORM_GET, name); + g_assert(pllink); + g_assert_cmpint(pllink->type, ==, NM_LINK_TYPE_DUMMY); + g_assert_cmpstr(pllink->name, ==, name); + + g_array_append_val(ifindexes, pllink->ifindex); + } + + _LOGI(">>> delete devices..."); + + g_assert_cmpint(ifindexes->len, ==, n_devices); + for (i = 0; i < n_devices; i++) { + nm_sprintf_buf(name, "t-%05u", i); + + if (EX == 2) + nmtstp_run_command_check("ip link delete %s", name); + else + nmtstp_link_delete(NULL, EX, g_array_index(ifindexes, int, i), name, TRUE); + } + + _LOGI(">>> process events after deleting devices..."); + nm_platform_process_events(NM_PLATFORM_GET); + + time = nm_utils_get_monotonic_timestamp_nsec() - start_time; + _LOGI(">>> finished in %ld.%09ld seconds", + (long) (time / NM_UTILS_NSEC_PER_SEC), + (long) (time % NM_UTILS_NSEC_PER_SEC)); +} + +static void +test_create_many_links(gconstpointer user_data) +{ + guint n_devices = GPOINTER_TO_UINT(user_data); + + if (n_devices > 100 && nmtst_test_quick()) { + g_print("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", + g_get_prgname() ?: "test-link-linux"); + g_test_skip("Skip long running test"); + return; + } + + test_create_many_links_do(n_devices); +} + +/*****************************************************************************/ + +static void +test_nl_bugs_veth(void) +{ + const char * IFACE_VETH0 = "nm-test-veth0"; + const char * IFACE_VETH1 = "nm-test-veth1"; + int ifindex_veth0, ifindex_veth1; + int i; + const NMPlatformLink *pllink_veth0, *pllink_veth1; + gs_free_error GError * error = NULL; + NMTstpNamespaceHandle *ns_handle = NULL; + + /* create veth pair. */ + ifindex_veth0 = nmtstp_link_veth_add(NM_PLATFORM_GET, -1, IFACE_VETH0, IFACE_VETH1)->ifindex; + ifindex_veth1 = + nmtstp_link_get_typed(NM_PLATFORM_GET, -1, IFACE_VETH1, NM_LINK_TYPE_VETH)->ifindex; + + /* assert that nm_platform_link_veth_get_properties() returns the expected peer ifindexes. */ + g_assert(nm_platform_link_veth_get_properties(NM_PLATFORM_GET, ifindex_veth0, &i)); + g_assert_cmpint(i, ==, ifindex_veth1); + + g_assert(nm_platform_link_veth_get_properties(NM_PLATFORM_GET, ifindex_veth1, &i)); + g_assert_cmpint(i, ==, ifindex_veth0); + + /* assert that NMPlatformLink.parent is the peer-ifindex. */ + pllink_veth0 = nm_platform_link_get(NM_PLATFORM_GET, ifindex_veth0); + g_assert(pllink_veth0); + if (pllink_veth0->parent == 0) { + /* Kernels prior to 4.1 dated 21 June, 2015 don't support exposing the veth peer + * as IFA_LINK. skip the remainder of the test. */ + goto out; + } + g_assert_cmpint(pllink_veth0->parent, ==, ifindex_veth1); + + /* The following tests whether we have a workaround for kernel bug + * https://bugzilla.redhat.com/show_bug.cgi?id=1285827 in place. */ + pllink_veth1 = nm_platform_link_get(NM_PLATFORM_GET, ifindex_veth1); + g_assert(pllink_veth1); + g_assert_cmpint(pllink_veth1->parent, ==, ifindex_veth0); + + /* move one veth peer to another namespace and check that the + * parent/IFLA_LINK of the remaining peer properly updates + * (https://bugzilla.redhat.com/show_bug.cgi?id=1262908). */ + ns_handle = nmtstp_namespace_create(CLONE_NEWNET, &error); + g_assert_no_error(error); + g_assert(ns_handle); + + nmtstp_run_command_check("ip link set %s netns %ld", + IFACE_VETH1, + (long) nmtstp_namespace_handle_get_pid(ns_handle)); + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); + nm_platform_process_events(NM_PLATFORM_GET); + + pllink_veth1 = nm_platform_link_get(NM_PLATFORM_GET, ifindex_veth1); + pllink_veth0 = nm_platform_link_get(NM_PLATFORM_GET, ifindex_veth0); + if (!pllink_veth1 && pllink_veth0 && pllink_veth0->parent == NM_PLATFORM_LINK_OTHER_NETNS) { + break; + } + }); + +out: + nmtstp_link_delete(NULL, -1, ifindex_veth0, IFACE_VETH0, TRUE); + g_assert(!nmtstp_link_get(NM_PLATFORM_GET, ifindex_veth0, IFACE_VETH0)); + g_assert(!nmtstp_link_get(NM_PLATFORM_GET, ifindex_veth1, IFACE_VETH1)); + nmtstp_namespace_handle_release(ns_handle); +} + +/*****************************************************************************/ + +static void +test_nl_bugs_spuroius_newlink(void) +{ + const char * IFACE_BOND0 = "nm-test-bond0"; + const char * IFACE_DUMMY0 = "nm-test-dummy0"; + int ifindex_bond0, ifindex_dummy0; + const NMPlatformLink *pllink; + gboolean wait_for_settle; + + /* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */ + + nmtstp_run_command_check("ip link add %s type dummy", IFACE_DUMMY0); + ifindex_dummy0 = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100) + ->ifindex; + + nmtstp_run_command_check("ip link add %s type bond", IFACE_BOND0); + ifindex_bond0 = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, IFACE_BOND0, NM_LINK_TYPE_BOND, 100)->ifindex; + + nmtstp_link_set_updown(NULL, -1, ifindex_bond0, TRUE); + + nmtstp_run_command_check("ip link set %s master %s", IFACE_DUMMY0, IFACE_BOND0); + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); + + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_dummy0); + g_assert(pllink); + if (pllink->master == ifindex_bond0) + break; + }); + + nmtstp_run_command_check("ip link del %s", IFACE_BOND0); + + wait_for_settle = TRUE; + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); +again: + nm_platform_process_events(NM_PLATFORM_GET); + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_bond0); + g_assert(!pllink); + + if (wait_for_settle) { + wait_for_settle = FALSE; + NMTST_WAIT(300, { nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); }); + goto again; + } + + g_assert(!nmtstp_link_get(NM_PLATFORM_GET, ifindex_bond0, IFACE_BOND0)); + nmtstp_link_delete(NULL, -1, ifindex_dummy0, IFACE_DUMMY0, TRUE); +} + +/*****************************************************************************/ + +static void +test_nl_bugs_spuroius_dellink(void) +{ + const char * IFACE_BRIDGE0 = "nm-test-bridge0"; + const char * IFACE_DUMMY0 = "nm-test-dummy0"; + int ifindex_bridge0, ifindex_dummy0; + const NMPlatformLink *pllink; + gboolean wait_for_settle; + + /* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */ + + nmtstp_run_command_check("ip link add %s type dummy", IFACE_DUMMY0); + ifindex_dummy0 = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, IFACE_DUMMY0, NM_LINK_TYPE_DUMMY, 100) + ->ifindex; + + nmtstp_run_command_check("ip link add %s type bridge", IFACE_BRIDGE0); + ifindex_bridge0 = + nmtstp_assert_wait_for_link(NM_PLATFORM_GET, IFACE_BRIDGE0, NM_LINK_TYPE_BRIDGE, 100) + ->ifindex; + + nmtstp_link_set_updown(NULL, -1, ifindex_bridge0, TRUE); + + nmtstp_run_command_check("ip link set %s master %s", IFACE_DUMMY0, IFACE_BRIDGE0); + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); + + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_dummy0); + g_assert(pllink); + if (pllink->master == ifindex_bridge0) + break; + }); + + nm_platform_process_events(NM_PLATFORM_GET); + + nmtstp_run_command_check("ip link set %s nomaster", IFACE_DUMMY0); + + wait_for_settle = TRUE; + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); +again: + nm_platform_process_events(NM_PLATFORM_GET); + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_bridge0); + g_assert(pllink); + pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_dummy0); + g_assert(pllink); + g_assert_cmpint(pllink->parent, ==, 0); + + if (wait_for_settle) { + wait_for_settle = FALSE; + NMTST_WAIT(300, { nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); }); + goto again; + } + + nmtstp_link_delete(NULL, -1, ifindex_bridge0, IFACE_BRIDGE0, TRUE); + nmtstp_link_delete(NULL, -1, ifindex_dummy0, IFACE_DUMMY0, TRUE); +} + +/*****************************************************************************/ + +static void +_test_netns_setup(gpointer fixture, gconstpointer test_data) +{ + /* the singleton platform instance has netns support disabled. + * Destroy the instance before the test and re-create it afterwards. */ + g_object_unref(NM_PLATFORM_GET); +} + +static void +_test_netns_teardown(gpointer fixture, gconstpointer test_data) +{ + nmtstp_setup_platform(); +} + +static NMPlatform * +_test_netns_create_platform(void) +{ + NMPNetns * netns; + NMPlatform *platform; + + netns = nmp_netns_new(); + g_assert(NMP_IS_NETNS(netns)); + + platform = nm_linux_platform_new(TRUE, TRUE); + g_assert(NM_IS_LINUX_PLATFORM(platform)); + + nmp_netns_pop(netns); + g_object_unref(netns); + + return platform; +} + +static gboolean +_test_netns_check_skip(void) +{ + static int support = -1; + static int support_errsv = 0; + NMPNetns * netns; + gs_unref_object NMPNetns *netns2 = NULL; + + netns = nmp_netns_get_current(); + if (!netns) { + g_test_skip("No netns support"); + return TRUE; + } + + g_assert(nmp_netns_get_fd_net(netns) > 0); + + if (support == -1) { + support = (setns(nmp_netns_get_fd_net(netns), CLONE_NEWNET) == 0); + if (!support) + support_errsv = errno; + } + if (!support) { + _LOGD("setns() failed with \"%s\". This indicates missing support (valgrind?)", + nm_strerror_native(support_errsv)); + g_test_skip("No netns support (setns failed)"); + return TRUE; + } + + netns2 = nmp_netns_new(); + if (!netns2) { + /* skip tests for https://bugzilla.gnome.org/show_bug.cgi?id=790214 */ + g_assert_cmpint(errno, ==, EINVAL); + g_test_skip("No netns support to create another netns"); + return TRUE; + } + nmp_netns_pop(netns2); + + return FALSE; +} + +static gboolean +_check_sysctl_skip(void) +{ + if (access("/proc/sys/net/ipv4/ip_forward", W_OK) == -1) { + g_test_skip("Can not write sysctls"); + return TRUE; + } + + return FALSE; +} + +/*****************************************************************************/ + +#define _sysctl_assert_eq(plat, path, value) \ + G_STMT_START \ + { \ + gs_free char *_val = NULL; \ + \ + _val = nm_platform_sysctl_get(plat, NMP_SYSCTL_PATHID_ABSOLUTE(path)); \ + g_assert_cmpstr(_val, ==, value); \ + } \ + G_STMT_END + +static void +test_netns_general(gpointer fixture, gconstpointer test_data) +{ + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + NMPNetns * netns_tmp; + char sbuf[100]; + int i, j, k; + gboolean ethtool_support; + NMPUtilsEthtoolDriverInfo driver_info; + + if (_test_netns_check_skip()) + return; + + if (_check_sysctl_skip()) + return; + + platform_1 = nm_linux_platform_new(TRUE, TRUE); + platform_2 = _test_netns_create_platform(); + + /* add some dummy devices. The "other-*" devices are there to bump the ifindex */ + for (k = 0; k < 2; k++) { + NMPlatform *p = (k == 0 ? platform_1 : platform_2); + const char *id = (k == 0 ? "a" : "b"); + + for (i = 0, j = nmtst_get_rand_uint32() % 5; i < j; i++) + _ADD_DUMMY(p, nm_sprintf_buf(sbuf, "other-a-%s-%02d", id, i)); + + _ADD_DUMMY(p, "dummy1_"); + + for (i = 0, j = nmtst_get_rand_uint32() % 5; i < j; i++) + _ADD_DUMMY(p, nm_sprintf_buf(sbuf, "other-b-%s-%02d", id, i)); + + _ADD_DUMMY(p, nm_sprintf_buf(sbuf, "dummy2%s", id)); + + for (i = 0, j = nmtst_get_rand_uint32() % 5; i < j; i++) + _ADD_DUMMY(p, nm_sprintf_buf(sbuf, "other-c-%s-%02d", id, i)); + } + + _sysctl_assert_eq( + platform_1, + "/sys/devices/virtual/net/dummy1_/ifindex", + nm_sprintf_buf( + sbuf, + "%d", + nmtstp_link_get_typed(platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex)); + _sysctl_assert_eq( + platform_1, + "/sys/devices/virtual/net/dummy2a/ifindex", + nm_sprintf_buf( + sbuf, + "%d", + nmtstp_link_get_typed(platform_1, 0, "dummy2a", NM_LINK_TYPE_DUMMY)->ifindex)); + _sysctl_assert_eq(platform_1, "/sys/devices/virtual/net/dummy2b/ifindex", NULL); + + _sysctl_assert_eq( + platform_2, + "/sys/devices/virtual/net/dummy1_/ifindex", + nm_sprintf_buf( + sbuf, + "%d", + nmtstp_link_get_typed(platform_2, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex)); + _sysctl_assert_eq(platform_2, "/sys/devices/virtual/net/dummy2a/ifindex", NULL); + _sysctl_assert_eq( + platform_2, + "/sys/devices/virtual/net/dummy2b/ifindex", + nm_sprintf_buf( + sbuf, + "%d", + nmtstp_link_get_typed(platform_2, 0, "dummy2b", NM_LINK_TYPE_DUMMY)->ifindex)); + + for (i = 0; i < 10; i++) { + NMPlatform *pl; + const char *path; + + j = nmtst_get_rand_uint32() % 2; + + if (nmtst_get_rand_uint32() % 2) { + pl = platform_1; + if (nmtst_get_rand_uint32() % 2) + path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6"; + else + path = "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6"; + } else { + pl = platform_2; + if (nmtst_get_rand_uint32() % 2) + path = "/proc/sys/net/ipv6/conf/dummy1_/disable_ipv6"; + else + path = "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6"; + } + g_assert(nm_platform_sysctl_set(pl, + NMP_SYSCTL_PATHID_ABSOLUTE(path), + nm_sprintf_buf(sbuf, "%d", j))); + _sysctl_assert_eq(pl, path, nm_sprintf_buf(sbuf, "%d", j)); + } + + _sysctl_assert_eq(platform_1, "/proc/sys/net/ipv6/conf/dummy2b/disable_ipv6", NULL); + _sysctl_assert_eq(platform_2, "/proc/sys/net/ipv6/conf/dummy2a/disable_ipv6", NULL); + + /* Kernels prior to 3.19 dated 8 February, 2015 don't support ethtool -i for dummy devices. + * Work around that and skip asserts that are known to fail. */ + ethtool_support = nmtstp_run_command("ethtool -i dummy1_ > /dev/null") == 0; + if (ethtool_support) { + g_assert(nmp_utils_ethtool_get_driver_info( + nmtstp_link_get_typed(platform_1, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex, + &driver_info)); + g_assert(nmp_utils_ethtool_get_driver_info( + nmtstp_link_get_typed(platform_1, 0, "dummy2a", NM_LINK_TYPE_DUMMY)->ifindex, + &driver_info)); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy1_ > /dev/null"), ==, 0); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy2a > /dev/null"), ==, 0); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy2b 2> /dev/null"), !=, 0); + } + + g_assert(nm_platform_netns_push(platform_2, &netns_tmp)); + + if (ethtool_support) { + g_assert(nmp_utils_ethtool_get_driver_info( + nmtstp_link_get_typed(platform_2, 0, "dummy1_", NM_LINK_TYPE_DUMMY)->ifindex, + &driver_info)); + g_assert(nmp_utils_ethtool_get_driver_info( + nmtstp_link_get_typed(platform_2, 0, "dummy2b", NM_LINK_TYPE_DUMMY)->ifindex, + &driver_info)); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy1_ > /dev/null"), ==, 0); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy2a 2> /dev/null"), !=, 0); + g_assert_cmpint(nmtstp_run_command("ethtool -i dummy2b > /dev/null"), ==, 0); + } + + nmp_netns_pop(netns_tmp); +} + +/*****************************************************************************/ + +static void +test_netns_set_netns(gpointer fixture, gconstpointer test_data) +{ + NMPlatform * platforms[3]; + gs_unref_object NMPlatform *platform_0 = NULL; + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop = NULL; + + if (_test_netns_check_skip()) + return; + + platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE); + platforms[1] = platform_1 = _test_netns_create_platform(); + platforms[2] = platform_2 = _test_netns_create_platform(); + + nmtstp_netns_select_random(platforms, G_N_ELEMENTS(platforms), &netns_pop); + +#define LINK_MOVE_NAME "link-move" + g_assert(!nm_platform_link_get_by_ifname(platform_1, LINK_MOVE_NAME)); + g_assert(!nm_platform_link_get_by_ifname(platform_2, LINK_MOVE_NAME)); + _ADD_DUMMY(platform_1, LINK_MOVE_NAME); + g_assert(nm_platform_link_get_by_ifname(platform_1, LINK_MOVE_NAME)); + g_assert(!nm_platform_link_get_by_ifname(platform_2, LINK_MOVE_NAME)); + g_assert(nm_platform_link_set_netns( + platform_1, + nm_platform_link_get_by_ifname(platform_1, LINK_MOVE_NAME)->ifindex, + nmp_netns_get_fd_net(nm_platform_netns_get(platform_2)))); + g_assert(!nm_platform_link_get_by_ifname(platform_1, LINK_MOVE_NAME)); + g_assert(!nm_platform_link_get_by_ifname(platform_2, LINK_MOVE_NAME)); + nmtstp_assert_wait_for_link(platform_2, LINK_MOVE_NAME, NM_LINK_TYPE_DUMMY, 100); + g_assert(!nm_platform_link_get_by_ifname(platform_1, LINK_MOVE_NAME)); + g_assert(nm_platform_link_get_by_ifname(platform_2, LINK_MOVE_NAME)); +} + +/*****************************************************************************/ + +static char * +_get_current_namespace_id(int ns_type) +{ + const char *p; + GError * error = NULL; + char * id; + + switch (ns_type) { + case CLONE_NEWNET: + p = "/proc/self/ns/net"; + break; + case CLONE_NEWNS: + p = "/proc/self/ns/mnt"; + break; + default: + g_assert_not_reached(); + } + + id = g_file_read_link(p, &error); + g_assert_no_error(error); + g_assert(id); + return id; +} + +static char * +_get_sysctl_value(const char *path) +{ + char * data = NULL; + gs_free_error GError *error = NULL; + + if (!g_file_get_contents(path, &data, NULL, &error)) { + nmtst_assert_error(error, G_FILE_ERROR, G_FILE_ERROR_NOENT, NULL); + g_assert(!data); + } else { + g_assert_no_error(error); + g_assert(data); + g_strstrip(data); + } + return data; +} + +static void +test_netns_push(gpointer fixture, gconstpointer test_data) +{ + gs_unref_object NMPlatform *platform_0 = NULL; + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop = NULL; + gs_unref_ptrarray GPtrArray *device_names = g_ptr_array_new_with_free_func(g_free); + int i, j; + const int ns_types_list[] = {CLONE_NEWNET, CLONE_NEWNS, CLONE_NEWNET | CLONE_NEWNS}; + const int ns_types_test[] = {CLONE_NEWNET, CLONE_NEWNS}; + typedef struct { + NMPlatform *platform; + const char *device_name; + const char *sysctl_path; + const char *sysctl_value; + const char *ns_net; + const char *ns_mnt; + } PlatformData; + PlatformData pl[3] = {}; + PlatformData *pl_base; + struct { + PlatformData *pl; + int ns_types; + } stack[6] = {}; + int nstack; + + if (_test_netns_check_skip()) + return; + + if (_check_sysctl_skip()) + return; + + pl[0].platform = platform_0 = nm_linux_platform_new(TRUE, TRUE); + pl[1].platform = platform_1 = _test_netns_create_platform(); + pl[2].platform = platform_2 = _test_netns_create_platform(); + + pl_base = &pl[0]; + i = nmtst_get_rand_uint32() % (G_N_ELEMENTS(pl) + 1); + if (i < G_N_ELEMENTS(pl)) { + pl_base = &pl[i]; + g_assert(nm_platform_netns_push(pl[i].platform, &netns_pop)); + } + + for (i = 0; i < G_N_ELEMENTS(pl); i++) { + nm_auto_pop_netns NMPNetns *netns_free = NULL; + char * tmp; + + g_assert(nm_platform_netns_push(pl[i].platform, &netns_free)); + + tmp = g_strdup_printf("nmtst-dev-%d", i); + g_ptr_array_add(device_names, tmp); + pl[i].device_name = tmp; + + tmp = g_strdup_printf("/proc/sys/net/ipv6/conf/%s/disable_ipv6", pl[i].device_name); + g_ptr_array_add(device_names, tmp); + pl[i].sysctl_path = tmp; + + pl[i].sysctl_value = nmtst_get_rand_uint32() % 2 ? "1" : "0"; + + _ADD_DUMMY(pl[i].platform, pl[i].device_name); + + g_assert(nm_platform_sysctl_set(pl[i].platform, + NMP_SYSCTL_PATHID_ABSOLUTE(pl[i].sysctl_path), + pl[i].sysctl_value)); + + tmp = _get_current_namespace_id(CLONE_NEWNET); + g_ptr_array_add(device_names, tmp); + pl[i].ns_net = tmp; + + tmp = _get_current_namespace_id(CLONE_NEWNS); + g_ptr_array_add(device_names, tmp); + pl[i].ns_mnt = tmp; + } + + nstack = nmtst_get_rand_uint32() % (G_N_ELEMENTS(stack) + 1); + for (i = 0; i < nstack; i++) { + stack[i].pl = &pl[nmtst_get_rand_uint32() % G_N_ELEMENTS(pl)]; + stack[i].ns_types = ns_types_list[nmtst_get_rand_uint32() % G_N_ELEMENTS(ns_types_list)]; + + nmp_netns_push_type(nm_platform_netns_get(stack[i].pl->platform), stack[i].ns_types); + } + + /* pop some again. */ + for (i = nmtst_get_rand_uint32() % (nstack + 1); i > 0; i--) { + g_assert(nstack > 0); + nstack--; + nmp_netns_pop(nm_platform_netns_get(stack[nstack].pl->platform)); + } + + for (i = 0; i < G_N_ELEMENTS(ns_types_test); i++) { + int ns_type = ns_types_test[i]; + PlatformData *p; + gs_free char *current_namespace_id = NULL; + + p = pl_base; + for (j = nstack; j >= 1;) { + j--; + if (NM_FLAGS_ANY(stack[j].ns_types, ns_type)) { + p = stack[j].pl; + break; + } + } + + current_namespace_id = _get_current_namespace_id(ns_type); + + if (ns_type == CLONE_NEWNET) { + g_assert_cmpstr(current_namespace_id, ==, p->ns_net); + for (j = 0; j < G_N_ELEMENTS(pl); j++) { + gs_free char *data = NULL; + + if (p == &pl[j]) + g_assert_cmpint( + nmtstp_run_command("ip link show %s 1>/dev/null", pl[j].device_name), + ==, + 0); + else + g_assert_cmpint( + nmtstp_run_command("ip link show %s 2>/dev/null", pl[j].device_name), + !=, + 0); + + data = _get_sysctl_value(pl[j].sysctl_path); + if (p == &pl[j]) + g_assert_cmpstr(data, ==, pl[j].sysctl_value); + else + g_assert(!data); + } + } else if (ns_type == CLONE_NEWNS) { + g_assert_cmpstr(current_namespace_id, ==, p->ns_mnt); + for (j = 0; j < G_N_ELEMENTS(pl); j++) { + char path[600]; + gs_free char *data = NULL; + + nm_sprintf_buf(path, "/sys/devices/virtual/net/%s/ifindex", pl[j].device_name); + + data = _get_sysctl_value(path); + if (p == &pl[j]) + g_assert_cmpstr(data, + ==, + nm_sprintf_buf(path, + "%d", + nmtstp_link_get_typed(p->platform, + 0, + p->device_name, + NM_LINK_TYPE_DUMMY) + ->ifindex)); + else + g_assert(!data); + } + } else + g_assert_not_reached(); + } + + for (i = nstack; i >= 1;) { + i--; + nmp_netns_pop(nm_platform_netns_get(stack[i].pl->platform)); + } +} + +/*****************************************************************************/ + +static void +test_netns_bind_to_path(gpointer fixture, gconstpointer test_data) +{ +#define P_VAR_RUN "/run" +#define P_VAR_RUN_NETNS "/run/netns" +#define P_VAR_RUN_NETNS_BINDNAME "/run/netns/" P_NETNS_BINDNAME +#define P_NETNS_BINDNAME "nmtst-iproute2-netns" + gs_unref_object NMPlatform *platform_0 = NULL; + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop = NULL; + NMPlatform * platforms[3]; + NMPNetns * netns; + int i; + + if (_test_netns_check_skip()) + return; + + platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE); + platforms[1] = platform_1 = _test_netns_create_platform(); + platforms[2] = platform_2 = _test_netns_create_platform(); + + nmtstp_netns_select_random(platforms, G_N_ELEMENTS(platforms), &netns_pop); + + g_assert_cmpint( + mount("tmpfs", P_VAR_RUN, "tmpfs", MS_NOATIME | MS_NODEV | MS_NOSUID, "mode=0755,size=32K"), + ==, + 0); + g_assert_cmpint(mkdir(P_VAR_RUN_NETNS, 755), ==, 0); + + i = (nmtst_get_rand_uint32() % 2) + 1; + netns = nm_platform_netns_get(platforms[i]); + + _ADD_DUMMY(platforms[i], "dummy2b"); + + g_assert(!g_file_test(P_VAR_RUN_NETNS_BINDNAME, G_FILE_TEST_EXISTS)); + g_assert_cmpint(nmtstp_run_command("ip netns exec " P_NETNS_BINDNAME " true 2>/dev/null"), + !=, + 0); + + g_assert(nmp_netns_bind_to_path(netns, P_VAR_RUN_NETNS_BINDNAME, NULL)); + + g_assert(g_file_test(P_VAR_RUN_NETNS_BINDNAME, G_FILE_TEST_EXISTS)); + g_assert_cmpint(nmtstp_run_command("ip netns exec " P_NETNS_BINDNAME " true"), ==, 0); + g_assert_cmpint( + nmtstp_run_command("ip netns exec " P_NETNS_BINDNAME " ip link show dummy2b 1>/dev/null"), + ==, + 0); + + g_assert(nmp_netns_bind_to_path_destroy(netns, P_VAR_RUN_NETNS_BINDNAME)); + + g_assert(!g_file_test(P_VAR_RUN_NETNS_BINDNAME, G_FILE_TEST_EXISTS)); + g_assert_cmpint(nmtstp_run_command("ip netns exec " P_NETNS_BINDNAME " true 2>/dev/null"), + !=, + 0); + + g_assert_cmpint(umount(P_VAR_RUN), ==, 0); +} + +/*****************************************************************************/ + +static void +test_sysctl_rename(void) +{ + NMPlatform *const PL = NM_PLATFORM_GET; + const char *const IFNAME[3] = { + "nm-dummy-0", + "nm-dummy-1", + "nm-dummy-2", + }; + int ifindex[G_N_ELEMENTS(IFNAME)] = {0}; + nm_auto_close int dirfd = -1; + int i; + char ifname_buf[IFNAMSIZ]; + char * s; + const NMPlatformLink *pllink; + + ifindex[0] = nmtstp_link_dummy_add(PL, -1, IFNAME[0])->ifindex; + ifindex[1] = nmtstp_link_dummy_add(PL, -1, IFNAME[1])->ifindex; + + s = (nmtst_get_rand_uint32() % 2) ? NULL : ifname_buf; + + if (nmtst_get_rand_uint32() % 2) { + /* bring the platform cache out of sync */ + nmtstp_run_command_check("ip link set %s name %s", IFNAME[0], IFNAME[2]); + nm_platform_process_events(PL); + nmtstp_run_command_check("ip link set %s name %s", IFNAME[2], IFNAME[0]); + + pllink = nm_platform_link_get_by_ifname(PL, IFNAME[2]); + g_assert(pllink && pllink->ifindex == ifindex[0]); + pllink = nm_platform_link_get_by_ifname(PL, IFNAME[0]); + g_assert(!pllink); + } + + /* open dirfd for IFNAME[0] */ + i = nmtst_get_rand_uint32() % (2 + G_N_ELEMENTS(IFNAME)); + if (i == 0) { + dirfd = nm_platform_sysctl_open_netdir(PL, ifindex[0], s); + } else { + const char *ifname_guess; + + /* provide a wrong or no guess. */ + ifname_guess = i == 1 ? NULL : IFNAME[i - 2]; + dirfd = nmp_utils_sysctl_open_netdir(ifindex[0], ifname_guess, s); + } + g_assert(dirfd >= 0); + if (s) + g_assert_cmpstr(s, ==, IFNAME[0]); + + /* possibly rename the interfaces. */ + switch (nmtst_get_rand_uint32() % 4) { + case 0: + break; + case 1: + nmtstp_run_command_check("ip link set %s name %s", IFNAME[0], IFNAME[2]); + break; + case 2: + nmtstp_run_command_check("ip link set %s name %s", IFNAME[0], IFNAME[2]); + nmtstp_run_command_check("ip link set %s name %s", IFNAME[1], IFNAME[0]); + break; + } + + /* possibly, resync platform cache (should make no difference). */ + if (nmtst_get_rand_uint32() % 2) + nm_platform_process_events(PL); + + /* check that we still read the same file. */ + switch (nmtst_get_rand_uint32() % 2) { + case 0: + { + gs_free char *c = NULL; + + if (!nm_utils_file_get_contents(dirfd, + "ifindex", + 1 * 1024 * 1024, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &c, + NULL, + NULL, + NULL)) + g_assert_not_reached(); + g_assert_cmpint(ifindex[0], ==, (int) _nm_utils_ascii_str_to_int64(c, 10, 0, G_MAXINT, -1)); + break; + } + case 1: + { + g_assert_cmpint(ifindex[0], + ==, + (gint32) nm_platform_sysctl_get_int32( + PL, + NMP_SYSCTL_PATHID_NETDIR(dirfd, s ?: "<unknown>", "ifindex"), + -1)); + break; + } + } + + nm_platform_process_events(PL); + nmtstp_link_delete(PL, -1, ifindex[0], NULL, TRUE); + nmtstp_link_delete(PL, -1, ifindex[1], NULL, TRUE); +} + +/*****************************************************************************/ + +static void +test_sysctl_netns_switch(void) +{ + const char *const IFNAME = "nm-dummy-0"; + int ifindex, ifindex_tmp; + nm_auto_close int dirfd = -1; + char ifname_buf[IFNAMSIZ]; + char * s; + gs_unref_object NMPlatform *platform_0 = NULL; + gs_unref_object NMPlatform *platform_1 = NULL; + gs_unref_object NMPlatform *platform_2 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop_1 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop_2 = NULL; + nm_auto_pop_netns NMPNetns *netns_pop_3 = NULL; + NMPlatform * PL; + NMPlatform * platforms[3]; + + if (_test_netns_check_skip()) + return; + + platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE); + platforms[1] = platform_1 = _test_netns_create_platform(); + platforms[2] = platform_2 = _test_netns_create_platform(); + PL = platforms[nmtst_get_rand_uint32() % 3]; + + nmtstp_netns_select_random(platforms, G_N_ELEMENTS(platforms), &netns_pop_1); + + ifindex = nmtstp_link_dummy_add(PL, FALSE, IFNAME)->ifindex; + + nmtstp_netns_select_random(platforms, G_N_ELEMENTS(platforms), &netns_pop_2); + + s = (nmtst_get_rand_uint32() % 2) ? NULL : ifname_buf; + dirfd = nm_platform_sysctl_open_netdir(PL, ifindex, s); + g_assert(dirfd >= 0); + if (s) + g_assert_cmpstr(s, ==, IFNAME); + + nmtstp_netns_select_random(platforms, G_N_ELEMENTS(platforms), &netns_pop_3); + + /* even if we switch to other namespaces, we can still lookup the path correctly, + * either using dirfd or via the platform instance (which switches namespace as needed). */ + { + gs_free char *c = NULL; + + if (!nm_utils_file_get_contents(dirfd, + "ifindex", + 0, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &c, + NULL, + NULL, + NULL)) + g_assert_not_reached(); + g_assert_cmpint(ifindex, ==, (int) _nm_utils_ascii_str_to_int64(c, 10, 0, G_MAXINT, -1)); + } + g_assert_cmpint(ifindex, + ==, + (gint32) nm_platform_sysctl_get_int32( + PL, + NMP_SYSCTL_PATHID_NETDIR(dirfd, s ?: "<unknown>", "ifindex"), + -1)); + g_assert_cmpint( + ifindex, + ==, + (gint32) nm_platform_sysctl_get_int32( + PL, + NMP_SYSCTL_PATHID_ABSOLUTE(nm_sprintf_bufa(100, "/sys/class/net/%s/ifindex", IFNAME)), + -1)); + + /* also test that nm_platform_sysctl_get() sets errno to ENOENT for non-existing paths. */ + { + gint64 i64; + int errsv; + char * v; + + errno = ESRCH; + v = nm_platform_sysctl_get( + PL, + NMP_SYSCTL_PATHID_ABSOLUTE("/sys/devices/virtual/net/not-existing/ifindex")); + errsv = errno; + g_assert(!v); + g_assert_cmpint(errsv, ==, ENOENT); + + errno = ESRCH; + i64 = nm_platform_sysctl_get_int_checked( + PL, + NMP_SYSCTL_PATHID_ABSOLUTE("/sys/devices/virtual/net/not-existing/ifindex"), + 10, + 1, + G_MAXINT, + -1); + errsv = errno; + g_assert_cmpint(i64, ==, -1); + g_assert_cmpint(errsv, ==, ENOENT); + + errno = ESRCH; + v = nm_platform_sysctl_get( + PL, + NMP_SYSCTL_PATHID_ABSOLUTE("/sys/devices/virtual/net/lo/not-existing")); + errsv = errno; + g_assert(!v); + g_assert_cmpint(errsv, ==, ENOENT); + + errno = ESRCH; + i64 = nm_platform_sysctl_get_int_checked( + PL, + NMP_SYSCTL_PATHID_ABSOLUTE("/sys/devices/virtual/net/lo/not-existing"), + 10, + 1, + G_MAXINT, + -1); + errsv = errno; + g_assert_cmpint(i64, ==, -1); + g_assert_cmpint(errsv, ==, ENOENT); + } + + /* accessing the path directly, only succeeds iff the current namespace happens to be the namespace + * in which we created the link. */ + { + gs_free char *c = NULL; + + if (!nm_utils_file_get_contents(-1, + nm_sprintf_bufa(100, "/sys/class/net/%s/ifindex", IFNAME), + 0, + NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE, + &c, + NULL, + NULL, + NULL)) + ifindex_tmp = -1; + else + ifindex_tmp = _nm_utils_ascii_str_to_int64(c, 10, 0, G_MAXINT, -2); + } + if (nmp_netns_get_current() == nm_platform_netns_get(PL)) + g_assert_cmpint(ifindex_tmp, ==, ifindex); + else + g_assert_cmpint(ifindex_tmp, ==, -1); + + nmtstp_link_delete(PL, FALSE, ifindex, NULL, TRUE); +} + +typedef struct { + GMainLoop * loop; + const char *path; + gboolean expected_success; + gint32 expected_value; +} SetAsyncData; + +static void +sysctl_set_async_cb(GError *error, gpointer user_data) +{ + SetAsyncData *data = user_data; + + if (data->expected_success) { + g_assert_no_error(error); + g_assert_cmpint(nm_platform_sysctl_get_int32(NM_PLATFORM_GET, + NMP_SYSCTL_PATHID_ABSOLUTE(data->path), + -1), + ==, + data->expected_value); + } else + g_assert(error); + + g_main_loop_quit(data->loop); +} + +static void +test_sysctl_set_async(void) +{ + NMPlatform *const PL = NM_PLATFORM_GET; + const char *const IFNAME = "nm-dummy-0"; + const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/rp_filter"; + GMainLoop * loop; + gs_unref_object GCancellable *cancellable = NULL; + gboolean proc_writable; + SetAsyncData data; + int ifindex; + + ifindex = nmtstp_link_dummy_add(PL, -1, IFNAME)->ifindex; + loop = g_main_loop_new(NULL, FALSE); + cancellable = g_cancellable_new(); + proc_writable = access(PATH, W_OK) == 0; + + data = (SetAsyncData){ + .loop = loop, + .path = PATH, + .expected_success = proc_writable, + .expected_value = 2, + }; + + nm_platform_sysctl_set_async(PL, + NMP_SYSCTL_PATHID_ABSOLUTE(PATH), + (const char *[]){"2", NULL}, + sysctl_set_async_cb, + &data, + cancellable); + + if (!nmtst_main_loop_run(loop, 1000)) + g_assert_not_reached(); + + data = (SetAsyncData){ + .loop = loop, + .path = PATH, + .expected_success = proc_writable, + .expected_value = 1, + }; + + nm_platform_sysctl_set_async(PL, + NMP_SYSCTL_PATHID_ABSOLUTE(PATH), + (const char *[]){"2", "0", "1", "0", "1", NULL}, + sysctl_set_async_cb, + &data, + cancellable); + + if (!nmtst_main_loop_run(loop, 2000)) + g_assert_not_reached(); + + nmtstp_link_delete(NULL, -1, ifindex, IFNAME, TRUE); + g_main_loop_unref(loop); +} + +static void +test_sysctl_set_async_fail(void) +{ + NMPlatform *const PL = NM_PLATFORM_GET; + const char *const IFNAME = "nm-dummy-0"; + const char *const PATH = "/proc/sys/net/ipv4/conf/nm-dummy-0/does-not-exist"; + GMainLoop * loop; + gs_unref_object GCancellable *cancellable = NULL; + SetAsyncData data; + int ifindex; + + ifindex = nmtstp_link_dummy_add(PL, -1, IFNAME)->ifindex; + loop = g_main_loop_new(NULL, FALSE); + cancellable = g_cancellable_new(); + + data = (SetAsyncData){ + .loop = loop, + .path = PATH, + .expected_success = FALSE, + }; + + nm_platform_sysctl_set_async(PL, + NMP_SYSCTL_PATHID_ABSOLUTE(PATH), + (const char *[]){"2", NULL}, + sysctl_set_async_cb, + &data, + cancellable); + + if (!nmtst_main_loop_run(loop, 1000)) + g_assert_not_reached(); + + nmtstp_link_delete(NULL, -1, ifindex, IFNAME, TRUE); + g_main_loop_unref(loop); +} + +/*****************************************************************************/ + +static gpointer +_test_netns_mt_thread(gpointer data) +{ + NMPNetns * netns1 = data; + gs_unref_object NMPNetns *netns2 = NULL; + NMPNetns * netns_bottom; + NMPNetns * initial; + + netns_bottom = nmp_netns_get_initial(); + g_assert(netns_bottom); + + /* I don't know why, but we need to create a new netns here at least once. + * Otherwise, setns(, CLONE_NEWNS) below fails with EINVAL (???). + * + * Something is not right here, but what? */ + netns2 = nmp_netns_new(); + nmp_netns_pop(netns2); + g_clear_object(&netns2); + + nmp_netns_push(netns1); + nmp_netns_push_type(netns_bottom, CLONE_NEWNET); + nmp_netns_push_type(netns_bottom, CLONE_NEWNS); + nmp_netns_push_type(netns1, CLONE_NEWNS); + nmp_netns_pop(netns1); + nmp_netns_pop(netns_bottom); + nmp_netns_pop(netns_bottom); + nmp_netns_pop(netns1); + + initial = nmp_netns_get_initial(); + g_assert(NMP_IS_NETNS(initial)); + return g_object_ref(initial); +} + +static void +test_netns_mt(void) +{ + gs_unref_object NMPNetns *netns1 = NULL; + NMPNetns * initial_from_other_thread; + GThread * th; + + if (_test_netns_check_skip()) + return; + + netns1 = nmp_netns_new(); + g_assert(NMP_NETNS(netns1)); + nmp_netns_pop(netns1); + + th = g_thread_new("nm-test-netns-mt", _test_netns_mt_thread, netns1); + initial_from_other_thread = g_thread_join(th); + g_assert(NMP_IS_NETNS(initial_from_other_thread)); + + if (nmtst_get_rand_bool()) { + nmp_netns_push(initial_from_other_thread); + nmp_netns_pop(initial_from_other_thread); + } + + g_object_add_weak_pointer(G_OBJECT(initial_from_other_thread), + (gpointer *) &initial_from_other_thread); + g_object_unref(initial_from_other_thread); + g_assert(initial_from_other_thread == NULL); +} + +/*****************************************************************************/ + +static void +ethtool_features_dump(const NMEthtoolFeatureStates *features) +{ + guint i, j; + + g_assert(features); + + _LOGT(">>> %u features (%u ss-features)", features->n_states, features->n_ss_features); + + for (i = 0; i < features->n_states; i++) { + const NMEthtoolFeatureState *s = &features->states_list[i]; + + _LOGT(">>> feature-list[%3u]: %3d = %-32s (%3u) | %s %s %s %s", + i, + (int) s->info->ethtool_id, + s->info->kernel_names[s->idx_kernel_name], + s->idx_ss_features, + s->active ? "ACT" : "act", + s->available ? "AVA" : "ava", + s->never_changed ? "NCH" : "nch", + s->requested ? "REQ" : "req"); + } + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) { + _LOGT(">>> feature-idx [%3u]: %-32s = %u features", + i + (guint) _NM_ETHTOOL_ID_FEATURE_FIRST, + nm_ethtool_data[i + _NM_ETHTOOL_ID_FEATURE_FIRST]->optname, + (guint) NM_PTRARRAY_LEN(features->states_indexed[i])); + for (j = 0; features->states_indexed[i] && features->states_indexed[i][j]; j++) { + const NMEthtoolFeatureState *s = features->states_indexed[i][j]; + + _LOGT(">>> %3u: %-32s | %s %s %s %s", + j, + s->info->kernel_names[s->idx_kernel_name], + s->active ? "ACT" : "act", + s->available ? "AVA" : "ava", + s->never_changed ? "NCH" : "nch", + s->requested ? "REQ" : "req"); + } + } +} + +static void +test_ethtool_features_get(void) +{ + gs_unref_ptrarray GPtrArray *gfree_keeper = g_ptr_array_new_with_free_func(g_free); + const int IFINDEX = 1; + guint i; + guint i_run; + + for (i_run = 0; i_run < 5; i_run++) { + NMEthtoolFeatureStates *features; + NMOptionBool * requested; + gboolean do_set = TRUE; + + requested = g_new(NMOptionBool, _NM_ETHTOOL_ID_FEATURE_NUM); + for (i = 0; i < _NM_ETHTOOL_ID_FEATURE_NUM; i++) + requested[i] = NM_OPTION_BOOL_DEFAULT; + g_ptr_array_add(gfree_keeper, requested); + + if (i_run == 0) { + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_RX)] = + NM_OPTION_BOOL_FALSE; + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TSO)] = + NM_OPTION_BOOL_FALSE; + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION)] = + NM_OPTION_BOOL_FALSE; + } else if (i_run == 1) + do_set = FALSE; + else if (i_run == 2) { + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TSO)] = + NM_OPTION_BOOL_FALSE; + requested[_NM_ETHTOOL_ID_FEATURE_AS_IDX(NM_ETHTOOL_ID_FEATURE_TX_TCP6_SEGMENTATION)] = + NM_OPTION_BOOL_TRUE; + } else if (i_run == 3) + do_set = FALSE; + + _LOGT(">>> ethtool-features-get RUN %u (do-set=%s", i_run, do_set ? "set" : "reset"); + + features = nmp_utils_ethtool_get_features(IFINDEX); + g_ptr_array_add(gfree_keeper, features); + + ethtool_features_dump(features); + + if (_LOGT_ENABLED()) + _system("ethtool -k lo"); + + if (!do_set) { + requested = gfree_keeper->pdata[i_run * 2 - 2]; + features = gfree_keeper->pdata[i_run * 2 - 1]; + } + + nmp_utils_ethtool_set_features(IFINDEX, features, requested, do_set); + } +} + +/*****************************************************************************/ + +NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, DEVICE_NAME, FALSE); + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, SLAVE_NAME, FALSE); + nmtstp_link_delete(NM_PLATFORM_GET, -1, -1, PARENT_NAME, FALSE); + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, DEVICE_NAME)); + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, SLAVE_NAME)); + g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, PARENT_NAME)); + + g_test_add_func("/link/bogus", test_bogus); + g_test_add_func("/link/loopback", test_loopback); + g_test_add_func("/link/internal", test_internal); + g_test_add_func("/link/software/bridge", test_bridge); + g_test_add_func("/link/software/bond", test_bond); + g_test_add_func("/link/software/team", test_team); + g_test_add_func("/link/software/vlan", test_vlan); + g_test_add_func("/link/software/bridge/addr", test_bridge_addr); + + if (nmtstp_is_root_test()) { + g_test_add_func("/link/external", test_external); + + test_software_detect_add("/link/software/detect/bridge", NM_LINK_TYPE_BRIDGE, 0); + test_software_detect_add("/link/software/detect/gre", NM_LINK_TYPE_GRE, 0); + test_software_detect_add("/link/software/detect/gretap", NM_LINK_TYPE_GRETAP, 0); + test_software_detect_add("/link/software/detect/ip6tnl/0", NM_LINK_TYPE_IP6TNL, 0); + test_software_detect_add("/link/software/detect/ip6tnl/1", NM_LINK_TYPE_IP6TNL, 1); + test_software_detect_add("/link/software/detect/ip6gre", NM_LINK_TYPE_IP6GRE, 0); + test_software_detect_add("/link/software/detect/ip6gretap", NM_LINK_TYPE_IP6GRETAP, 0); + test_software_detect_add("/link/software/detect/ipip", NM_LINK_TYPE_IPIP, 0); + test_software_detect_add("/link/software/detect/macvlan", NM_LINK_TYPE_MACVLAN, 0); + test_software_detect_add("/link/software/detect/macvtap", NM_LINK_TYPE_MACVTAP, 0); + test_software_detect_add("/link/software/detect/sit", NM_LINK_TYPE_SIT, 0); + test_software_detect_add("/link/software/detect/tun", NM_LINK_TYPE_TUN, 0); + test_software_detect_add("/link/software/detect/vlan", NM_LINK_TYPE_VLAN, 0); + test_software_detect_add("/link/software/detect/vrf", NM_LINK_TYPE_VRF, 0); + test_software_detect_add("/link/software/detect/vxlan/0", NM_LINK_TYPE_VXLAN, 0); + test_software_detect_add("/link/software/detect/vxlan/1", NM_LINK_TYPE_VXLAN, 1); + test_software_detect_add("/link/software/detect/wireguard/0", NM_LINK_TYPE_WIREGUARD, 0); + test_software_detect_add("/link/software/detect/wireguard/1", NM_LINK_TYPE_WIREGUARD, 1); + test_software_detect_add("/link/software/detect/wireguard/2", NM_LINK_TYPE_WIREGUARD, 2); + + g_test_add_func("/link/software/vlan/set-xgress", test_vlan_set_xgress); + + g_test_add_data_func("/link/create-many-links/20", + GUINT_TO_POINTER(20), + test_create_many_links); + g_test_add_data_func("/link/create-many-links/1000", + GUINT_TO_POINTER(1000), + test_create_many_links); + + g_test_add_func("/link/nl-bugs/veth", test_nl_bugs_veth); + g_test_add_func("/link/nl-bugs/spurious-newlink", test_nl_bugs_spuroius_newlink); + g_test_add_func("/link/nl-bugs/spurious-dellink", test_nl_bugs_spuroius_dellink); + + g_test_add_vtable("/general/netns/general", + 0, + NULL, + _test_netns_setup, + test_netns_general, + _test_netns_teardown); + g_test_add_vtable("/general/netns/set-netns", + 0, + NULL, + _test_netns_setup, + test_netns_set_netns, + _test_netns_teardown); + g_test_add_vtable("/general/netns/push", + 0, + NULL, + _test_netns_setup, + test_netns_push, + _test_netns_teardown); + g_test_add_vtable("/general/netns/bind-to-path", + 0, + NULL, + _test_netns_setup, + test_netns_bind_to_path, + _test_netns_teardown); + + g_test_add_func("/general/netns/mt", test_netns_mt); + + g_test_add_func("/general/sysctl/rename", test_sysctl_rename); + g_test_add_func("/general/sysctl/netns-switch", test_sysctl_netns_switch); + g_test_add_func("/general/sysctl/set-async", test_sysctl_set_async); + g_test_add_func("/general/sysctl/set-async-fail", test_sysctl_set_async_fail); + + g_test_add_func("/link/ethtool/features/get", test_ethtool_features_get); + } +} diff --git a/src/core/platform/tests/test-nmp-object.c b/src/core/platform/tests/test-nmp-object.c new file mode 100644 index 00000000..310611ee --- /dev/null +++ b/src/core/platform/tests/test-nmp-object.c @@ -0,0 +1,617 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2017 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include <libudev.h> +#include <linux/pkt_sched.h> + +#include "platform/nmp-object.h" +#include "nm-udev-aux/nm-udev-utils.h" + +#include "nm-test-utils-core.h" + +struct { + GList *udev_devices; +} global; + +/*****************************************************************************/ + +static void +test_obj_base(void) +{ + static const union { + GObject g; + NMPObject k; + } x = {}; + static const union { + GTypeClass k; + NMPClass c; + } l = {}; + static const GObject * g = &x.g; + static const GTypeClass *k = &l.k; + static const NMPObject * o = &x.k; + static const NMPClass * c = &l.c; + + NMObjBaseInst * obj; + gs_unref_object GCancellable *obj_cancellable = g_cancellable_new(); + nm_auto_nmpobj NMPObject *obj_link = nmp_object_new_link(10); + + g_assert(&g->g_type_instance == (void *) &o->_class); + g_assert(&g->g_type_instance.g_class == (void *) &o->_class); + + g_assert(sizeof(o->parent.parent) == sizeof(GTypeInstance)); + + g_assert(&c->parent == (void *) c); + g_assert(&c->parent.parent.g_type_class == (void *) c); + g_assert(&c->parent.parent.g_type == (void *) c); + g_assert(&c->parent.parent.g_type == &k->g_type); + + g_assert(sizeof(c->parent.parent) == sizeof(GTypeClass)); + + g_assert(&o->parent == (void *) o); + g_assert(&o->parent.klass == (void *) &o->_class); + + obj = (NMObjBaseInst *) obj_cancellable; + g_assert(!NMP_CLASS_IS_VALID((NMPClass *) obj->klass)); + g_assert(G_TYPE_CHECK_INSTANCE_TYPE(obj, G_TYPE_CANCELLABLE)); + + obj = (NMObjBaseInst *) obj_link; + g_assert(NMP_CLASS_IS_VALID((NMPClass *) obj->klass)); + g_assert(!G_TYPE_CHECK_INSTANCE_TYPE(obj, G_TYPE_CANCELLABLE)); +} + +/*****************************************************************************/ + +static gboolean +_nmp_object_id_equal(const NMPObject *a, const NMPObject *b) +{ + gboolean a_b = nmp_object_id_equal(a, b); + + g_assert(NM_IN_SET(a_b, FALSE, TRUE) && a_b == nmp_object_id_equal(b, a)); + return a_b; +} +#define nmp_object_id_equal _nmp_object_id_equal + +static gboolean +_nmp_object_equal(const NMPObject *a, const NMPObject *b) +{ + gboolean a_b = nmp_object_equal(a, b); + + g_assert(NM_IN_SET(a_b, FALSE, TRUE) && a_b == nmp_object_equal(b, a)); + return a_b; +} +#define nmp_object_equal _nmp_object_equal + +/*****************************************************************************/ + +static void +_assert_cache_multi_lookup_contains(const NMPCache * cache, + const NMDedupMultiHeadEntry *head_entry, + const NMPObject * obj, + gboolean visible_only, + gboolean contains) +{ + NMDedupMultiIter iter; + gboolean found; + guint i, len; + const NMPObject *o; + + g_assert(NMP_OBJECT_IS_VALID(obj)); + + g_assert(nmp_cache_lookup_obj(cache, obj) == obj); + g_assert(!head_entry + || (head_entry->len > 0 + && c_list_length(&head_entry->lst_entries_head) == head_entry->len)); + + len = head_entry ? head_entry->len : 0; + + found = FALSE; + i = 0; + nmp_cache_iter_for_each (&iter, head_entry, &o) { + g_assert(NMP_OBJECT_IS_VALID(o)); + if (obj == o) { + if (!visible_only || nmp_object_is_visible(o)) { + g_assert(!found); + found = TRUE; + } + } + i++; + } + + g_assert(len == i); + g_assert(!!contains == found); +} + +static void +_assert_cache_multi_lookup_contains_link(const NMPCache * cache, + gboolean visible_only, + const NMPObject *obj, + gboolean contains) +{ + const NMDedupMultiHeadEntry *head_entry; + NMPLookup lookup; + + g_assert(cache); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_LINK); + head_entry = nmp_cache_lookup(cache, &lookup); + _assert_cache_multi_lookup_contains(cache, head_entry, obj, visible_only, contains); +} + +/*****************************************************************************/ + +static void +ops_post_check(NMPCache * cache, + NMPCacheOpsType ops_type, + const NMPObject *obj_old, + const NMPObject *obj_new, + const NMPObject *obj_new_expected, + NMPCacheOpsType expected_ops_type) +{ + g_assert(cache); + + g_assert_cmpint(expected_ops_type, ==, ops_type); + + switch (ops_type) { + case NMP_CACHE_OPS_ADDED: + g_assert(!obj_old); + g_assert(NMP_OBJECT_IS_VALID(obj_new)); + g_assert(nmp_object_is_alive(obj_new)); + g_assert(nmp_object_id_equal(obj_new_expected, obj_new)); + g_assert(nmp_object_equal(obj_new_expected, obj_new)); + break; + case NMP_CACHE_OPS_UPDATED: + g_assert(obj_old != obj_new); + g_assert(NMP_OBJECT_IS_VALID(obj_old)); + g_assert(NMP_OBJECT_IS_VALID(obj_new)); + g_assert(nmp_object_is_alive(obj_old)); + g_assert(nmp_object_is_alive(obj_new)); + g_assert(nmp_object_id_equal(obj_new_expected, obj_new)); + g_assert(nmp_object_id_equal(obj_new_expected, obj_old)); + g_assert(nmp_object_id_equal(obj_old, obj_new)); + g_assert(nmp_object_equal(obj_new_expected, obj_new)); + g_assert(!nmp_object_equal(obj_new_expected, obj_old)); + g_assert(!nmp_object_equal(obj_old, obj_new)); + break; + case NMP_CACHE_OPS_REMOVED: + g_assert(!obj_new); + g_assert(NMP_OBJECT_IS_VALID(obj_old)); + g_assert(nmp_object_is_alive(obj_old)); + if (obj_new_expected) + g_assert(nmp_object_id_equal(obj_new_expected, obj_old)); + break; + case NMP_CACHE_OPS_UNCHANGED: + g_assert(obj_old == obj_new); + if (obj_old) { + g_assert(NMP_OBJECT_IS_VALID(obj_old)); + g_assert(nmp_object_is_alive(obj_old)); + g_assert(nmp_object_equal(obj_old, obj_new)); + g_assert(nmp_object_id_equal(obj_new_expected, obj_new)); + } else + g_assert(!obj_new_expected); + break; + default: + g_assert_not_reached(); + } +} + +static void +_nmp_cache_update_netlink(NMPCache * cache, + NMPObject * obj, + const NMPObject **out_obj_old, + const NMPObject **out_obj_new, + NMPCacheOpsType expected_ops_type) +{ + NMPCacheOpsType ops_type; + const NMPObject *obj_prev; + const NMPObject *obj_old; + const NMPObject *obj_new; + nm_auto_nmpobj NMPObject *obj_new_expected = NULL; + + g_assert(cache); + g_assert(NMP_OBJECT_IS_VALID(obj)); + + obj_prev = nmp_cache_lookup_link(cache, NMP_OBJECT_CAST_LINK(obj)->ifindex); + obj_new_expected = nmp_object_clone(obj, FALSE); + if (obj_prev && obj_prev->_link.udev.device) + obj_new_expected->_link.udev.device = udev_device_ref(obj_prev->_link.udev.device); + _nmp_object_fixup_link_udev_fields(&obj_new_expected, NULL, nmp_cache_use_udev_get(cache)); + + ops_type = nmp_cache_update_netlink(cache, obj, FALSE, &obj_old, &obj_new); + ops_post_check(cache, + ops_type, + obj_old, + obj_new, + nmp_object_is_alive(obj_new_expected) ? obj_new_expected : NULL, + expected_ops_type); + + if (out_obj_new) + *out_obj_new = obj_new; + else + nmp_object_unref(obj_new); + if (out_obj_old) + *out_obj_old = obj_old; + else + nmp_object_unref(obj_old); +} + +static const NMPlatformLink pl_link_2 = { + .ifindex = 2, + .name = "eth0", + .type = NM_LINK_TYPE_ETHERNET, +}; + +static const NMPlatformLink pl_link_3 = { + .ifindex = 3, + .name = "wlan0", + .type = NM_LINK_TYPE_WIFI, +}; + +static void +test_cache_link(void) +{ + NMPCache * cache; + NMPObject * objm1; + const NMPObject * obj_old, *obj_new; + NMPObject objs1; + struct udev_device * udev_device_2 = g_list_nth_data(global.udev_devices, 0); + struct udev_device * udev_device_3 = g_list_nth_data(global.udev_devices, 0); + NMPCacheOpsType ops_type; + nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = NULL; + gboolean use_udev = nmtst_get_rand_uint32() % 2; + + multi_idx = nm_dedup_multi_index_new(); + + cache = nmp_cache_new(multi_idx, use_udev); + + /* if we have a link, and don't set is_in_netlink, adding it has no effect. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + g_assert(NMP_OBJECT_UP_CAST(&objm1->object) == objm1); + g_assert(!nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UNCHANGED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(!obj_old); + g_assert(!obj_new); + g_assert(!nmp_cache_lookup_obj(cache, objm1)); + g_assert(!nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex))); + nmp_object_unref(objm1); + + /* Only when setting @is_in_netlink the link is added. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + objm1->_link.netlink.is_in_netlink = TRUE; + g_assert(nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_ADDED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(!obj_old); + g_assert(obj_new); + g_assert(objm1 == obj_new); + g_assert(nmp_object_equal(objm1, obj_new)); + g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); + g_assert(nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == obj_new); + g_assert(nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); + nmp_object_unref(objm1); + nmp_object_unref(obj_new); + + /* updating the same link with identical value, has no effect. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + objm1->_link.netlink.is_in_netlink = TRUE; + g_assert(nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UNCHANGED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(obj_old); + g_assert(obj_new); + g_assert(obj_new != objm1); + g_assert(nmp_object_equal(objm1, obj_new)); + g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); + g_assert(nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == obj_new); + nmp_object_unref(objm1); + nmp_object_unref(obj_new); + nmp_object_unref(obj_new); + + /* remove the link from netlink */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + g_assert(!nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_REMOVED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(obj_old); + g_assert(!obj_new); + g_assert(!nmp_cache_lookup_obj(cache, objm1)); + g_assert(!nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex))); + nmp_object_unref(objm1); + nmp_object_unref(obj_old); + nmp_object_unref(obj_new); + + if (udev_device_2) { + /* now add the link only with aspect UDEV. */ + ops_type = + nmp_cache_update_link_udev(cache, pl_link_2.ifindex, udev_device_2, &obj_old, &obj_new); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert_cmpint(ops_type, ==, NMP_CACHE_OPS_ADDED); + g_assert(!obj_old); + g_assert(obj_new); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == obj_new); + g_assert(!nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, FALSE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + nmp_object_unref(obj_new); + } + + /* add it in netlink too. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + objm1->_link.netlink.is_in_netlink = TRUE; + g_assert(nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, + objm1, + &obj_old, + &obj_new, + udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_ADDED); + nmtst_assert_nmp_cache_is_consistent(cache); + if (udev_device_2) { + g_assert(obj_old); + g_assert(!nmp_object_is_visible(obj_old)); + } else + g_assert(!obj_old); + g_assert(nmp_object_equal(objm1, obj_new)); + g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); + g_assert(nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == obj_new); + g_assert(nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + nmp_object_unref(objm1); + nmp_object_unref(obj_old); + nmp_object_unref(obj_new); + + /* remove again from netlink. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + objm1->_link.netlink.is_in_netlink = FALSE; + g_assert(!nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, + objm1, + &obj_old, + &obj_new, + udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_REMOVED); + nmtst_assert_nmp_cache_is_consistent(cache); + if (udev_device_2) + g_assert(obj_new == objm1); + else + g_assert(!obj_new); + g_assert(obj_old); + g_assert(nmp_object_is_alive(obj_old)); + if (udev_device_2) { + g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == obj_new); + g_assert(!nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, FALSE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + } else { + g_assert(nmp_cache_lookup_obj(cache, objm1) == NULL); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_2.ifindex)) + == NULL); + g_assert(!nmp_object_is_alive(obj_new)); + g_assert(!nmp_object_is_visible(obj_new)); + } + nmp_object_unref(objm1); + nmp_object_unref(obj_old); + nmp_object_unref(obj_new); + + /* now another link only with aspect UDEV. */ + if (udev_device_3) { + /* now add the link only with aspect UDEV. */ + ops_type = + nmp_cache_update_link_udev(cache, pl_link_3.ifindex, udev_device_3, &obj_old, &obj_new); + g_assert_cmpint(ops_type, ==, NMP_CACHE_OPS_ADDED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(NMP_OBJECT_IS_VALID(obj_new)); + g_assert(!obj_old); + g_assert(!nmp_object_is_visible(obj_new)); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_3.ifindex)) + == obj_new); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, FALSE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + g_assert_cmpint(obj_new->_link.netlink.is_in_netlink, ==, FALSE); + g_assert_cmpint(obj_new->link.initialized, ==, FALSE); + nmp_object_unref(obj_new); + + /* add it in netlink too. */ + objm1 = nmp_object_new(NMP_OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_3); + objm1->_link.netlink.is_in_netlink = TRUE; + g_assert(nmp_object_is_alive(objm1)); + _nmp_cache_update_netlink(cache, objm1, &obj_old, &obj_new, NMP_CACHE_OPS_UPDATED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(obj_old); + g_assert(obj_new == objm1); + g_assert(nmp_object_equal(objm1, obj_new)); + g_assert(!obj_old || !nmp_object_is_visible(obj_old)); + g_assert(nmp_cache_lookup_obj(cache, objm1) == obj_new); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_3.ifindex)) + == obj_new); + g_assert(nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + g_assert_cmpint(obj_new->_link.netlink.is_in_netlink, ==, TRUE); + g_assert_cmpint(obj_new->link.initialized, ==, TRUE); + nmp_object_unref(objm1); + nmp_object_unref(obj_old); + nmp_object_unref(obj_new); + + /* remove UDEV. */ + ops_type = nmp_cache_update_link_udev(cache, pl_link_3.ifindex, NULL, &obj_old, &obj_new); + g_assert_cmpint(ops_type, ==, NMP_CACHE_OPS_UPDATED); + nmtst_assert_nmp_cache_is_consistent(cache); + g_assert(obj_old && nmp_object_is_visible(obj_old)); + g_assert( + nmp_cache_lookup_obj(cache, nmp_object_stackinit_id_link(&objs1, pl_link_3.ifindex)) + == obj_new); + g_assert(nmp_object_is_visible(obj_new)); + _assert_cache_multi_lookup_contains_link(cache, TRUE, obj_new, TRUE); + _assert_cache_multi_lookup_contains_link(cache, FALSE, obj_new, TRUE); + g_assert_cmpint(obj_new->_link.netlink.is_in_netlink, ==, TRUE); + g_assert_cmpint(obj_new->link.initialized, ==, !nmp_cache_use_udev_get(cache)); + nmp_object_unref(obj_new); + nmp_object_unref(obj_old); + } + + nmp_cache_free(cache); +} + +const char noqueue[] = "noqueue"; +const char fq_codel[] = "fq_codel"; +const char ingress[] = "ingress"; + +static const NMPlatformQdisc pl_qdisc_1a = { + .ifindex = 1, + .kind = noqueue, + .addr_family = AF_UNSPEC, + .handle = 0, + .parent = TC_H_ROOT, + .info = 0, +}; + +static const NMPlatformQdisc pl_qdisc_1b = { + .ifindex = 1, + .kind = fq_codel, + .addr_family = AF_UNSPEC, + .handle = 0, + .parent = TC_H_ROOT, + .info = 0, +}; + +static const NMPlatformQdisc pl_qdisc_1c = { + .ifindex = 1, + .kind = ingress, + .addr_family = AF_UNSPEC, + .handle = TC_H_MAKE(TC_H_INGRESS, 0), + .parent = TC_H_INGRESS, + .info = 0, +}; + +static const NMPlatformQdisc pl_qdisc_2 = { + .ifindex = 2, + .kind = fq_codel, + .addr_family = AF_UNSPEC, + .handle = 0, + .parent = TC_H_ROOT, + .info = 0, +}; + +static void +test_cache_qdisc(void) +{ + NMPCache * cache; + nm_auto_unref_dedup_multi_index NMDedupMultiIndex *multi_idx = NULL; + NMPLookup lookup; + const NMDedupMultiHeadEntry * head_entry; + nm_auto_nmpobj NMPObject *obj1a = + nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1a); + nm_auto_nmpobj NMPObject *obj1b = + nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1b); + nm_auto_nmpobj NMPObject *obj1c = + nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_1c); + nm_auto_nmpobj NMPObject *obj2 = + nmp_object_new(NMP_OBJECT_TYPE_QDISC, (NMPlatformObject *) &pl_qdisc_2); + + multi_idx = nm_dedup_multi_index_new(); + cache = nmp_cache_new(multi_idx, nmtst_get_rand_uint32() % 2); + + g_assert(nmp_cache_lookup_obj(cache, obj1a) == NULL); + + g_assert(nmp_cache_update_netlink(cache, obj1a, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED); + g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1a); + g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1a); + g_assert(nmp_cache_lookup_obj(cache, obj2) == NULL); + + g_assert(nmp_cache_update_netlink(cache, obj1b, FALSE, NULL, NULL) == NMP_CACHE_OPS_UPDATED); + g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj2) == NULL); + + g_assert(nmp_cache_update_netlink(cache, obj1c, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED); + g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj1c) == obj1c); + g_assert(nmp_cache_lookup_obj(cache, obj2) == NULL); + + g_assert(nmp_cache_update_netlink(cache, obj2, FALSE, NULL, NULL) == NMP_CACHE_OPS_ADDED); + g_assert(nmp_cache_lookup_obj(cache, obj1a) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1b); + g_assert(nmp_cache_lookup_obj(cache, obj2) == obj2); + + head_entry = nmp_cache_lookup(cache, nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_QDISC, 1)); + g_assert(head_entry->len == 2); + + nmp_cache_free(cache); +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + int result; + NMUdevClient *udev_client; + + nmtst_init_assert_logging(&argc, &argv, "INFO", "DEFAULT"); + + udev_client = nm_udev_client_new(NM_MAKE_STRV("net"), NULL, NULL); + { + struct udev_enumerate * enumerator; + struct udev_list_entry *devices, *l; + + enumerator = nm_udev_client_enumerate_new(udev_client); + + /* Demand that the device is initialized (udev rules ran, + * device has a stable name now) in case udev is running + * (not in a container). */ + if (access("/sys", W_OK) == 0) + udev_enumerate_add_match_is_initialized(enumerator); + + udev_enumerate_scan_devices(enumerator); + + devices = udev_enumerate_get_list_entry(enumerator); + for (l = devices; l != NULL; l = udev_list_entry_get_next(l)) { + struct udev_device *udevice; + + udevice = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerator), + udev_list_entry_get_name(l)); + if (udevice == NULL) + continue; + + global.udev_devices = g_list_prepend(global.udev_devices, udevice); + } + global.udev_devices = g_list_reverse(global.udev_devices); + + udev_enumerate_unref(enumerator); + } + + g_test_add_func("/nmp-object/obj-base", test_obj_base); + g_test_add_func("/nmp-object/cache_link", test_cache_link); + g_test_add_func("/nmp-object/cache_qdisc", test_cache_qdisc); + + result = g_test_run(); + + while (global.udev_devices) { + udev_device_unref(global.udev_devices->data); + global.udev_devices = g_list_delete_link(global.udev_devices, global.udev_devices); + } + + nm_udev_client_destroy(udev_client); + + return result; +} diff --git a/src/core/platform/tests/test-platform-general.c b/src/core/platform/tests/test-platform-general.c new file mode 100644 index 00000000..b426cc65 --- /dev/null +++ b/src/core/platform/tests/test-platform-general.c @@ -0,0 +1,772 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 - 2018 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include <linux/rtnetlink.h> + +#include "nm-platform/nm-platform-utils.h" +#include "platform/nm-linux-platform.h" + +#include "nm-test-utils-core.h" + +/*****************************************************************************/ + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPAddress, address_ptr) + == G_STRUCT_OFFSET(NMPlatformIP4Address, address)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPAddress, address_ptr) + == G_STRUCT_OFFSET(NMPlatformIP6Address, address)); + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPRoute, network_ptr) + == G_STRUCT_OFFSET(NMPlatformIP4Route, network)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(NMPlatformIPRoute, network_ptr) + == G_STRUCT_OFFSET(NMPlatformIP6Route, network)); + +/*****************************************************************************/ + +static void +test_init_linux_platform(void) +{ + gs_unref_object NMPlatform *platform = NULL; + + platform = nm_linux_platform_new(TRUE, NM_PLATFORM_NETNS_SUPPORT_DEFAULT); +} + +/*****************************************************************************/ + +static void +test_link_get_all(void) +{ + gs_unref_object NMPlatform *platform = NULL; + gs_unref_ptrarray GPtrArray *links = NULL; + + platform = nm_linux_platform_new(TRUE, NM_PLATFORM_NETNS_SUPPORT_DEFAULT); + + links = nm_platform_link_get_all(platform, TRUE); +} + +/*****************************************************************************/ + +static void +test_nm_platform_link_flags2str(void) +{ + int i; + + for (i = 0; i < 100; i++) { + char buf[NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN + 100]; + const char *s; + const guint flags = ((i == 0) ? ~0u : nmtst_get_rand_uint()); + gsize l; + + s = nm_platform_link_flags2str(flags, buf, sizeof(buf)); + g_assert(s); + + l = strlen(s); + if (l > NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN) + g_error( + "nm_platform_link_flags2str(%x) produced a longer output than %zu chars: \"%s\"", + flags, + NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN, + s); + if (flags == ~0u && l != NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN) + g_error("nm_platform_link_flags2str(%x) is expected to produce %zu chars, but produced " + "%zu: \"%s\"", + flags, + NM_PLATFORM_LINK_FLAGS2STR_MAX_LEN, + l, + s); + } +} + +/*****************************************************************************/ + +static int +_address_pretty_sort_cmp(gconstpointer a, gconstpointer b, gpointer test_data) +{ + const int TEST_DATA_I = GPOINTER_TO_INT(test_data); + const int addr_family = (TEST_DATA_I == 0 ? AF_INET : AF_INET6); + const int IPV6_PREFER_TEMP = (TEST_DATA_I == 2); + const NMPlatformIPXAddress *a_a = a; + const NMPlatformIPXAddress *a_b = b; + + if (addr_family == AF_INET) + return nm_platform_ip4_address_pretty_sort_cmp(&a_a->a4, &a_b->a4); + + return nm_platform_ip6_address_pretty_sort_cmp(&a_a->a6, &a_b->a6, IPV6_PREFER_TEMP); +} + +static void +test_platform_ip_address_pretty_sort_cmp(gconstpointer test_data) +{ + static const char *const EXPECTED_BUFFER[3] = { + [0] = ("5b1aea34648cabfec7c3523f76cf1ce34ca17a9a32f3f0f218424e48836dd1cb504e03d53e1124c5" + "0065aeb2e6fbf952902383028e3b47f280f062ea1a7e0b7be218d067530e1b0487b8c3b99f2b8a1a" + "8982c42f0000003437c5156e072b2f2f0037c9cfe07c34ddb3980deb14ab7b5af84a034703000000" + "883b0f3fd6ed84d6c959e553b887edcd01c2f5d200000043b809d259e499db7d00f1853bdcb0e4bc" + "0e2b00b667b7b16d8d1e69c803000000b973972c17a47631c169f11ff9119c400368b6630000007a" + "034f43340d01683c0045097aea4a849f060ddf57b24a5be9636360d603000000ad7c499dd538d345" + "74c038404923e75d0209e2fc0000004acc807cdce682f80f00315c45ef817264c89a736ed55ed637" + "b96c200203000000faf1809becd2506315a6da29b2e94d3a031fe0e900000083a36035fb6297dfa7" + "00686b5efd0d53bb6215de4bb6f6f3d031a79028030000008bb836c0a25ea71f5daaed4d99eb2ebe" + "033c432f000000bf4ccf30d3aaaf02a4005d7308b67f91bf9d82c856ba942455e8d07c8403000000" + "f2abb982b001ec16901f55f960c55c22022099a80000002b4d4647f53b1921af0088e3759a08e7a5" + "6663861eea1bf42c12ea3b9503000000fb95e8332fdfff658483a2d039a7bf1402d3481e00000060" + "e89f7abdb682380a00eae374835b4a49a2b980b6aba92da6409969aa03000000e00473755d31e5b2" + "de252167c1c91b3a02ba0c700000007740318db913a353ed006efc068829c0e66ad0143a0554efb4" + "40e55b8b03000000c0cfb2b4386bec092fa5757ecde9348b00c12ebd000000ab667224dae775e5cc" + "0041aca2ff0f576767d3648102b61886d149f07403000000153ece68ade15cec25a59273e7519f34" + "c4458d70000000f3819aa46fbe1439340033ae6dec0fb124f264af67eed7c9a8ecc8fb1103000000" + "fcebbaeb0c56535923f14874042a8aff021f8e5ec3cc13cc36bbe3c9bb0ec36f00e007bb64a2827b" + "7cdd38d0314c178e5a06c40b03000000ab90135fa636af4464d210a256be75e0029c44770000004b" + "2e69220d6c0fc09c25d6534c809829af4a9df58dbfef186d416f3a1e030000002c932e655203d82a" + "3c84c4eb274ed18603780281000000f2235376239daeaacb3cae864b437baaae91921681c2162b9b" + "69e66142030000004fda8a3e0b841cf76391dd68269b53ec0244a831000000b78c54dda9ac3bb1b1" + "d43e6505621b9a7f0422ae3fc8979ee0416f95e70300000057d6249b652ba98c7dc7f17f666969e4" + "023baf7a000000ee0b06fa9e988f80f0de6dc8dfcf2a3ad3bbcc0fa3b314f695111d891d03000000" + "cd897619f51f44e644d7cf1d06b2b1150082549e62c12fba5b1cdec48d10bbb13b8313d8cd2a24d3" + "4fc812bd2f8a59d90fcc00ac030000005292cd32dc096cd5d8a4c5cf3351ee1c03c2056c00000051" + "bbce426cfa4b861cc78592be7b14e7ba9c15acb881ae55f0e5fe7d360300000066a3ae3939762df3" + "3a2d55060c78d55100b110fd00000041b9aca07b6e4925dd27943a272c171ed15abbbe1cd911db7b" + "86ed271803000000a5edc511c1507a141e0f515638c7ba31027609450000003357ae79989870ccec" + "3def0ad92749e016663fe6ee0228c1da82d1595603000000348352d715cf9d411ea012e5307294b6" + "0146dac4000000075efee38dd16f8ee4ccd2f50c30706cae3fdcc2f0ee3d5e26bb20413203000000" + "862573c2303dd1d65c7b2cffeca6d1adaccae11f0000000f855ebf3b772eb2b1c896c9a7304f6645" + "0a5f6abd850b06e3b10123e6030000001dff045298cfee0636674cdefb57b9ae54cfe8f400000038" + "1ba2c4396de60f032bc7f34de2959871c0d4c0d4eb720c4ab550c5db03000000f32f4af595d785d5" + "c1b5aad487c192f08bd7a09b00000043288cd9bf66ec305a225a0c71b2ce78bb16104c8eaf18c565" + "b16c7376030000005219061ab4c5c79489b2cc6a883c146972decb8b000000f5e6d66df46ea13910" + "7754dee62c36d2fc70ccc567df7a49b8585287dc03000000cfb18b2b2cb749e2e03e544d0eb4f73e" + "75039fcf251b32fc79685b05ddd3aa9ba511d2e40edb4d758fb554158ae5c7c0beb42b3403000000" + "895d5f24037d233302ad3b82d639272e0246eadfbd2146bf8cfdb205f90e54b58a6ee136a779f37c" + "30d2c5053c40ecaec38b6b8e03000000bea73223e59bf0193432e9fa7a899f2d8ec7e4b89bf5a5d0" + "6776e66a9d80ab132e1ac921eb76adbb229df32e561fa80a0fc4676703000000a23eb66e720da9e0" + "7ac998b5690807d50217369ee1af4ca5a6a95581af5fd7ceacdca10f47d7b351a36b178aabc78a4a" + "1a0dd8c003000000e2815a1a37a52bddd1c2f1018b587eed03bb58f0e9201f17bd99fcf72909ac9e" + "7a55299e9bb4fd53bc7417940fcffe3f81cafd6302000000d6732578acd14320aefd4503189f7630" + "038e501c0000002b9f3c39f24b0572b100745cb25851429b3bbfb50168dfd04eb62f22ca02000000" + "891715df7fc6a902edae579e2e10c7f7022ba0340436242cbeb0248cee3fbc160032d4f28aa28c08" + "f80dd50c6712dfb4abba4a32020000008ffe423d01883918039249f398f9b37ea091465100000064" + "3722d9b707c0d8a400b7c8307f06b4b29088f20d9ac676d5e4bafc4e02000000fde69eec3af2e6d0" + "bd68ab722af14548b29572e504265f6c72923e22594f3f790008ed2e2ebb0771db46a54cadb245ea" + "8c3b371502000000710c030690f5f18ea125dbf7d7e93bd6011fa56dfdcfc1155f236c8b9c79a620" + "00660bbf024b03ff0a8e27c405e64244e36f90d402000000fd41fe47684b370b6ec6584d64496089" + "570968ead4d1ae91c819bb068196d59900de3246e43f5e7945aaf95e2ffa3a119c64ed0402000000" + "a660ac824b7fae389861419c50da49bf02092583000000259f9f0251becc987907879cca68fec7bb" + "bb5f8edf248b4995d184e82002000000a19cdf6dd1c173f6078a806d329c9b0000bfc972000000f5" + "b2cd3dbddc74e26de958e48d2ab8b0313e7f8933e315130f641e447502000000b9b68c08a5e4351e" + "a349e1ccf662e058b879a45100000045fcb6a035339d504c9726d80d9c2d89df765b4d9a130257dc" + "d1e1b34902000000bcd7be07d78e6222e45aaf61814f703b40125e6b000000cbaaa37b861e6d46da" + "fe7d6ec4ac1ea051010911915ddb05f2c43bd794010000005bfe47c6f53a54e01b0c1d89414d94d0" + "032d2ec50000000103781b0f294a2b7300421398f4de67e9cee64b38b56e03e01539ce4101000000" + "18238487a417f3da01d99dae5f19009601582cab0000005b2363d13edc5aa115005eb914d8fbe9a4" + "fdb3d117d76b0de5bd82e9ea010000003d1b91caae8cb60b49ba9be338d856a40267c3d400000064" + "9da90bd2fff2f2560046870bd7d5f14870c6d18d6242b356b9ef1b2101000000f90adac616a31dc2" + "e46a234558817151008fc9c900000073e64b0bd761fdf274005dca8ce1bd1871ae17bb4515856092" + "b4d9e89b010000006855676c277cf1bd017c9148da5892bb0351c3a70000009f3391ce7d3fd48469" + "005f0c233dbdd2a97835df1f1782dee86c1de913010000003ee1d76fc1ea76e98c9dfc78997ab53e" + "00c21963f5b4cb2454830c68e44ea74b002b83f9b3bee14d861a4c9eeecc89f65408c1b701000000" + "d9e1825fa638e1af284a024b7f9e85ff00e050a2000000276e08cb887464b93400c3127c750fabdd" + "69121aec129cdc690d58fbcc01000000d0d44ff1e51c35157666c05348e6f50700a8e89800000012" + "b900b2061d0c334b009f2dd1cdf64b0f9a60e0e289f08db3fde6b0250100000039dd8f88152a5845" + "4d9ca9d20f45dfa702e78604000000fbcd7db68b9ae586da00b4070c50320427c4dd3d031e33f22c" + "210aeb09010000007000b96d06992b6a58acd3995b9663d20248f333ef467092818aa77d6732b678" + "00844ef5a943825fcd743f59bd14c89b955e1a13010000007acbce3e3bcdf3824f1b134847ff26ba" + "009a774900000017ec852d59f3d17232edd86ad6c3103a68843a9aef34983882d3d3878501000000" + "5e0dfe491d1ba96742c7b5e02b2271220188b02a06d0dc5504b0595daf37deb499996bfb667f072d" + "ec1e5d9cdc8a11f4409bcfb30100000099d90a8543961b2ccd47724a3c460ba80383f4c500000063" + "eafb1ac4c0982b283aa9986700b2a2b3ed257b8b0489f48f053ec8ca0100000027335a25a364d101" + "5ffac03089f4553902b01784000000d7b83579b8da27345a72437f9b6245de39ec9e71ee4b951507" + "f121014a01000000efc67bce716c856e3973dc42a1003be94f89d8ee0000009b5d5bbe6c10085f3d" + "6176f78a19bb8df1804c122fce5078c156e8f3fe010000000ea8042602a1f8e6f5657f3f9e3eb807" + "cbad7645000000b8df6f628a70456d79f25d5895fb57fa60d9279fb2b8fcbac65ad47b8a01000000" + "da40d88d40a6d75bc404156225b7eedefc2b44574b15e2ae496ad01bf007eacb0a28aec868282510" + "b60291ea6480e356925b568a0000000087bb24e5264fd3ebe9cf9f6df9615189018ee815000000a7" + "5c9555876b6a3f13002b6cb8360feaac1d5c302df59dd32a7a859db500000000362956cd46646a0e" + "222160e5f769bb290366ed370000005b6a813387e99bb834009da86c64fefab2548759d313a5b92d" + "8e47935e0000000034f0386a253c21d94064f6b021281e230094ae20000000d71fd050bf8d85055b" + "00e3756ccdb3455c60ca7b11c66af76e594f24a100000000e143fd52599364e13468f80fd514573f" + "b572671c0000006932d1d5f5d0ce2cf6007a70ba5193a162bc92ec1b11d9172c857ae81200000000" + "3e29535402e9b690c628d048eacce745ea213cb1000000b632ef3be6070dafa200187470e9da5570" + "9427c226d324d9a08487fd0d00000000b7a350f9fc1519defa7db4532545666937c22a3b000000dc" + "c405cbee5016c25200d8901d7a0165fe20744edb6ba04f14a4c73cf500000000a4bab14874afdf54" + "e6aae816430607ca0675e09818e9bbec5918c59068baf76a008940f6fc3bbdc7f6090f756aae660b" + "6e4c699300000000d9c1e67743efb54e54270e46042e911803894e38000000376feecb80ac245409" + "c0becc271d9c2f67179bff0644399ae7e3c9472e00000000c5cfd9f2343b21362c19a0921dce2f83" + "00defa45000000270b9977e166bee737fe73670c439a644c323b59b4cd20eb7dabea74f700000000" + "f6989d2d6a909e986ff7add5df2c93e05459507b0000000f466554d2ae4d52a8c67b2e48b47003c8" + "1785d3ffdbd9a617df6b3542000000004c5cf8107ba282f4f983821918f93e742d08f0550000006f" + "2292362e5d68265d9f98c82d9b7a559be3acf4fc36fa6b5159747cf200000000ab2dadc5a39411fd" + "4ff1116d478987316a553fc2000000cfc6ebe434a7ae8ff040483e310819e3b10db116431ec6f769" + "438a72e1000000002495a609675344f7e2e3a5ebaec3c85f0a1742f70abe95c50345132a61eda239" + "d9d083c3bf085387046ef8a36f0e9e696b382ab0000000009a6ce5d906837dbca6a5ee19d6f63fe9" + "03dd01f7246f13b2050424a2b3a45ef7a029c896b4132bd895072cfeffe9d6815997069500000000" + "0d3c723b91adb0da7c4aa7e7eb5a15bc03015fb98b841fd84cc43c510385b9a4c2aac1d67a909b29" + "7c703915312e9c3cae02dfa000000000dd603bd35e7fa0f02f2f3313d8469d09a92409c0b7f0318a" + "575a4f8e061db3dd7fde25654a4059d565dbc8a91e3b4457b077ddad3108be69f9b97d05c917ad6b" + "10e693bb6e26f2ba90c8e909a9fe20e5c7a4c656482a9b0d00625009a40aeb62a42b6a62548e3c38" + "cd3c72f203000000ca82ac5180101be4f85cef468ea086ea01aafdc3a9fe1ec787bc45db7c52a52d" + "00bd39a44e8e8bc17c01ac63eca0c1cf5ff7f03a03000000c9a89192c1c8be55281a59d1fd338f35" + "0075f8cea9fec34573654ea6624f138ef9531cd9367a02e4d241989477a363d53b02239e03000000" + "24438387def0f4c6544e4b275d9b714600f810d2a9fec17647176b7c07d856e3b883efebc09dd9d6" + "1966b7ae7412041d57393c6f03000000182c0287822a272bec4501a1e27acfee018588ffa9fe6cae" + "426de59560fad65d67c624f285d7174177a47579dda0b6eaa9a84c820300000070b1646d8026e9f1" + "704f1b16286ba2da017ef082a9feed33ef60a8b540b26f66761d1f13badfad0fe8fa8f3c1aad2a82" + "fa40546c03000000df2d7c2790d3119a051bb2ee8192ac0cfa3abc1ea9fe3e7d75a2f42b50c6a363" + "40132378b95c59313bacba64dbe996206e6904f50200000047150b9b14010469823acb72bb89182f" + "00112196a9feb9153b36bc60be5b534e006527f67485ab35aca0c7ee419733853cf09e8b02000000" + "e79c10acfce165e332a62384ec04e5ba009185ada9fe0070a36dd51323b2c54200154d12f86c260a" + "9edfa7a74c1c83c1050f63f802000000443cacf59c6379a44b7892f487afa98c0221c19ca9fe942f" + "460bcea75481f25e007d0de9a7afe283bd2f22ead05ff72006c83bc0020000004bdade862c224f6f" + "36506ebd455e679c00369bb8a9fecec3f8c8fa6867a982be8a934f852cc3d4d82bc0ec7303f99f8f" + "def85b7502000000a0bef8675b29a197b7b3cceaf5f1bb120335256aa9fe6e5d58099ffc4a503a71" + "2350acbd48411f0dc15d2f0f49dad345d966279502000000e06302aba042aaa218dc091e9aa1477f" + "6fdc9830a9fec95829a8838314dff34d24c332219a1b163a732d803e0e2f4f916d06412601000000" + "98c39e7cc282208fefc57ff447036b9501adcb22a9fe793f797a3c7dadd1c86e009d0c90bc512e13" + "7dcef5e4a27985bd5cfd5ce601000000152f2b70eaef7443e0f79ab6902dde5301b3ec71a9fe9f25" + "4ac95883195580410062ed564153e17478f8c3344d89c0bbfaa100fd01000000be184524a6bdc878" + "9cf851782d895bcc03a98489a9fe8c1287e6f7bb020ffdb00012098610e52bb2a16a4008aefd545b" + "0d80684e01000000ba8110fb9733cc24904f288262e6ea77032fa5f8a9feeefa701d120523bd98f2" + "00098b43cd68be6e3f81268193fd637e9037d7a701000000c47cf0f551e96770a754ac19ef820fe0" + "0031f2d3a9fe049150b8d10ab700cc3a7cf51be0403b654ba2f56808092069af5f5b481b01000000" + "68cb3bc873b04d937a6ed8f7bc51e54066fed098a9fe048a92d3adc69a84eb47622400207799416a" + "f1f0a086fbd7e2f7dea0077a00000000c386e9c6e6a2cbfa10ee58bdc75183600085d627a9feb1cb" + "e491cbbbf9443fd6007eb3c5bf64b671d6f18dbf463f9b83f512dc1c00000000fbab244735d67c61" + "283031667b2d74a102e0b1b1a9fe2aa590a2312e17f1a35900459582f4ef43c780908872746e39ef" + "a9a89f8700000000fcdf6d9be94030b34774d1d7dddedd9802f0f627a9fe965a87041331b2834bcf" + "00b4e3ce848518c4e3f6cbf25e5e1b992231bb0200000000173c333cd03bc905b7b899afeb760e3f" + "024a2efca9fe96b89b1bc8f415bd4e77be46bae5a1b3cae76665a268abfe8a41a84e27c100000000" + "cb29efdf672d2fa57fc85ebbe276c5660364192da9fee7af5eb888e9eb37bb046686943b101e1f55" + "3215abf8fbdf17c3677e5a3a00000000608df061d45d864d09f4ecf17625f82d03c74828a9fe1e37" + "1051852c972ea7954079884af257b044fd13a6826a4c619f3d136cac000000009402a4c216772167" + "3f2b02b3256ead1f03ea9bc1a9fefca162fb81e733cff620ca7feefe1933631e8e69f6d9d6962d2c"), + [1] = ("54270e46020000006a894e387625da376feecb80ac245409c0becc271d9c2f67179bff0644399ae7" + "9c64ed0432d599eaa660ac824b7fae389861419c7a899f2d010000009bf5a5d06776e66a9d80ab13" + "2e1ac921eb76adbb229df32e561fa80a40e55b8b1dd92e18c0cfb2b4386bec092fa5757ecde9348b" + "c4e1966800000000b3121cd5ef51e696c816290dbaee0e7726d082e1530ff5397c9125f59577d71c" + "4b258a005116d11354edff62ceaa458fc75a91c425a5927300000000c4458d70911714f1819aa46f" + "be143934c933ae6dec0fb124f264af67eed7c9a8c38b6b8ee2614344bea73223e59bf0193432e9fa" + "4cc43c5103000000c2aac1d67a909b297c703915312e9c3c5a06c40b20ea99d5ab90135fa636af44" + "64d210a256be75e0509c4477d19ac64b2e69220d12ea3b9501000000fb95e8332fdfff658483a2d0" + "39a7bf148ed3481e0bccc460e89f7abdb682380a62eae374835b4a49a2b980b6aba92da6409969aa" + "288cd9bf00000000225a0c71b2ce78bb16104c8eaf18c565ecc8fb112087b97cfcebbaeb0c565359" + "23f14874042a8aff1d1f8e5ec3cc13cc36bbe3c9bb0ec36f0000000064a2827b7cdd38d0314c178e" + "925b568ae56bc4e9fefb24e5264fd3ebe9cf9f6df9615189f78ee815b2781ea55c9555876b6a3f13" + "02e9b69000000000eacce745ea213cb1a84035b632ef3be6070dafa2fed87470e9da55709427c226" + "d324d9a086ed27184b443181a5edc511c1507a142fea9bc10300000062fb81e733cff620ca7feefe" + "1933631e8e69f6d9fed62d2c6e6904f57239c09c47150b9b14010469823acb72bb89182f93112196" + "9985d62700000000e491cbbbf9443fd6a77eb3c5bf64b671d6f18dbffeff9b8306c83bc026977911" + "4bdade862c224f6f36506ebd455e679cef369bb8d514573f03000000f6932f6932d1d5f5d0ce2cf6" + "bf7a70ba5193a162fe92ec1b11d9172cf84a03478b82cdd8883b0f3fd6ed84d6c959e553b887edcd" + "6297dfa700000000fd0d53bb6215de4bb6f6f3d031a790287e45e9c1feb836c0a25ea71f5daaed4d" + "99eb2ebebe3c432fec7d3abc4ccf30d3aaaf02a41e0f515601000000f57609453df7803357ae7998" + "9870ccec3def0ad9000000000000000000000000000000019f05a16768cb3bc873b04d937a6ed8f7" + "ae8cb60b0300000038d856a40c67c3d4afa9c8649da90bd2fff2f256000000000000000000000000" + "00000001955e1a13d1111b067acbce3e3bcdf38200154d12010000009edfa7a74c1c83c18e47935e" + "8354846e34f0386a00000000000000000000000000000001ac26c7d41fd050bf8d85055bcfe3756c" + "a2f56808092069affaa100fdfdd68d34be184524a6bdc8789cf85178000000000000000000000000" + "00000000020ffdb0e612098610e52bb2a16a40086f0e9e6903000000ec2e2924348352d715cf9d41" + "1ea012e5307294b600000000000000000000000000000000ccd2f50c30706cae3fdcc2f0ee3d5e26" + "81ae55f0030000000aa20624fbab244735d67c61283031667b2d74a1000000000000000000000000" + "0000000000459582f4ef43c780908872746e39efaefd545b020000007ced4f8b362956cd46646a0e" + "222160e5f769bb2900000000000000000000000000000000a19da86c64fefab2548759d313a5b92d" + "f05d7308020000009d82c856ba942455050f63f8282f3464443cacf5000000000000000000000000" + "0000000069d9942f460bcea75481f25e307d0de96e91b91501000000be5b534e0a6527f67485ab35" + "aca0c7ee4197338500000000000000000000000000000000aefd4503189f7630248e501c9052c22b" + "5d68265d010000009b7a559be3acf4fc36fa6b513cf09e8b99289462000000000000000000000000" + "00000000b49185ad9b7d0070a36dd51323b2c542e5fe7d360100000066a3ae3939762df33a2d5506" + "0c78d551cbb110fd000000000000000000000000000000002c171ed15abbbe1cd911db7b56e8f3fe" + "9a6ce5d900000000a6a5ee19d6f63fe9cbdd01f7246f13b2050424a2000000000000000000000000" + "00000000ffe9d6812231bb02f922259b173c333cbae87d60000000005d31e5b2de252167c1c91b3a" + "36ba0c700ee78477000000000000000000000000000000006ad0143a0554efb46b382ab0404cdf02" + "7c52a52d030000004e8e8bc17c01ac63eca0c1cfbb204132fb7213e4000000000000000000000000" + "00000000accae11f8045a80f855ebf3b772eb2b1d03bc90503000000eb760e3f004a2efc8ffc96b8" + "9b1bc8f415bd4e7700000000000000000000000000000000d149f074a378b881153ece68ade15cec" + "7666c05303000000ffa8e89871fb9510b900b2061d0c334b819f2dd1000000000000000000000000" + "00000000095a3230fde69eec3af2e6d0bd68ab7210613fc90200000016a31dc2e46a234558817151" + "b38fc9c909cf8d7100000000000000000000000000000000ae17bb4515856092b4d9e89b676761b2" + "57d6249b020000007dc7f17f666969e45c3baf7a119a65ee0b06fa9e000000000000000000000000" + "00000000b314f695bd82e9ea8891ed0a3d1b91cab56e03e0010000002f70249eefc67bce716c856e" + "3973dc42a1003be9000000000000000000000000000000006176f78a19bb8df1804c122fce5078c1" + "eecc89f601000000d635edea8ffe423d01883918039249f398f9b37e000000000000000000000000" + "000000001fb7c8307f06b4b29088f20d9ac676d5f90e54b501000000a779f37c30d2c5053c40ecae" + "210aeb0934cb2a2400000000000000000000000000000000df48f333ef467092818aa77d6732b678" + "bcd7be0700000000e45aaf61814f703b40125e6baf4648cbaaa37b86000000000000000000000000" + "000000005ddb05f2a84e27c18a06e2c9cb29efdf50da49bf00000000d94beb259f9f0251becc9879" + "07879cca68fec7bb000000000000000000000000000000009402a4c2167721673f2b02b3256ead1f" + "a349e1cc00000000b879a45176740744fcb6a035339d504c9726d80d000000000000000000000000" + "000000009518862bc9a89192c1c8be55281a59d1ebfdcec30000000067a982be8a934f852cc3d4d8" + "2bc0ec7303f99f8f00000000000000000000000000000000901f55f960c55c22f32099a8020bea2a" + "5b1aea3401000000c7c3523f76cf1ce34ca17a9a32f3f0f218424e48000000000000000000000000" + "00000002e6fbf952902383028e3b47f2111d891d536c75bb01000000720da9e07ac998b5690807d5" + "2617369ee1af4ca500000000000000000000ffff47d7b351a36b178aabc78a4a0d80684ee5dbf45b" + "17a4763102000000f9119c40b468b66331bca67a034f43340d01683c00000000000000000000ffff" + "b24a5be9b10123e6e4f15ecd1dff045298cfee069c88b02a0300000004b0595daf37deb499996bfb" + "667f072dec1e5d9cdc8a11f4409bcfb3c167090e99d90a8543961b2ccd47724a3c460ba85183f4c5" + "a7afe28302000000d05ff720b16c73761a3fcb675219061ab4c5c79489b2cc6a883c146972decb8b" + "5adac8f5e6d66df46ea139107754dee62c36d2fcc7a4c65601000000cd625009a40aeb62a42b6a62" + "548e3c38b96c20022753c5fbfaf1809becd2506315a6da29b2e94d3ab51fe0e9af98b180a36035fb" + "e36f90d401000000fd41fe47684b370b6ec6584d64496089570968ead4d1ae91c819bb068196d599" + "efde3246e43f5e7945aaf95e2ffa3a11b9ef1b21cdb3455c00000000c66af76e80f062ea1a7e0b7b" + "e218d067530e1b0487b8c3b99f2b8a1a8982c42f53700a3437c5156e072b2f2fb337c9cfe07c34dd" + "c896c9a7000000000a5f6abd850b06e3d9662795ce2cc1cee06302aba042aaa218dc091e9aa1477f" + "6fdc9830d7fbc95829a8838314dff34d24c332214d4647f5000000000f88e3759a08e7a56663861e" + "ea1bf42ccd3c72f2ad063857ca82ac5180101be4f85cef468ea086ea9aaafdc388811ec787bc45db" + "7625ca4a00000000e682f80f94315c45ef817264c89a736ed55ed637b077ddad3108be69f9b97d05" + "c917ad6b10e693bb6e26f2ba90c8e909e85e20e5b0916e5d030000004a503a712350acbd48411f0d" + "c15d2f0f49dad345599706954d2471f00d3c723b91adb0da7c4aa7e7eb5a15bcde015fb98b841fd8" + "ba8110fb03000000904f288262e6ea77a22fa5f863eaeefa701d120523bd98f29b098b43cd68be6e" + "3f81268193fd637e585287dc6ca972a2cfb18b2b3cae864b0200000091921681c2162b9bfa40546c" + "db544e44df2d7c2790d3119a051bb2ee8192ac0cfa3abc1ea0ab3e7d75a2f42b50c6a36340132378" + "8c3b371502000000710c030690f5f18ea125dbf7d7e93bd65c1fa56dfdcfc1155f236c8b9c79a620" + "5f660bbf024b03ff0a8e27c405e6424457393c6fdac12ebd01000000667224dae775e5ccd141aca2" + "ff0f576767d3648102b61886b62f22ca33478e1f891715df7fc6a902edae579e2e10c7f7a22ba034" + "abf85f0c0100000008d33a2ea67776d7f88d69c8c7e3b2d3c93ef054e93f8120abb42316c533d9c9" + "4d88189c471a1ff2f0ce3ff66e782110125ae2eddaae7d6300000000c0982b283aa9986700b2a2b3" + "ed257b8b0489f48f053ec8cac72105b327335a25a364d1015ffac03089f45539e1b0178462156cd6" + "6855676c00000000017c9148da5892bb4951c3a7ed55689d3391ce7d3fd48469845f0c233dbdd2a9" + "7835df1f1782dee88487fd0db5971b46b7a350f998c39e7c00000000efc57ff447036b9551adcb22" + "fd50793f797a3c7dadd1c86e759d0c90bc512e137dcef5e4a27985bd8d1e69c83dc21056b973972c" + "bc51e540030000006c3d048a92d3adc69a84eb47622400207799416afef0a086fbd7e2f7dea0077a" + "e9c0c0d9c386e9c6e6a2cbfa10ee58bdc75183600422ae3f03000000c43bd794c697895e5bfe47c6" + "f53a54e01b0c1d89fecd94d0e02d2ec587fc330003781b0f294a2b73c4421398f4de67e9cee64b38" + "69121aec010000000fcc00ac2daa755e5292cd32dc096cd5d8a4c5cffe91ee1cfcc2056cc1ff8e51" + "bbce426cfa4b861cc78592be7b14e7ba9c15acb836674cde0200000054cfe8f42bf0e9381ba2c439" + "6de60f032bc7f34dfe959871c0d4c0d4eb720c4ae3c9472e5f2da5d9c5cfd9f2343b21362c19a092" + "b2cd3dbd01000000e958e48d2ab8b0313e7f8933e315130fa9a84c82feb83f6e70b1646d8026e9f1" + "704f1b16286ba2dabc7ef0820c91ed33ef60a8b5bcfe97e1000000007ad742d3d7320a4f880cf47f" + "5dd0cf69cb22840ffeb3fe7749509cb6752b2cee30b7e7736a0afc9879ea40e69710fc9f6e8e99cd" + "1dce2f83020000003a3efc240b9977e166bee737fe73670c439a644c000000000000000000000000" + "000000015e0dfe491d1ba96742c7b5e02b227122cf844ef501000000cd743f59bd14c89b5cfd5ce6" + "e82e7854152f2b700000000000000000000000000000000127e19f254ac9588319558041fc62ed56" + "b83579b80300000072437f9b6245de39ec9e71ee4b951507beb42b34000000000000000000000000" + "00000001d639272e4a46eadfbd2146bf8cfdb205b3980deb020000006c1de913df1424ce3ee1d76f" + "c1ea76e98c9dfc7800000000000000000000000000000001e44ea74b4f2b83f9b3bee14d861a4c9e" + "0436242c03000000ee3fbc164632d4f28aa28c08f80dd50c6712dfb4000000000000000000000000" + "00000000078a806d329c9b008bbfc9723107f3f52af145480300000004265f6c72923e22594f3f79" + "e208ed2e2ebb07710000000000000000000000000000000039dd8f88152a58454d9ca9d20f45dfa7" + "922b6cb8030000001d5c302df59dd32a677e5a3af4fe297f608df061000000000000000000000000" + "000000008ae21e371051852c972ea7954079884a9a1b163a030000000e2f4f911a0dd8c052ed6b13" + "e2815a1a37a52bdd00000000000000000000000000000000bd99fcf72909ac9e7a55299e9bb4fd53" + "9f3c2c8803000000f51f44e644d7cf1d06b2b115d882549e62c12fba000000000000000000000000" + "000000004fc812bd2f8a59d96d064126666befbbf257b044030000006a4c619f9037d7a754655354" + "c47cf0f551e967700000000000000000000000000000000050b8d10ab700cc3a7cf51be0403b654b" + "74e7860402000000cd7db68b9ae586dacdb4070c50320427c4dd3d03000000000000000000000000" + "00000000d538d34574c038404923e75d0e09e2fcfc1519de010000002545666937c22a3b6e9686dc" + "c405cbee5016c252000000000000000000000000000000006e4c699341b95f41d9c1e67743efb54e" + "6aae660b01000000fc1fb23d24438387def0f4c6544e4b275d9b7146000000000000000000000000" + "00000000b883efebc09dd9d61966b7ae7412041dd7ac5f9901000000822a272bec4501a1e27acfee" + "7a8588ffd5a06cae0000000000000000000000000000000077a47579dda0b6ead1e1b34901eaedd5" + "61c2f5d200000000b809d259e499db7dc8f1853bdcb0e4bc0e2b00b6000000000000000000000000" + "000000005e7fa0f02f2f3313d8469d09a92409c0b4d54acb00000000a417f3da01d99dae5f190096" + "cc582cabd5ddd15a00000000000000000000000000000000fdb3d117d76b0de5416f95e7bb75d72f" + "fc2b445700000000496ad01bf007eacb0a28aec868282510b60291ea000000000000000000000000" + "000000005b29a197b7b3cceaf5f1bb12c535256a74993ade03000000e62112898dab2dade2ab2fc9" + "c56a7c86be5f962200000000000000000000000000000000bdd06352cb2c354434819f4b248eb2b8" + "dc335c8b030000000a3c8e7c91bf821c4b09cee3c37ff4283a631480000000000000000000000000" + "000000005588456e4ead7cbda620a3abae816e34e3ff1a5f03000000b4425486c619147eb0216050" + "ed7afd741024e83600000000000000000000000000000000f493472201e48d163106cc397446a33f" + "bc74179403000000438a72e12ae0436a2495a609675344f7e2e3a5eb000000000000000000000000" + "0000000061eda239d9d083c3bf085387046ef8a34153e174020000004d89c0bb5408c1b7d17fb084" + "d9e1825fa638e1af000000000000000000000000000000006e08cb887464b9344ec3127c750fabdd" + "6c0fc09c02000000809829af4a9df58dbfef186d416f3a1ef170d10f000000000000000000000000" + "0000000087780281d47a32f2235376239daeaacba9a89f8701000000fcdf6d9be94030b34774d1d7" + "dddedd9899f0f62700000000000000000000000000000000848518c4e3f6cbf25e5e1b990fc46767" + "fd338f35010000006e8cc34573654ea6624f138ef9531cd9367a02e4000000000000000000000000" + "00000000da40d88d40a6d75bc404156225b7eede2cb749e2010000000eb4f73e75039fcf251b32fc" + "79685b05ddd3aa9b000000000000000000000000000000000d58fbcc58fc4c2fd0d44ff1e51c3515" + "b7f0318a01000000061db3dd7fde25654a4059d565dbc8a91e3b4457000000000000000000000000" + "00000000c1b5aad487c192f08bd7a09bdd21444359747cf200000000ab2dadc5a39411fd4ff1116d" + "478987316a553fc2000000000000000000000000000000000819e3b10db116431ec6f7691539ce41" + "b95c593100000000dbe9962069e66142e22753344fda8a3e0b841cf7000000000000000000000000" + "000000008c54dda9ac3bb1b1d43e6505621b9a7f672d2fa501000000e276c5661a64192db44ce7af" + "5eb888e9eb37bb0400000000000000000000000000000002641e447514ca565fb9b68c08a5e4351e" + "9f3c39f201000000de745cb25851429b3bbfb50168dfd04edf6b3542000000000000000000000000" + "0000000218f93e742d08f0550b55726f2292362e70ccc56700000000a4c73cf5ffa4ffc8a4bab148" + "74afdf54e6aae8160000000000000000000000000000000268baf76ab08940f6fc3bbdc7f6090f75" + "4f1b134803000000449a774929e86716ec852d59f3d17232edd86ad6000000000000000000000000" + "000000027dba1ee7e143fd52599364e13468f80f40b26f6603000000badfad0fe8fa8f3c1aad2a82" + "530b7447d9c2f401000000000000000000000000000000028147507e1cd59ea1ae6da48b1eba6d16" + "dedea2030100000002a1f8e6f5657f3f9e3eb807cbad76451055edb8000000000000000000000000" + "00000002d9279fb2b8fcbac6abea74f7a6df7979f6989d2d000000006ff7add5df2c93e05459507b" + "5d8c6a0f466554d200000000000000000000ffff1785d3ffdbd9a617857ae8126b028c0e3e295354"), + [2] = ("4cc43c5103000000c2aac1d67a909b297c703915312e9c3c5a06c40b20ea99d5ab90135fa636af44" + "64d210a256be75e0509c4477d19ac64b2e69220d54270e46020000006a894e387625da376feecb80" + "ac245409c0becc271d9c2f67179bff0644399ae79c64ed0432d599eaa660ac824b7fae389861419c" + "12ea3b9501000000fb95e8332fdfff658483a2d039a7bf148ed3481e0bccc460e89f7abdb682380a" + "62eae374835b4a49a2b980b6aba92da6409969aa7a899f2d010000009bf5a5d06776e66a9d80ab13" + "2e1ac921eb76adbb229df32e561fa80a40e55b8b1dd92e18c0cfb2b4386bec092fa5757ecde9348b" + "288cd9bf00000000225a0c71b2ce78bb16104c8eaf18c565ecc8fb112087b97cfcebbaeb0c565359" + "23f14874042a8aff1d1f8e5ec3cc13cc36bbe3c9c4e1966800000000b3121cd5ef51e696c816290d" + "baee0e7726d082e1530ff5397c9125f59577d71c4b258a005116d11354edff62ceaa458fc75a91c4" + "25a5927300000000c4458d70911714f1819aa46fbe143934c933ae6dec0fb124f264af67eed7c9a8" + "c38b6b8ee2614344bea73223e59bf0193432e9fa2fea9bc10300000062fb81e733cff620ca7feefe" + "1933631e8e69f6d9fed62d2c6e6904f57239c09c47150b9b14010469823acb72bb89182f93112196" + "9985d62700000000e491cbbbf9443fd6a77eb3c5bf64b671d6f18dbffeff9b8306c83bc026977911" + "4bdade862c224f6f36506ebd455e679cef369bb8bb0ec36f0000000064a2827b7cdd38d0314c178e" + "925b568ae56bc4e9fefb24e5264fd3ebe9cf9f6df9615189f78ee815b2781ea55c9555876b6a3f13" + "02e9b69000000000eacce745ea213cb1a84035b632ef3be6070dafa2fed87470e9da55709427c226" + "d324d9a086ed27184b443181a5edc511c1507a14d514573f03000000f6932f6932d1d5f5d0ce2cf6" + "bf7a70ba5193a162fe92ec1b11d9172cf84a03478b82cdd8883b0f3fd6ed84d6c959e553b887edcd" + "6297dfa700000000fd0d53bb6215de4bb6f6f3d031a790287e45e9c1feb836c0a25ea71f5daaed4d" + "99eb2ebebe3c432fec7d3abc4ccf30d3aaaf02a4ae8cb60b0300000038d856a40c67c3d4afa9c864" + "9da90bd2fff2f25600000000000000000000000000000001955e1a13d1111b067acbce3e3bcdf382" + "00154d12010000009edfa7a74c1c83c18e47935e8354846e34f0386a000000000000000000000000" + "00000001ac26c7d41fd050bf8d85055bcfe3756c1e0f515601000000f57609453df7803357ae7998" + "9870ccec3def0ad9000000000000000000000000000000019f05a16768cb3bc873b04d937a6ed8f7" + "7c52a52d030000004e8e8bc17c01ac63eca0c1cfbb204132fb7213e4000000000000000000000000" + "00000000accae11f8045a80f855ebf3b772eb2b1d03bc90503000000eb760e3f004a2efc8ffc96b8" + "9b1bc8f415bd4e7700000000000000000000000000000000d149f074a378b881153ece68ade15cec" + "6f0e9e6903000000ec2e2924348352d715cf9d411ea012e5307294b6000000000000000000000000" + "00000000ccd2f50c30706cae3fdcc2f0ee3d5e267666c05303000000ffa8e89871fb9510b900b206" + "1d0c334b819f2dd100000000000000000000000000000000095a3230fde69eec3af2e6d0bd68ab72" + "81ae55f0030000000aa20624fbab244735d67c61283031667b2d74a1000000000000000000000000" + "0000000000459582f4ef43c780908872746e39ef10613fc90200000016a31dc2e46a234558817151" + "b38fc9c909cf8d7100000000000000000000000000000000ae17bb4515856092b4d9e89b676761b2" + "57d6249b020000007dc7f17f666969e45c3baf7a119a65ee0b06fa9e000000000000000000000000" + "00000000b314f695bd82e9ea8891ed0a3d1b91ca6e91b91501000000be5b534e0a6527f67485ab35" + "aca0c7ee4197338500000000000000000000000000000000aefd4503189f7630248e501c9052c22b" + "5d68265d010000009b7a559be3acf4fc36fa6b513cf09e8b99289462000000000000000000000000" + "00000000b49185ad9b7d0070a36dd51323b2c542b56e03e0010000002f70249eefc67bce716c856e" + "3973dc42a1003be9000000000000000000000000000000006176f78a19bb8df1804c122fce5078c1" + "e5fe7d360100000066a3ae3939762df33a2d55060c78d551cbb110fd000000000000000000000000" + "000000002c171ed15abbbe1cd911db7b56e8f3feeecc89f601000000d635edea8ffe423d01883918" + "039249f398f9b37e000000000000000000000000000000001fb7c8307f06b4b29088f20d9ac676d5" + "f90e54b501000000a779f37c30d2c5053c40ecae210aeb0934cb2a24000000000000000000000000" + "00000000df48f333ef467092818aa77d6732b678bcd7be0700000000e45aaf61814f703b40125e6b" + "af4648cbaaa37b86000000000000000000000000000000005ddb05f2a84e27c18a06e2c9cb29efdf" + "50da49bf00000000d94beb259f9f0251becc987907879cca68fec7bb000000000000000000000000" + "000000009402a4c2167721673f2b02b3256ead1fa349e1cc00000000b879a45176740744fcb6a035" + "339d504c9726d80d000000000000000000000000000000009518862bc9a89192c1c8be55281a59d1" + "bae87d60000000005d31e5b2de252167c1c91b3a36ba0c700ee78477000000000000000000000000" + "000000006ad0143a0554efb46b382ab0404cdf02ebfdcec30000000067a982be8a934f852cc3d4d8" + "2bc0ec7303f99f8f00000000000000000000000000000000901f55f960c55c22f32099a8020bea2a" + "a2f56808092069affaa100fdfdd68d34be184524a6bdc8789cf85178000000000000000000000000" + "00000000020ffdb0e612098610e52bb2a16a4008aefd545b020000007ced4f8b362956cd46646a0e" + "222160e5f769bb2900000000000000000000000000000000a19da86c64fefab2548759d313a5b92d" + "f05d7308020000009d82c856ba942455050f63f8282f3464443cacf5000000000000000000000000" + "0000000069d9942f460bcea75481f25e307d0de99a6ce5d900000000a6a5ee19d6f63fe9cbdd01f7" + "246f13b2050424a200000000000000000000000000000000ffe9d6812231bb02f922259b173c333c" + "5b1aea3401000000c7c3523f76cf1ce34ca17a9a32f3f0f218424e48000000000000000000000000" + "00000002e6fbf952902383028e3b47f2111d891d17a4763102000000f9119c40b468b66331bca67a" + "034f43340d01683c00000000000000000000ffffb24a5be9b10123e6e4f15ecd1dff045298cfee06" + "536c75bb01000000720da9e07ac998b5690807d52617369ee1af4ca500000000000000000000ffff" + "47d7b351a36b178aabc78a4a0d80684ee5dbf45bb0916e5d030000004a503a712350acbd48411f0d" + "c15d2f0f49dad345599706954d2471f00d3c723b91adb0da7c4aa7e7eb5a15bcde015fb98b841fd8" + "ba8110fb03000000904f288262e6ea77a22fa5f863eaeefa701d120523bd98f29b098b43cd68be6e" + "3f81268193fd637e585287dc6ca972a2cfb18b2b3cae864b0200000091921681c2162b9bfa40546c" + "db544e44df2d7c2790d3119a051bb2ee8192ac0cfa3abc1ea0ab3e7d75a2f42b50c6a36340132378" + "8c3b371502000000710c030690f5f18ea125dbf7d7e93bd65c1fa56dfdcfc1155f236c8b9c79a620" + "5f660bbf024b03ff0a8e27c405e6424457393c6fdac12ebd01000000667224dae775e5ccd141aca2" + "ff0f576767d3648102b61886b62f22ca33478e1f891715df7fc6a902edae579e2e10c7f7a22ba034" + "abf85f0c0100000008d33a2ea67776d7f88d69c8c7e3b2d3c93ef054e93f8120abb42316c533d9c9" + "4d88189c471a1ff2f0ce3ff66e782110125ae2edc7a4c65601000000cd625009a40aeb62a42b6a62" + "548e3c38b96c20022753c5fbfaf1809becd2506315a6da29b2e94d3ab51fe0e9af98b180a36035fb" + "daae7d6300000000c0982b283aa9986700b2a2b3ed257b8b0489f48f053ec8cac72105b327335a25" + "a364d1015ffac03089f45539e1b0178462156cd66855676c00000000017c9148da5892bb4951c3a7" + "ed55689d3391ce7d3fd48469845f0c233dbdd2a97835df1f1782dee88487fd0db5971b46b7a350f9" + "cdb3455c00000000c66af76e80f062ea1a7e0b7be218d067530e1b0487b8c3b99f2b8a1a8982c42f" + "53700a3437c5156e072b2f2fb337c9cfe07c34ddc896c9a7000000000a5f6abd850b06e3d9662795" + "ce2cc1cee06302aba042aaa218dc091e9aa1477f6fdc9830d7fbc95829a8838314dff34d24c33221" + "98c39e7c00000000efc57ff447036b9551adcb22fd50793f797a3c7dadd1c86e759d0c90bc512e13" + "7dcef5e4a27985bd8d1e69c83dc21056b973972c7625ca4a00000000e682f80f94315c45ef817264" + "c89a736ed55ed637b077ddad3108be69f9b97d05c917ad6b10e693bb6e26f2ba90c8e909e85e20e5" + "9c88b02a0300000004b0595daf37deb499996bfb667f072dec1e5d9cdc8a11f4409bcfb3c167090e" + "99d90a8543961b2ccd47724a3c460ba85183f4c5a7afe28302000000d05ff720b16c73761a3fcb67" + "5219061ab4c5c79489b2cc6a883c146972decb8b5adac8f5e6d66df46ea139107754dee62c36d2fc" + "e36f90d401000000fd41fe47684b370b6ec6584d64496089570968ead4d1ae91c819bb068196d599" + "efde3246e43f5e7945aaf95e2ffa3a11b9ef1b214d4647f5000000000f88e3759a08e7a56663861e" + "ea1bf42ccd3c72f2ad063857ca82ac5180101be4f85cef468ea086ea9aaafdc388811ec787bc45db" + "0422ae3f03000000c43bd794c697895e5bfe47c6f53a54e01b0c1d89fecd94d0e02d2ec587fc3300" + "03781b0f294a2b73c4421398f4de67e9cee64b38bc51e540030000006c3d048a92d3adc69a84eb47" + "622400207799416afef0a086fbd7e2f7dea0077ae9c0c0d9c386e9c6e6a2cbfa10ee58bdc7518360" + "36674cde0200000054cfe8f42bf0e9381ba2c4396de60f032bc7f34dfe959871c0d4c0d4eb720c4a" + "e3c9472e5f2da5d9c5cfd9f2343b21362c19a092b2cd3dbd01000000e958e48d2ab8b0313e7f8933" + "e315130fa9a84c82feb83f6e70b1646d8026e9f1704f1b16286ba2dabc7ef0820c91ed33ef60a8b5" + "bcfe97e1000000007ad742d3d7320a4f880cf47f5dd0cf69cb22840ffeb3fe7749509cb6752b2cee" + "30b7e7736a0afc9879ea40e69710fc9f6e8e99cd69121aec010000000fcc00ac2daa755e5292cd32" + "dc096cd5d8a4c5cffe91ee1cfcc2056cc1ff8e51bbce426cfa4b861cc78592be7b14e7ba9c15acb8" + "b83579b80300000072437f9b6245de39ec9e71ee4b951507beb42b34000000000000000000000000" + "00000001d639272e4a46eadfbd2146bf8cfdb205b3980deb020000006c1de913df1424ce3ee1d76f" + "c1ea76e98c9dfc7800000000000000000000000000000001e44ea74b4f2b83f9b3bee14d861a4c9e" + "cf844ef501000000cd743f59bd14c89b5cfd5ce6e82e7854152f2b70000000000000000000000000" + "0000000127e19f254ac9588319558041fc62ed561dce2f83020000003a3efc240b9977e166bee737" + "fe73670c439a644c000000000000000000000000000000015e0dfe491d1ba96742c7b5e02b227122" + "2af145480300000004265f6c72923e22594f3f79e208ed2e2ebb0771000000000000000000000000" + "0000000039dd8f88152a58454d9ca9d20f45dfa774993ade03000000e62112898dab2dade2ab2fc9" + "c56a7c86be5f962200000000000000000000000000000000bdd06352cb2c354434819f4b248eb2b8" + "922b6cb8030000001d5c302df59dd32a677e5a3af4fe297f608df061000000000000000000000000" + "000000008ae21e371051852c972ea7954079884a9f3c2c8803000000f51f44e644d7cf1d06b2b115" + "d882549e62c12fba000000000000000000000000000000004fc812bd2f8a59d96d064126666befbb" + "dc335c8b030000000a3c8e7c91bf821c4b09cee3c37ff4283a631480000000000000000000000000" + "000000005588456e4ead7cbda620a3abae816e34e3ff1a5f03000000b4425486c619147eb0216050" + "ed7afd741024e83600000000000000000000000000000000f493472201e48d163106cc397446a33f" + "f257b044030000006a4c619f9037d7a754655354c47cf0f551e96770000000000000000000000000" + "0000000050b8d10ab700cc3a7cf51be0403b654bbc74179403000000438a72e12ae0436a2495a609" + "675344f7e2e3a5eb0000000000000000000000000000000061eda239d9d083c3bf085387046ef8a3" + "4153e174020000004d89c0bb5408c1b7d17fb084d9e1825fa638e1af000000000000000000000000" + "000000006e08cb887464b9344ec3127c750fabdd6c0fc09c02000000809829af4a9df58dbfef186d" + "416f3a1ef170d10f0000000000000000000000000000000087780281d47a32f2235376239daeaacb" + "74e7860402000000cd7db68b9ae586dacdb4070c50320427c4dd3d03000000000000000000000000" + "00000000d538d34574c038404923e75d0e09e2fca9a89f8701000000fcdf6d9be94030b34774d1d7" + "dddedd9899f0f62700000000000000000000000000000000848518c4e3f6cbf25e5e1b990fc46767" + "fd338f35010000006e8cc34573654ea6624f138ef9531cd9367a02e4000000000000000000000000" + "00000000da40d88d40a6d75bc404156225b7eede2cb749e2010000000eb4f73e75039fcf251b32fc" + "79685b05ddd3aa9b000000000000000000000000000000000d58fbcc58fc4c2fd0d44ff1e51c3515" + "6aae660b01000000fc1fb23d24438387def0f4c6544e4b275d9b7146000000000000000000000000" + "00000000b883efebc09dd9d61966b7ae7412041db7f0318a01000000061db3dd7fde25654a4059d5" + "65dbc8a91e3b445700000000000000000000000000000000c1b5aad487c192f08bd7a09bdd214443" + "61c2f5d200000000b809d259e499db7dc8f1853bdcb0e4bc0e2b00b6000000000000000000000000" + "000000005e7fa0f02f2f3313d8469d09a92409c0b4d54acb00000000a417f3da01d99dae5f190096" + "cc582cabd5ddd15a00000000000000000000000000000000fdb3d117d76b0de5416f95e7bb75d72f" + "59747cf200000000ab2dadc5a39411fd4ff1116d478987316a553fc2000000000000000000000000" + "000000000819e3b10db116431ec6f7691539ce41b95c593100000000dbe9962069e66142e2275334" + "4fda8a3e0b841cf7000000000000000000000000000000008c54dda9ac3bb1b1d43e6505621b9a7f" + "fc2b445700000000496ad01bf007eacb0a28aec868282510b60291ea000000000000000000000000" + "000000005b29a197b7b3cceaf5f1bb12c535256a0436242c03000000ee3fbc164632d4f28aa28c08" + "f80dd50c6712dfb400000000000000000000000000000000078a806d329c9b008bbfc9723107f3f5" + "9a1b163a030000000e2f4f911a0dd8c052ed6b13e2815a1a37a52bdd000000000000000000000000" + "00000000bd99fcf72909ac9e7a55299e9bb4fd53fc1519de010000002545666937c22a3b6e9686dc" + "c405cbee5016c252000000000000000000000000000000006e4c699341b95f41d9c1e67743efb54e" + "d7ac5f9901000000822a272bec4501a1e27acfee7a8588ffd5a06cae000000000000000000000000" + "0000000077a47579dda0b6ead1e1b34901eaedd54f1b134803000000449a774929e86716ec852d59" + "f3d17232edd86ad6000000000000000000000000000000027dba1ee7e143fd52599364e13468f80f" + "40b26f6603000000badfad0fe8fa8f3c1aad2a82530b7447d9c2f401000000000000000000000000" + "000000028147507e1cd59ea1ae6da48b1eba6d16dedea2030100000002a1f8e6f5657f3f9e3eb807" + "cbad76451055edb800000000000000000000000000000002d9279fb2b8fcbac6abea74f7a6df7979" + "672d2fa501000000e276c5661a64192db44ce7af5eb888e9eb37bb04000000000000000000000000" + "00000002641e447514ca565fb9b68c08a5e4351e9f3c39f201000000de745cb25851429b3bbfb501" + "68dfd04edf6b35420000000000000000000000000000000218f93e742d08f0550b55726f2292362e" + "70ccc56700000000a4c73cf5ffa4ffc8a4bab14874afdf54e6aae816000000000000000000000000" + "0000000268baf76ab08940f6fc3bbdc7f6090f75f6989d2d000000006ff7add5df2c93e05459507b" + "5d8c6a0f466554d200000000000000000000ffff1785d3ffdbd9a617857ae8126b028c0e3e295354"), + }; + const int TEST_DATA_I = GPOINTER_TO_INT(test_data); + const int addr_family = (TEST_DATA_I == 0 ? AF_INET : AF_INET6); + const int IPV6_PREFER_TEMP = (TEST_DATA_I == 2); + const guint N_ADDRESSES = 100; + const gsize ELM_SIZE = + addr_family == AF_INET ? sizeof(NMPlatformIP4Address) : sizeof(NMPlatformIP6Address); + const gboolean DO_REGENERATE = FALSE; + const gboolean PRINT_RESULT = DO_REGENERATE; + const gboolean CHECK_RESULT = !DO_REGENERATE; + gs_free guint8 *addresses = NULL; + gs_free guint64 *rand_map = NULL; + gsize i, j; + +#if !defined(__amd64__) + /* The test generates a random array of NMPlatformIPXAddress (by crudely randomizing the memory, + * not the structures themself) and then compares the sorted result with the expected output. + * The sole purpose is to ensure that the sorting order stays stable. + * + * This only works on an architecture for which the test was made, otherwise + * the expected data does not match (due to different layout of the structures + * in memory). + * + * That's fine. Skip the test. */ + g_test_skip("skip test on non-amd64 architecture"); + return; +#endif + + /* + * First we create a list of addresses filled with (stable) random bytes. + * We tweak some fields explicitly (stable randomly), so that we cover all + * relevant cases. + * + * Then we sort the list of addresses, and compare that the result is + * as our EXPECTED_BUFFER. + */ + + addresses = g_malloc(ELM_SIZE * N_ADDRESSES); + rand_map = g_malloc(sizeof(rand_map[0]) * N_ADDRESSES); + nmtst_stable_rand(258829693, addresses, ELM_SIZE * N_ADDRESSES); + nmtst_stable_rand(710086081, rand_map, sizeof(rand_map[0]) * N_ADDRESSES); + + for (i = 0; i < N_ADDRESSES; i++) { + NMPlatformIPXAddress *a = (gpointer)(&addresses[i * ELM_SIZE]); + guint64 r = rand_map[i]; + struct in6_addr * a6; + +#define CONSUME_BITS(r, nbits) \ + ({ \ + guint64 _r = (r); \ + const int _nbits = (nbits); \ + \ + r >>= (_nbits); \ + (_r & (((((guint64) 1u) << (_nbits)) - 1u))); \ + }) + + if (addr_family == AF_INET) { + if (CONSUME_BITS(r, 1)) { + /* randomly create a link-local address or not. */ + g_assert((NM_IPV4LL_NETWORK & ~NM_IPV4LL_NETMASK) == 0); + a->a4.address = (a->a4.address & ~NM_IPV4LL_NETMASK) | NM_IPV4LL_NETWORK; + } + } else { + a6 = &a->a6.address; + switch (CONSUME_BITS(r, 4)) { + case 0: + a6->s6_addr32[0] = 0; + a6->s6_addr32[1] = 0; + a6->s6_addr32[2] = htonl(0xffff); + g_assert(IN6_IS_ADDR_V4MAPPED(a6)); + break; + case 1: + a6->s6_addr32[0] = 0; + a6->s6_addr32[1] = 0; + a6->s6_addr32[2] = 0; + a6->s6_addr32[3] = htonl(2); + g_assert(IN6_IS_ADDR_V4COMPAT(a6)); + break; + case 2: + a6->s6_addr32[0] = 0; + a6->s6_addr32[1] = 0; + a6->s6_addr32[2] = 0; + a6->s6_addr32[3] = 0; + g_assert(IN6_IS_ADDR_UNSPECIFIED(a6)); + break; + case 3: + a6->s6_addr32[0] = 0; + a6->s6_addr32[1] = 0; + a6->s6_addr32[2] = 0; + a6->s6_addr32[3] = htonl(1); + g_assert(IN6_IS_ADDR_LOOPBACK(a6)); + break; + case 4: + a6->s6_addr32[0] = (a6->s6_addr32[0] & ~htonl(0xffc00000)) | htonl(0xfe800000); + g_assert(IN6_IS_ADDR_LINKLOCAL(a6)); + break; + case 6: + a6->s6_addr32[0] = (a6->s6_addr32[0] & ~htonl(0xffc00000)) | htonl(0xfec00000); + g_assert(IN6_IS_ADDR_SITELOCAL(a6)); + break; + case 7: + case 8: + case 9: + case 10: + break; + default: + memset(a6, 0, sizeof(*a6)); + break; + } + } + + if (CONSUME_BITS(r, 5) != 0) { + /* randomly make addr-source the same (so that several addresses compare + * equal). */ + a->a4.addr_source = CONSUME_BITS(r, 2); + } + + if (addr_family == AF_INET) { + if (CONSUME_BITS(r, 1)) { + /* randomly make the label empty or not. */ + a->a4.label[0] = '\0'; + } + } + if (addr_family == AF_INET) { + if (CONSUME_BITS(r, 2) != 0) { + /* randomly make the label empty or not. */ + a->a4.plen = CONSUME_BITS(r, 2); + } + } + if (addr_family == AF_INET) { + if (CONSUME_BITS(r, 1)) { + a->a4.address = (a->a4.address & ~0xFFFFFFu) | (CONSUME_BITS(r, 2) << 24); + } + } + } + + g_qsort_with_data(addresses, + N_ADDRESSES, + ELM_SIZE, + _address_pretty_sort_cmp, + (gpointer) test_data); + + for (i = 0; i < N_ADDRESSES; i++) { + const NMPlatformIPXAddress *a = (gconstpointer)(&addresses[i * ELM_SIZE]); + + for (j = i + 1; j < N_ADDRESSES; j++) { + const NMPlatformIPXAddress *b = (gconstpointer)(&addresses[j * ELM_SIZE]); + int c1; + int c2; + + if (addr_family == AF_INET) { + c1 = nm_platform_ip4_address_pretty_sort_cmp(&a->a4, &b->a4); + c2 = nm_platform_ip4_address_pretty_sort_cmp(&b->a4, &a->a4); + } else { + c1 = nm_platform_ip6_address_pretty_sort_cmp(&a->a6, &b->a6, IPV6_PREFER_TEMP); + c2 = nm_platform_ip6_address_pretty_sort_cmp(&b->a6, &a->a6, IPV6_PREFER_TEMP); + } + + g_assert_cmpint(c1, >=, -1); + g_assert_cmpint(c1, <=, 1); + g_assert_cmpint(c1, ==, -c2); + } + } + + if (PRINT_RESULT) { + g_print("\n [%d] = (", TEST_DATA_I); + for (i = 0; i < ELM_SIZE * N_ADDRESSES;) { + if (i > 0) + g_print("\n "); + g_print("\""); + for (j = 0; j < 40 && i < ELM_SIZE * N_ADDRESSES; j++, i++) + g_print("%02x", addresses[i]); + g_print("\""); + } + g_print("),\n"); + return; + } + + if (CHECK_RESULT) { + gs_free guint8 *bin_arr = NULL; + gsize bin_len; + + bin_arr = nm_utils_hexstr2bin_alloc(EXPECTED_BUFFER[TEST_DATA_I], + FALSE, + FALSE, + NULL, + 0, + &bin_len); + + if (bin_len != ELM_SIZE * N_ADDRESSES || memcmp(addresses, bin_arr, bin_len) != 0) { + char *addresses_str = nm_utils_bin2hexstr(addresses, ELM_SIZE * N_ADDRESSES, -1); + + g_error(">>> test_platform_ip_address_pretty_sort_cmp() will fail:\n" + ">>> addresses[%zu]: %s\n" + ">>> expected [%zu]: %s\n", + ELM_SIZE * N_ADDRESSES, + addresses_str, + bin_len, + EXPECTED_BUFFER[TEST_DATA_I]); + } + + g_assert_cmpmem(addresses, ELM_SIZE * N_ADDRESSES, bin_arr, bin_len); + } +} + +/*****************************************************************************/ + +NMTST_DEFINE(); + +int +main(int argc, char **argv) +{ + nmtst_init_assert_logging(&argc, &argv, "WARN", "DEFAULT"); + + g_test_add_func("/general/init_linux_platform", test_init_linux_platform); + g_test_add_func("/general/link_get_all", test_link_get_all); + g_test_add_func("/general/nm_platform_link_flags2str", test_nm_platform_link_flags2str); + g_test_add_data_func("/general/platform_ip_address_pretty_sort_cmp/4", + GINT_TO_POINTER(0), + test_platform_ip_address_pretty_sort_cmp); + g_test_add_data_func("/general/platform_ip_address_pretty_sort_cmp/6/1", + GINT_TO_POINTER(1), + test_platform_ip_address_pretty_sort_cmp); + g_test_add_data_func("/general/platform_ip_address_pretty_sort_cmp/6/2", + GINT_TO_POINTER(2), + test_platform_ip_address_pretty_sort_cmp); + + return g_test_run(); +} diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c new file mode 100644 index 00000000..47587865 --- /dev/null +++ b/src/core/platform/tests/test-route.c @@ -0,0 +1,1926 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 - 2017 Red Hat, Inc. + */ + +#include "src/core/nm-default-daemon.h" + +#include <linux/rtnetlink.h> +#include <linux/fib_rules.h> + +#include "nm-core-utils.h" +#include "nm-platform/nm-platform-utils.h" +#include "platform/nmp-rules-manager.h" + +#include "test-common.h" + +#define DEVICE_IFINDEX NMTSTP_ENV1_IFINDEX +#define EX NMTSTP_ENV1_EX + +static void +_wait_for_ipv4_addr_device_route(NMPlatform *platform, + gint64 timeout_msec, + int ifindex, + in_addr_t addr, + guint8 plen) +{ + /* Wait that the addresses gets a device-route. After adding a address, + * the device route is not added immediately. It takes a moment... */ + + addr = nm_utils_ip4_address_clear_host_address(addr, plen); + NMTST_WAIT_ASSERT(400, { + NMDedupMultiIter iter; + NMPLookup lookup; + const NMPObject *o; + + nmp_cache_iter_for_each ( + &iter, + nm_platform_lookup(platform, + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex)), + &o) { + const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE(o); + + if (r->plen == plen && addr == nm_utils_ip4_address_clear_host_address(r->network, plen) + && r->metric == 0 && r->scope_inv == nm_platform_route_scope_inv(RT_SCOPE_LINK) + && r->rt_source == NM_IP_CONFIG_SOURCE_RTPROT_KERNEL) + return; + } + nmtstp_assert_wait_for_signal(platform, + (nmtst_wait_end_us - g_get_monotonic_time()) / 1000); + }); +} + +static void +_wait_for_ipv6_addr_non_tentative(NMPlatform * platform, + gint64 timeout_msec, + int ifindex, + guint addr_n, + const struct in6_addr *addrs) +{ + guint i; + + /* Wait that the addresses become non-tentative. Dummy interfaces are NOARP + * and thus don't do DAD, but the kernel sets the address as tentative for a + * small amount of time, which prevents the immediate addition of the route + * with RTA_PREFSRC */ + + NMTST_WAIT_ASSERT(timeout_msec, { + gboolean should_wait = FALSE; + const NMPlatformIP6Address *plt_addr; + + for (i = 0; i < addr_n; i++) { + plt_addr = nm_platform_ip6_address_get(platform, ifindex, &addrs[i]); + if (!plt_addr || NM_FLAGS_HAS(plt_addr->n_ifa_flags, IFA_F_TENTATIVE)) { + should_wait = TRUE; + break; + } + } + if (!should_wait) + return; + nmtstp_assert_wait_for_signal(platform, + (nmtst_wait_end_us - g_get_monotonic_time()) / 1000); + }); +} + +static void +ip4_route_callback(NMPlatform * platform, + int obj_type_i, + int ifindex, + const NMPlatformIP4Route *received, + int change_type_i, + SignalData * data) +{ + const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; + NMPObject o_id; + nm_auto_nmpobj NMPObject *o_id_p = nmp_object_new(NMP_OBJECT_TYPE_IP4_ROUTE, NULL); + + g_assert_cmpint(obj_type, ==, NMP_OBJECT_TYPE_IP4_ROUTE); + g_assert(received); + g_assert_cmpint(received->ifindex, ==, ifindex); + g_assert(data && data->name); + g_assert_cmpstr(data->name, ==, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED); + + /* run code for initializing the ID only */ + nmp_object_stackinit_id(&o_id, NMP_OBJECT_UP_CAST(received)); + nmp_object_copy(o_id_p, NMP_OBJECT_UP_CAST(received), TRUE); + nmp_object_copy(o_id_p, NMP_OBJECT_UP_CAST(received), FALSE); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + if (data->change_type != change_type) + return; + + if (data->loop) + g_main_loop_quit(data->loop); + + data->received_count++; + _LOGD("Received signal '%s' %dth time.", data->name, data->received_count); +} + +static void +ip6_route_callback(NMPlatform * platform, + int obj_type_i, + int ifindex, + const NMPlatformIP6Route *received, + int change_type_i, + SignalData * data) +{ + const NMPObjectType obj_type = obj_type_i; + const NMPlatformSignalChangeType change_type = change_type_i; + NMPObject o_id; + nm_auto_nmpobj NMPObject *o_id_p = nmp_object_new(NMP_OBJECT_TYPE_IP6_ROUTE, NULL); + + g_assert_cmpint(obj_type, ==, NMP_OBJECT_TYPE_IP6_ROUTE); + g_assert(received); + g_assert_cmpint(received->ifindex, ==, ifindex); + g_assert(data && data->name); + g_assert_cmpstr(data->name, ==, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED); + + /* run code for initializing the ID only */ + nmp_object_stackinit_id(&o_id, NMP_OBJECT_UP_CAST(received)); + nmp_object_copy(o_id_p, NMP_OBJECT_UP_CAST(received), TRUE); + nmp_object_copy(o_id_p, NMP_OBJECT_UP_CAST(received), FALSE); + + if (data->ifindex && data->ifindex != received->ifindex) + return; + if (data->change_type != change_type) + return; + + if (data->loop) + g_main_loop_quit(data->loop); + + data->received_count++; + _LOGD("Received signal '%s' %dth time.", data->name, data->received_count); +} + +static void +test_ip4_route_metric0(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + SignalData *route_added = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip4_route_callback); + SignalData *route_changed = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + ip4_route_callback); + SignalData *route_removed = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip4_route_callback); + in_addr_t network = + nmtst_inet4_from_string("192.0.2.5"); /* from 192.0.2.0/24 (TEST-NET-1) (rfc5737) */ + int plen = 32; + int metric = 22987; + int mss = 1000; + + /* No routes initially */ + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, metric, 0); + + /* add the first route */ + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + INADDR_ANY, + 0, + metric, + mss); + accept_signal(route_added); + + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + + /* Deleting route with metric 0 does nothing */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, 0)); + ensure_no_signal(route_removed); + + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + + /* add the second route */ + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + INADDR_ANY, + 0, + 0, + mss); + accept_signal(route_added); + + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + + /* Delete route with metric 0 */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, 0)); + accept_signal(route_removed); + + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + + /* Delete route with metric 0 again (we expect nothing to happen) */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, 0)); + ensure_no_signal(route_removed); + + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + + /* Delete the other route */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, metric)); + accept_signal(route_removed); + + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, 0, 0); + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, metric, 0); + + free_signal(route_added); + free_signal(route_changed); + free_signal(route_removed); +} + +static void +test_ip4_route(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + SignalData * route_added = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip4_route_callback); + SignalData * route_changed = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + ip4_route_callback); + SignalData * route_removed = add_signal(NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip4_route_callback); + GPtrArray * routes; + NMPlatformIP4Route rts[3]; + in_addr_t network; + guint8 plen = 24; + in_addr_t gateway; + /* Choose a high metric so that we hopefully don't conflict. */ + int metric = 22986; + int mss = 1000; + + inet_pton(AF_INET, "192.0.3.0", &network); + inet_pton(AF_INET, "198.51.100.1", &gateway); + + /* Add route to gateway */ + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + gateway, + 32, + INADDR_ANY, + 0, + metric, + mss); + accept_signal(route_added); + + /* Add route */ + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, metric, 0); + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + gateway, + 0, + metric, + mss); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, network, plen, metric, 0); + accept_signal(route_added); + + /* Add route again */ + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + gateway, + 0, + metric, + mss); + accept_signals(route_changed, 0, 1); + + /* Add default route */ + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, 0, 0, metric, 0); + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + gateway, + 0, + metric, + mss); + nmtstp_assert_ip4_route_exists(NULL, 1, DEVICE_NAME, 0, 0, metric, 0); + accept_signal(route_added); + + /* Add default route again */ + nmtstp_ip4_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + 0, + 0, + gateway, + 0, + metric, + mss); + accept_signals(route_changed, 0, 1); + + /* Test route listing */ + routes = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, ifindex); + memset(rts, 0, sizeof(rts)); + rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[0].network = gateway; + rts[0].plen = 32; + rts[0].ifindex = ifindex; + rts[0].gateway = INADDR_ANY; + rts[0].metric = metric; + rts[0].mss = mss; + rts[0].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_LINK); + rts[1].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[1].network = network; + rts[1].plen = plen; + rts[1].ifindex = ifindex; + rts[1].gateway = gateway; + rts[1].metric = metric; + rts[1].mss = mss; + rts[1].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE); + rts[2].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[2].network = 0; + rts[2].plen = 0; + rts[2].ifindex = ifindex; + rts[2].gateway = gateway; + rts[2].metric = metric; + rts[2].mss = mss; + rts[2].scope_inv = nm_platform_route_scope_inv(RT_SCOPE_UNIVERSE); + g_assert_cmpint(routes->len, ==, 3); + nmtst_platform_ip4_routes_equal_aptr((const NMPObject *const *) routes->pdata, + rts, + routes->len, + TRUE); + g_ptr_array_unref(routes); + + /* Remove route */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, metric)); + nmtstp_assert_ip4_route_exists(NULL, 0, DEVICE_NAME, network, plen, metric, 0); + accept_signal(route_removed); + + /* Remove route again */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, network, plen, metric)); + + /* Remove default route */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, 0, 0, metric)); + accept_signal(route_removed); + + /* Remove route to gateway */ + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, ifindex, gateway, 32, metric)); + accept_signal(route_removed); + + free_signal(route_added); + free_signal(route_changed); + free_signal(route_removed); +} + +static void +test_ip6_route(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + SignalData * route_added = add_signal(NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_ADDED, + ip6_route_callback); + SignalData * route_changed = add_signal(NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_CHANGED, + ip6_route_callback); + SignalData * route_removed = add_signal(NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, + ip6_route_callback); + GPtrArray * routes; + NMPlatformIP6Route rts[3]; + struct in6_addr network; + guint8 plen = 64; + struct in6_addr gateway, pref_src; + /* Choose a high metric so that we hopefully don't conflict. */ + const int metric = 22987; + int mss = 1000; + + inet_pton(AF_INET6, "2001:db8:a:b:0:0:0:0", &network); + inet_pton(AF_INET6, "2001:db8:c:d:1:2:3:4", &gateway); + inet_pton(AF_INET6, "::42", &pref_src); + + g_assert(nm_platform_ip6_address_add(NM_PLATFORM_GET, + ifindex, + pref_src, + 128, + in6addr_any, + NM_PLATFORM_LIFETIME_PERMANENT, + NM_PLATFORM_LIFETIME_PERMANENT, + 0)); + accept_signals(route_added, 0, 3); + + _wait_for_ipv6_addr_non_tentative(NM_PLATFORM_GET, 200, ifindex, 1, &pref_src); + + /* Add route to gateway */ + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + gateway, + 128, + in6addr_any, + in6addr_any, + metric, + mss); + accept_signals(route_added, 0, 3); + + /* Add route */ + g_assert(!nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0)); + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + gateway, + pref_src, + metric, + mss); + g_assert(nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0)); + accept_signal(route_added); + + /* Add route again */ + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + network, + plen, + gateway, + pref_src, + metric, + mss); + accept_signals(route_changed, 0, 1); + + /* Add default route */ + g_assert(!nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &in6addr_any, 0, metric, NULL, 0)); + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + in6addr_any, + 0, + gateway, + in6addr_any, + metric, + mss); + g_assert(nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &in6addr_any, 0, metric, NULL, 0)); + accept_signal(route_added); + + /* Add default route again */ + nmtstp_ip6_route_add(NM_PLATFORM_GET, + ifindex, + NM_IP_CONFIG_SOURCE_USER, + in6addr_any, + 0, + gateway, + in6addr_any, + metric, + mss); + accept_signals(route_changed, 0, 1); + + /* Test route listing */ + routes = nmtstp_ip6_route_get_all(NM_PLATFORM_GET, ifindex); + memset(rts, 0, sizeof(rts)); + rts[0].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[0].network = gateway; + rts[0].plen = 128; + rts[0].ifindex = ifindex; + rts[0].gateway = in6addr_any; + rts[0].pref_src = in6addr_any; + rts[0].metric = metric; + rts[0].mss = mss; + rts[1].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[1].network = network; + rts[1].plen = plen; + rts[1].ifindex = ifindex; + rts[1].gateway = gateway; + rts[1].pref_src = pref_src; + rts[1].metric = metric; + rts[1].mss = mss; + rts[2].rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER); + rts[2].network = in6addr_any; + rts[2].plen = 0; + rts[2].ifindex = ifindex; + rts[2].gateway = gateway; + rts[2].pref_src = in6addr_any; + rts[2].metric = metric; + rts[2].mss = mss; + g_assert_cmpint(routes->len, ==, 3); + nmtst_platform_ip6_routes_equal_aptr((const NMPObject *const *) routes->pdata, + rts, + routes->len, + TRUE); + g_ptr_array_unref(routes); + + /* Remove route */ + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, ifindex, network, plen, metric)); + g_assert(!nmtstp_ip6_route_get(NM_PLATFORM_GET, ifindex, &network, plen, metric, NULL, 0)); + accept_signal(route_removed); + + /* Remove route again */ + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, ifindex, network, plen, metric)); + + /* Remove default route */ + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, ifindex, in6addr_any, 0, metric)); + accept_signal(route_removed); + + /* Remove route to gateway */ + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, ifindex, gateway, 128, metric)); + accept_signal(route_removed); + + free_signal(route_added); + free_signal(route_changed); + free_signal(route_removed); +} + +/*****************************************************************************/ + +static void +test_ip4_route_get(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + in_addr_t a; + int result; + nm_auto_nmpobj NMPObject *route = NULL; + const NMPlatformIP4Route *r; + + nmtstp_run_command_check("ip route add 1.2.3.0/24 dev %s", DEVICE_NAME); + + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 10); + if (nmtstp_ip4_route_get(NM_PLATFORM_GET, + ifindex, + nmtst_inet4_from_string("1.2.3.0"), + 24, + 0, + 0)) + break; + }); + + a = nmtst_inet4_from_string("1.2.3.1"); + result = nm_platform_ip_route_get(NM_PLATFORM_GET, + AF_INET, + &a, + nmtst_get_rand_uint32() % 2 ? 0 : ifindex, + &route); + + g_assert(NMTST_NM_ERR_SUCCESS(result)); + g_assert(NMP_OBJECT_GET_TYPE(route) == NMP_OBJECT_TYPE_IP4_ROUTE); + g_assert(!NMP_OBJECT_IS_STACKINIT(route)); + g_assert(route->parent._ref_count == 1); + r = NMP_OBJECT_CAST_IP4_ROUTE(route); + g_assert(NM_FLAGS_HAS(r->r_rtm_flags, RTM_F_CLONED)); + g_assert(r->ifindex == ifindex); + g_assert(r->network == a); + g_assert(r->plen == 32); + + nmtstp_run_command_check("ip route flush dev %s", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); +} + +static void +test_ip4_zero_gateway(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + + nmtstp_run_command_check("ip route add 1.2.3.1/32 via 0.0.0.0 dev %s", DEVICE_NAME); + nmtstp_run_command_check("ip route add 1.2.3.2/32 dev %s", DEVICE_NAME); + + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 10); + if (nmtstp_ip4_route_get(NM_PLATFORM_GET, + ifindex, + nmtst_inet4_from_string("1.2.3.1"), + 32, + 0, + 0) + && nmtstp_ip4_route_get(NM_PLATFORM_GET, + ifindex, + nmtst_inet4_from_string("1.2.3.2"), + 32, + 0, + 0)) + break; + }); + + nmtstp_run_command_check("ip route flush dev %s", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); +} + +static void +test_ip4_route_options(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const int IFINDEX = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + gs_unref_ptrarray GPtrArray *routes = NULL; +#define RTS_MAX 3 + NMPlatformIP4Route rts_add[RTS_MAX] = {}; + NMPlatformIP4Route rts_cmp[RTS_MAX] = {}; + NMPlatformIP4Address addr[1] = {}; + guint i; + guint rts_n = 0; + guint addr_n = 0; + + switch (TEST_IDX) { + case 1: + rts_add[rts_n++] = ((NMPlatformIP4Route){ + .ifindex = IFINDEX, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = nmtst_inet4_from_string("172.16.1.0"), + .plen = 24, + .metric = 20, + .tos = 0x28, + .window = 10000, + .cwnd = 16, + .initcwnd = 30, + .initrwnd = 50, + .mtu = 1350, + .lock_cwnd = TRUE, + }); + break; + case 2: + addr[addr_n++] = ((NMPlatformIP4Address){ + .ifindex = IFINDEX, + .address = nmtst_inet4_from_string("172.16.1.5"), + .peer_address = nmtst_inet4_from_string("172.16.1.5"), + .plen = 24, + .lifetime = NM_PLATFORM_LIFETIME_PERMANENT, + .preferred = NM_PLATFORM_LIFETIME_PERMANENT, + .n_ifa_flags = 0, + }); + rts_add[rts_n++] = ((NMPlatformIP4Route){ + .ifindex = IFINDEX, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = nmtst_inet4_from_string("172.17.1.0"), + .gateway = nmtst_inet4_from_string("172.16.1.1"), + .plen = 24, + .metric = 20, + }); + rts_add[rts_n++] = ((NMPlatformIP4Route){ + .ifindex = IFINDEX, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = nmtst_inet4_from_string("172.19.1.0"), + .gateway = nmtst_inet4_from_string("172.18.1.1"), + .r_rtm_flags = RTNH_F_ONLINK, + .plen = 24, + .metric = 20, + }); + break; + default: + g_assert_not_reached(); + break; + } + g_assert(rts_n <= G_N_ELEMENTS(rts_add)); + g_assert(addr_n <= G_N_ELEMENTS(addr)); + + for (i = 0; i < addr_n; i++) { + const NMPlatformIP4Address *a = &addr[i]; + + g_assert(a->ifindex == IFINDEX); + g_assert(nm_platform_ip4_address_add( + NM_PLATFORM_GET, + a->ifindex, + a->address, + a->plen, + a->peer_address, + nm_platform_ip4_broadcast_address_create(a->address, a->plen), + a->lifetime, + a->preferred, + a->n_ifa_flags, + a->label)); + if (a->peer_address == a->address) + _wait_for_ipv4_addr_device_route(NM_PLATFORM_GET, 200, a->ifindex, a->address, a->plen); + } + + for (i = 0; i < rts_n; i++) + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_ip4_route_add(NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]))); + + for (i = 0; i < rts_n; i++) { + rts_cmp[i] = rts_add[i]; + nm_platform_ip_route_normalize(AF_INET, NM_PLATFORM_IP_ROUTE_CAST(&rts_cmp[i])); + } + + routes = nmtstp_ip4_route_get_all(NM_PLATFORM_GET, IFINDEX); + g_assert_cmpint(routes->len, ==, rts_n); + nmtst_platform_ip4_routes_equal_aptr((const NMPObject *const *) routes->pdata, + rts_cmp, + routes->len, + TRUE); + + for (i = 0; i < rts_n; i++) { + g_assert(nmtstp_platform_ip4_route_delete(NM_PLATFORM_GET, + IFINDEX, + rts_add[i].network, + rts_add[i].plen, + rts_add[i].metric)); + } +#undef RTS_MAX +} + +static void +test_ip6_route_get(void) +{ + int ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + const struct in6_addr *a; + int result; + nm_auto_nmpobj NMPObject *route = NULL; + const NMPlatformIP6Route *r; + + nmtstp_run_command_check("ip -6 route add fd01:abcd::/64 via fe80::99 dev %s", DEVICE_NAME); + + NMTST_WAIT_ASSERT(100, { + nmtstp_wait_for_signal(NM_PLATFORM_GET, 10); + if (nmtstp_ip6_route_get(NM_PLATFORM_GET, + ifindex, + nmtst_inet6_from_string("fd01:abcd::"), + 64, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, + NULL, + 0)) + break; + }); + + a = nmtst_inet6_from_string("fd01:abcd::42"); + result = nm_platform_ip_route_get(NM_PLATFORM_GET, + AF_INET6, + a, + nmtst_get_rand_uint32() % 2 ? 0 : ifindex, + &route); + + g_assert(NMTST_NM_ERR_SUCCESS(result)); + g_assert(NMP_OBJECT_GET_TYPE(route) == NMP_OBJECT_TYPE_IP6_ROUTE); + g_assert(!NMP_OBJECT_IS_STACKINIT(route)); + g_assert(route->parent._ref_count == 1); + r = NMP_OBJECT_CAST_IP6_ROUTE(route); + g_assert(r->ifindex == ifindex); + nmtst_assert_ip6_address(&r->network, "fd01:abcd::42"); + g_assert_cmpint(r->plen, ==, 128); + nmtst_assert_ip6_address(&r->gateway, "fe80::99"); + + nmtstp_run_command_check("ip -6 route flush dev %s", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 50); +} + +static void +test_ip6_route_options(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const int IFINDEX = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + gs_unref_ptrarray GPtrArray *routes = NULL; +#define RTS_MAX 3 + NMPlatformIP6Route rts_add[RTS_MAX] = {}; + NMPlatformIP6Route rts_cmp[RTS_MAX] = {}; + NMPlatformIP6Address addr[1] = {}; + struct in6_addr addr_in6[G_N_ELEMENTS(addr)] = {}; + guint rts_n = 0; + guint addr_n = 0; + guint i; + + switch (TEST_IDX) { + case 1: + rts_add[rts_n++] = ((NMPlatformIP6Route){ + .ifindex = IFINDEX, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = *nmtst_inet6_from_string("2001:db8:a:b:0:0:0:0"), + .plen = 64, + .gateway = in6addr_any, + .metric = 1024, + .window = 20000, + .cwnd = 8, + .initcwnd = 22, + .initrwnd = 33, + .mtu = 1300, + .lock_mtu = TRUE, + }); + break; + case 2: + addr[addr_n++] = ((NMPlatformIP6Address){ + .ifindex = IFINDEX, + .address = *nmtst_inet6_from_string("2000::2"), + .plen = 128, + .peer_address = in6addr_any, + .lifetime = NM_PLATFORM_LIFETIME_PERMANENT, + .preferred = NM_PLATFORM_LIFETIME_PERMANENT, + .n_ifa_flags = 0, + }); + rts_add[rts_n++] = ((NMPlatformIP6Route){ + .ifindex = IFINDEX, + .rt_source = NM_IP_CONFIG_SOURCE_USER, + .network = *nmtst_inet6_from_string("1010::1"), + .plen = 128, + .gateway = in6addr_any, + .metric = 256, + .pref_src = *nmtst_inet6_from_string("2000::2"), + }); + break; + case 3: + addr[addr_n++] = ((NMPlatformIP6Address){ + .ifindex = IFINDEX, + .address = *nmtst_inet6_from_string("2001:db8:8086::5"), + .plen = 128, + .peer_address = in6addr_any, + .lifetime = NM_PLATFORM_LIFETIME_PERMANENT, + .preferred = NM_PLATFORM_LIFETIME_PERMANENT, + .n_ifa_flags = 0, + }); + rts_add[rts_n++] = ((NMPlatformIP6Route){ + .ifindex = IFINDEX, + .rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER), + .network = *nmtst_inet6_from_string("2001:db8:8086::"), + .plen = 110, + .metric = 10021, + .mss = 0, + }); + rts_add[rts_n++] = ((NMPlatformIP6Route){ + .ifindex = IFINDEX, + .rt_source = nmp_utils_ip_config_source_round_trip_rtprot(NM_IP_CONFIG_SOURCE_USER), + .network = *nmtst_inet6_from_string("2001:db8:abad:c0de::"), + .plen = 64, + .gateway = *nmtst_inet6_from_string("2001:db8:8086::1"), + .metric = 21, + .mss = 0, + }); + break; + default: + g_assert_not_reached(); + } + g_assert(rts_n <= G_N_ELEMENTS(rts_add)); + g_assert(addr_n <= G_N_ELEMENTS(addr)); + + for (i = 0; i < addr_n; i++) { + g_assert(addr[i].ifindex == IFINDEX); + addr_in6[i] = addr[i].address; + g_assert(nm_platform_ip6_address_add(NM_PLATFORM_GET, + IFINDEX, + addr[i].address, + addr[i].plen, + addr[i].peer_address, + addr[i].lifetime, + addr[i].preferred, + addr[i].n_ifa_flags)); + } + _wait_for_ipv6_addr_non_tentative(NM_PLATFORM_GET, 400, IFINDEX, addr_n, addr_in6); + + for (i = 0; i < rts_n; i++) + g_assert(NMTST_NM_ERR_SUCCESS( + nm_platform_ip6_route_add(NM_PLATFORM_GET, NMP_NLM_FLAG_REPLACE, &rts_add[i]))); + + for (i = 0; i < rts_n; i++) { + rts_cmp[i] = rts_add[i]; + nm_platform_ip_route_normalize(AF_INET6, NM_PLATFORM_IP_ROUTE_CAST(&rts_cmp[i])); + } + + routes = nmtstp_ip6_route_get_all(NM_PLATFORM_GET, IFINDEX); + g_assert_cmpint(routes->len, ==, rts_n); + nmtst_platform_ip6_routes_equal_aptr((const NMPObject *const *) routes->pdata, + rts_cmp, + routes->len, + TRUE); + + for (i = 0; i < rts_n; i++) { + g_assert(nmtstp_platform_ip6_route_delete(NM_PLATFORM_GET, + IFINDEX, + rts_add[i].network, + rts_add[i].plen, + rts_add[i].metric)); + } + + for (i = 0; i < addr_n; i++) { + nmtstp_ip6_address_del(NM_PLATFORM_GET, EX, IFINDEX, rts_add[i].network, rts_add[i].plen); + } +#undef RTS_MAX +} + +/*****************************************************************************/ + +static void +test_ip(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const int IFINDEX = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + guint i, j, k; + const NMPlatformLink *l; + char ifname[IFNAMSIZ]; + char ifname2[IFNAMSIZ]; + char s1[NM_UTILS_INET_ADDRSTRLEN]; + NMPlatform * platform = NM_PLATFORM_GET; + const int EX_ = -1; + struct { + int ifindex; + } iface_data[10] = { + {0}, + }; + int order_idx[G_N_ELEMENTS(iface_data)] = {0}; + guint order_len; + guint try; + + for (i = 0; i < G_N_ELEMENTS(iface_data); i++) { + nm_sprintf_buf(ifname, "v%02u", i); + nm_sprintf_buf(ifname2, "w%02u", i); + + g_assert(!nm_platform_link_get_by_ifname(platform, ifname)); + g_assert(!nm_platform_link_get_by_ifname(platform, ifname2)); + l = nmtstp_link_veth_add(platform, EX_, ifname, ifname2); + iface_data[i].ifindex = l->ifindex; + + nmtstp_link_set_updown(platform, EX_, iface_data[i].ifindex, TRUE); + nmtstp_link_set_updown(platform, + EX_, + nmtstp_link_get(platform, -1, ifname2)->ifindex, + TRUE); + + nm_sprintf_buf(s1, "192.168.7.%d", 100 + i); + nmtstp_ip4_address_add(platform, + EX_, + iface_data[i].ifindex, + nmtst_inet4_from_string(s1), + 24, + nmtst_inet4_from_string(s1), + 3600, + 3600, + 0, + NULL); + } + + order_len = 0; + for (try = 0; try < 5 * G_N_ELEMENTS(order_idx); try++) { + NMPObject o; + NMPlatformIP4Route * r; + guint idx; + const NMDedupMultiHeadEntry *head_entry; + NMPLookup lookup; + + nmp_object_stackinit(&o, NMP_OBJECT_TYPE_IP4_ROUTE, NULL); + r = NMP_OBJECT_CAST_IP4_ROUTE(&o); + r->network = nmtst_inet4_from_string("192.168.9.0"); + r->plen = 24; + r->metric = 109; + + if (order_len == 0 + || (order_len < G_N_ELEMENTS(order_idx) && nmtst_get_rand_uint32() % 2)) { +again_find_idx: + idx = nmtst_get_rand_uint32() % G_N_ELEMENTS(iface_data); + for (i = 0; i < order_len; i++) { + if (order_idx[i] == idx) + goto again_find_idx; + } + order_idx[order_len++] = idx; + + r->ifindex = iface_data[idx].ifindex; + g_assert( + NMTST_NM_ERR_SUCCESS(nm_platform_ip4_route_add(platform, NMP_NLM_FLAG_APPEND, r))); + } else { + i = nmtst_get_rand_uint32() % order_len; + idx = order_idx[i]; + for (i++; i < order_len; i++) + order_idx[i - 1] = order_idx[i]; + order_len--; + + r->ifindex = iface_data[idx].ifindex; + g_assert(nm_platform_object_delete(platform, &o)); + } + + head_entry = + nm_platform_lookup(platform, + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_IP4_ROUTE)); + for (j = 0; j < G_N_ELEMENTS(iface_data); j++) { + gboolean has; + NMDedupMultiIter iter; + const NMPObject *o_cached; + + has = FALSE; + for (k = 0; k < order_len; k++) { + if (order_idx[k] == j) { + g_assert(!has); + has = TRUE; + } + } + + nmp_cache_iter_for_each (&iter, head_entry, &o_cached) { + const NMPlatformIP4Route *r_cached = NMP_OBJECT_CAST_IP4_ROUTE(o_cached); + + if (r_cached->ifindex != iface_data[j].ifindex || r_cached->metric != 109) + continue; + + g_assert(has); + has = FALSE; + } + g_assert(!has); + } + } + + for (i = 0; i < G_N_ELEMENTS(iface_data); i++) + g_assert(nm_platform_link_delete(platform, iface_data[i].ifindex)); + + (void) TEST_IDX; + (void) IFINDEX; +} + +/*****************************************************************************/ + +#define FRA_SUPPRESS_IFGROUP 13 +#define FRA_SUPPRESS_PREFIXLEN 14 +#define FRA_L3MDEV 19 +#define FRA_UID_RANGE 20 +#define FRA_PROTOCOL 21 +#define FRA_IP_PROTO 22 +#define FRA_SPORT_RANGE 23 +#define FRA_DPORT_RANGE 24 + +static const NMPObject * +_rule_find_by_priority(NMPlatform *platform, guint32 priority) +{ + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * o; + const NMPObject * obj = NULL; + NMPLookup lookup; + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + head_entry = nm_platform_lookup(platform, &lookup); + nmp_cache_iter_for_each (&iter, head_entry, &o) { + if (NMP_OBJECT_CAST_ROUTING_RULE(o)->priority != priority) + continue; + g_assert(!obj); + obj = o; + } + return obj; +} + +static const NMPObject * +_rule_check_kernel_support_one(NMPlatform *platform, const NMPlatformRoutingRule *rr) +{ + nm_auto_nmpobj const NMPObject *obj = NULL; + int r; + + g_assert(!_rule_find_by_priority(platform, rr->priority)); + + r = nm_platform_routing_rule_add(platform, NMP_NLM_FLAG_ADD, rr); + g_assert_cmpint(r, ==, 0); + + obj = nmp_object_ref(_rule_find_by_priority(platform, rr->priority)); + g_assert(obj); + + r = nm_platform_object_delete(platform, obj); + g_assert_cmpint(r, ==, TRUE); + + g_assert(!_rule_find_by_priority(platform, rr->priority)); + + return g_steal_pointer(&obj); +} + +static gboolean +_rule_check_kernel_support(NMPlatform *platform, int attribute) +{ + static int support[] = { + [FRA_SUPPRESS_IFGROUP] = 0, + [FRA_SUPPRESS_PREFIXLEN] = 0, + [FRA_L3MDEV] = 0, + [FRA_UID_RANGE] = 0, + [FRA_PROTOCOL] = 0, + [FRA_IP_PROTO] = 0, + [FRA_SPORT_RANGE] = 0, + [FRA_DPORT_RANGE] = 0, + }; + const guint32 PROBE_PRORITY = 12033; + gboolean sup; + int i; + + g_assert(NM_IS_PLATFORM(platform)); + + if (attribute == -1) { + for (i = 0; i < G_N_ELEMENTS(support); i++) { + if (support[i] < 0) { + /* indicate that some test was skipped. */ + return FALSE; + } + } + return TRUE; + } + + g_assert(attribute >= 0 && attribute < G_N_ELEMENTS(support)); + + if (support[attribute] != 0) + return support[attribute] >= 0; + + switch (attribute) { + case FRA_SUPPRESS_IFGROUP: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .suppress_ifgroup_inverse = ~((guint32) 1245), + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->suppress_prefixlen_inverse + == rr.suppress_ifgroup_inverse; + break; + } + case FRA_SUPPRESS_PREFIXLEN: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .suppress_prefixlen_inverse = ~((guint32) 1245), + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->suppress_prefixlen_inverse + == rr.suppress_prefixlen_inverse; + break; + } + case FRA_L3MDEV: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .l3mdev = TRUE, + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->l3mdev != 0; + break; + } + case FRA_UID_RANGE: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .uid_range = + { + .start = 0, + .end = 0, + }, + .uid_range_has = TRUE, + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->uid_range_has; + break; + } + case FRA_PROTOCOL: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .protocol = 30, + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->protocol == 30; + break; + } + case FRA_IP_PROTO: + { + nm_auto_nmpobj const NMPObject *obj = NULL; + const NMPlatformRoutingRule rr = { + .addr_family = AF_INET, + .priority = PROBE_PRORITY, + .ip_proto = 30, + }; + + obj = _rule_check_kernel_support_one(platform, &rr); + + sup = NMP_OBJECT_CAST_ROUTING_RULE(obj)->ip_proto == 30; + break; + } + case FRA_SPORT_RANGE: + case FRA_DPORT_RANGE: + /* these were added at the same time as FRA_IP_PROTO. */ + sup = _rule_check_kernel_support(platform, FRA_IP_PROTO); + break; + default: + g_assert_not_reached(); + return FALSE; + } + + support[attribute] = sup ? 1 : -1; + + _LOGD("kernel support for routing rule attribute #%d %s", + attribute, + sup ? "detected" : "not detected"); + return sup; +} + +static const NMPObject * +_platform_has_routing_rule(NMPlatform *platform, const NMPObject *obj) +{ + const NMPObject *o; + + g_assert(NM_IS_PLATFORM(platform)); + g_assert(NMP_OBJECT_IS_VALID(obj)); + g_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_ROUTING_RULE); + + o = nm_platform_lookup_obj(platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj); + if (o) + g_assert(nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(obj), + NMP_OBJECT_CAST_ROUTING_RULE(o), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) + == 0); + + return o; +} + +static guint32 +_rr_rand_choose_u32(guint32 p) +{ + /* mostly, we just return zero. We want that each rule only has few + * fields set -- having most fields at zero. */ + if ((p % 10000u) < 7500u) + return 0; + + /* give 0xFFFFFFFFu extra probability. */ + if ((p % 10000u) < 8250u) + return 0xFFFFFFFFu; + + /* choose a small number. */ + if ((p % 10000u) < 9125u) + return (~p) % 10; + + /* finally, full random number. */ + return ~p; +} + +#define _rr_rand_choose_u8(p) ((guint8) _rr_rand_choose_u32((p))) + +static const NMPObject * +_rule_create_random(NMPlatform *platform) +{ + NMPObject * obj; + NMPlatformRoutingRule *rr; + guint32 p; + int addr_size; + guint i; + char saddr[NM_UTILS_INET_ADDRSTRLEN]; + static struct { + guint32 uid; + guint32 euid; + bool initialized; + } uids; + + if (G_UNLIKELY(!uids.initialized)) { + uids.uid = getuid(); + uids.euid = geteuid(); + uids.initialized = TRUE; + } + + obj = nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE, NULL); + rr = NMP_OBJECT_CAST_ROUTING_RULE(obj); + + rr->addr_family = nmtst_rand_select(AF_INET, AF_INET6); + + addr_size = nm_utils_addr_family_to_size(rr->addr_family); + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 50) + rr->priority = 10000 + ((~p) % 20u); + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 40) + nm_sprintf_buf(rr->iifname, "t-iif-%u", (~p) % 20); + else if ((p % 1000u) < 80) + nm_sprintf_buf(rr->iifname, "%s", DEVICE_NAME); + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 40) + nm_sprintf_buf(rr->oifname, "t-oif-%d", (~p) % 20); + else if ((p % 1000u) < 80) + nm_sprintf_buf(rr->oifname, "%s", DEVICE_NAME); + + for (i = 0; i < 2; i++) { + NMIPAddr *p_addr = i ? &rr->src : &rr->dst; + guint8 * p_len = i ? &rr->src_len : &rr->dst_len; + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 100) { + /* if we set src_len/dst_len to zero, the src/dst is actually ignored. + * + * For fuzzying, still set the address. It shall have no further effect. + * */ + *p_len = (~p) % (addr_size * 8 + 1); + p = nmtst_get_rand_uint32(); + if ((p % 3u) == 0) { + if (rr->addr_family == AF_INET) + p_addr->addr4 = + nmtst_inet4_from_string(nm_sprintf_buf(saddr, "192.192.5.%u", (~p) % 256u)); + else + p_addr->addr6 = *nmtst_inet6_from_string( + nm_sprintf_buf(saddr, "1:2:3:4::f:%02x", (~p) % 256u)); + } else if ((p % 3u) == 1) + nmtst_rand_buf(NULL, p_addr, addr_size); + } + } + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 50) + rr->tun_id = 10000 + ((~p) % 20); + +again_action: + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 500) + rr->action = FR_ACT_UNSPEC; + else if ((p % 1000u) < 750) + rr->action = (~p) % 12u; + else + rr->action = (~p) % 0x100u; + + rr->priority = _rr_rand_choose_u32(nmtst_get_rand_uint32()); + + if (rr->action == FR_ACT_GOTO && rr->priority == G_MAXINT32) + goto again_action; + + p = nmtst_get_rand_uint32(); + if ((p % 10000u) < 100) + rr->goto_target = rr->priority + 1; + else + rr->goto_target = _rr_rand_choose_u32(nmtst_get_rand_uint32()); + if (rr->action == FR_ACT_GOTO && rr->goto_target <= rr->priority) + goto again_action; + + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 25) { + if (_rule_check_kernel_support(platform, FRA_L3MDEV)) { + rr->l3mdev = TRUE; + rr->table = RT_TABLE_UNSPEC; + } + } + +again_table: + if (!rr->l3mdev) { + p = nmtst_get_rand_uint32(); + if ((p % 1000u) < 700) + rr->table = RT_TABLE_UNSPEC; + else if ((p % 1000u) < 850) + rr->table = RT_TABLE_MAIN; + else + rr->table = 10000 + ((~p) % 10); + if (rr->action == FR_ACT_TO_TBL && rr->table == RT_TABLE_UNSPEC) + goto again_table; + } + + rr->fwmark = _rr_rand_choose_u32(nmtst_get_rand_uint32()); + rr->fwmask = _rr_rand_choose_u32(nmtst_get_rand_uint32()); + + rr->flow = _rr_rand_choose_u32(nmtst_get_rand_uint32()); + + if (_rule_check_kernel_support(platform, FRA_PROTOCOL)) + rr->protocol = _rr_rand_choose_u8(nmtst_get_rand_uint32()); + +#define IPTOS_TOS_MASK 0x1E + +again_tos: + rr->tos = _rr_rand_choose_u8(nmtst_get_rand_uint32()); + if (rr->addr_family == AF_INET && rr->tos & ~IPTOS_TOS_MASK) + goto again_tos; + + if (_rule_check_kernel_support(platform, FRA_IP_PROTO)) + rr->ip_proto = _rr_rand_choose_u8(nmtst_get_rand_uint32()); + + if (_rule_check_kernel_support(platform, FRA_SUPPRESS_PREFIXLEN)) + rr->suppress_prefixlen_inverse = ~_rr_rand_choose_u32(nmtst_get_rand_uint32()); + + if (_rule_check_kernel_support(platform, FRA_SUPPRESS_IFGROUP)) + rr->suppress_ifgroup_inverse = ~_rr_rand_choose_u32(nmtst_get_rand_uint32()); + + if (_rule_check_kernel_support(platform, FRA_UID_RANGE)) { + p = nmtst_get_rand_uint32(); + rr->uid_range_has = (p % 10000u) < 200; + } + +again_uid_range: + rr->uid_range.start = nmtst_rand_select(0u, uids.uid, uids.euid); + rr->uid_range.end = nmtst_rand_select(0u, uids.uid, uids.euid); + if (rr->uid_range_has) { + if (rr->uid_range.end < rr->uid_range.start) + NM_SWAP(&rr->uid_range.start, &rr->uid_range.end); + if (rr->uid_range.start == ((guint32) -1) || rr->uid_range.end == ((guint32) -1)) + goto again_uid_range; + } + + for (i = 0; i < 2; i++) { + NMFibRulePortRange *range = i ? &rr->sport_range : &rr->dport_range; + int attribute = i ? FRA_SPORT_RANGE : FRA_DPORT_RANGE; + + if (!_rule_check_kernel_support(platform, attribute)) + continue; + + p = nmtst_get_rand_uint32(); + if ((p % 10000u) < 300) { + while (range->start == 0) { + p = p ^ nmtst_get_rand_uint32(); + range->start = nmtst_rand_select(1u, 0xFFFEu, ((p) % 0xFFFEu) + 1); + range->end = + nmtst_rand_select(1u, 0xFFFEu, ((p >> 16) % 0xFFFEu) + 1, range->start); + if (range->end < range->start) + NM_SWAP(&range->start, &range->end); + } + } + } + + p = nmtst_get_rand_uint32() % 1000u; + if (p < 100) + rr->flags |= FIB_RULE_INVERT; + + return obj; +} + +static gboolean +_rule_fuzzy_equal(const NMPObject *obj, const NMPObject *obj_comp, int op_type) +{ + const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE(obj); + NMPlatformRoutingRule rr_co = *NMP_OBJECT_CAST_ROUTING_RULE(obj_comp); + + switch (op_type) { + case RTM_NEWRULE: + /* when adding rules with RTM_NEWRULE, kernel checks whether an existing + * rule already exists and may fail with EEXIST. This check has issues + * and reject legitimate rules (rh#1686075). + * + * Work around that. */ + if (rr->src_len == 0) + rr_co.src_len = 0; + if (rr->dst_len == 0) + rr_co.dst_len = 0; + if (rr->flow == 0) + rr_co.flow = 0; + if (rr->tos == 0) + rr_co.tos = 0; + rr_co.suppress_prefixlen_inverse = rr->suppress_prefixlen_inverse; + rr_co.suppress_ifgroup_inverse = rr->suppress_ifgroup_inverse; + if (!NM_FLAGS_HAS(rr->flags, FIB_RULE_INVERT)) + rr_co.flags &= ~((guint32) FIB_RULE_INVERT); + else + rr_co.flags |= ((guint32) FIB_RULE_INVERT); + break; + case RTM_DELRULE: + /* when deleting a rule with RTM_DELRULE, kernel tries to find the + * candidate to delete. It might delete the wrong rule (rh#1685816). */ + if (rr->action == FR_ACT_UNSPEC) + rr_co.action = FR_ACT_UNSPEC; + if (rr->iifname[0] == '\0') + rr_co.iifname[0] = '\0'; + if (rr->oifname[0] == '\0') + rr_co.oifname[0] = '\0'; + if (rr->src_len == 0) + rr_co.src_len = 0; + if (rr->dst_len == 0) + rr_co.dst_len = 0; + if (rr->tun_id == 0) + rr_co.tun_id = 0; + if (rr->fwmark == 0) + rr_co.fwmark = 0; + if (rr->fwmask == 0) + rr_co.fwmask = 0; + if (rr->flow == 0) + rr_co.flow = 0; + if (rr->protocol == 0) + rr_co.protocol = 0; + if (rr->table == RT_TABLE_UNSPEC) + rr_co.table = RT_TABLE_UNSPEC; + if (rr->l3mdev == 0) + rr_co.l3mdev = 0; + if (rr->tos == 0) + rr_co.tos = 0; + if (rr->ip_proto == 0) + rr_co.ip_proto = 0; + rr_co.suppress_prefixlen_inverse = rr->suppress_prefixlen_inverse; + if (rr->suppress_ifgroup_inverse == 0) + rr_co.suppress_ifgroup_inverse = 0; + if (!rr->uid_range_has) + rr_co.uid_range_has = FALSE; + if (rr->sport_range.start == 0 && rr->sport_range.end == 0) { + rr_co.sport_range.start = 0; + rr_co.sport_range.end = 0; + } + if (rr->dport_range.start == 0 && rr->dport_range.end == 0) { + rr_co.dport_range.start = 0; + rr_co.dport_range.end = 0; + } + if (!NM_FLAGS_HAS(rr->flags, FIB_RULE_INVERT)) + rr_co.flags &= ~((guint32) FIB_RULE_INVERT); + else + rr_co.flags |= ((guint32) FIB_RULE_INVERT); + break; + default: + g_assert_not_reached(); + break; + } + + return nm_platform_routing_rule_cmp(rr, &rr_co, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0; +} + +static void +test_rule(gconstpointer test_data) +{ + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const gboolean TEST_SYNC = (TEST_IDX == 4); + gs_unref_ptrarray GPtrArray *objs = NULL; + gs_unref_ptrarray GPtrArray *objs_initial = NULL; + NMPlatform * platform = NM_PLATFORM_GET; + guint i, j, n; + int r; + gboolean had_an_issue_exist = FALSE; + + nm_platform_process_events(platform); + + objs_initial = nmtstp_platform_routing_rules_get_all(platform, AF_UNSPEC); + g_assert(objs_initial); + g_assert_cmpint(objs_initial->len, ==, 5); + + nmtstp_run_command_check("ip rule add table 766"); + nm_platform_process_events(platform); + + for (i = 6; i > 0; i--) { + gs_unref_ptrarray GPtrArray *objs_extern = NULL; + const NMPObject * obj; + + objs_extern = nmtstp_platform_routing_rules_get_all(platform, AF_UNSPEC); + + g_assert(objs_extern); + g_assert_cmpint(objs_extern->len, ==, i); + + if (TEST_IDX != 1) + nmtst_rand_perm(NULL, objs_extern->pdata, NULL, sizeof(gpointer), objs_extern->len); + + obj = objs_extern->pdata[0]; + + r = nm_platform_object_delete(platform, obj); + g_assert_cmpint(r, ==, TRUE); + + g_assert(!_platform_has_routing_rule(platform, obj)); + } + + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), ==, 0); + +#define RR(...) \ + nmp_object_new(NMP_OBJECT_TYPE_ROUTING_RULE, \ + (const NMPlatformObject *) &((NMPlatformRoutingRule){__VA_ARGS__})) + + objs = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET, .priority = 10, )); + + g_ptr_array_add( + objs, + RR(.addr_family = AF_INET, .priority = 400, .action = FR_ACT_GOTO, .goto_target = 10000, )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET6, )); + + g_ptr_array_add(objs, + RR(.addr_family = AF_INET6, .action = FR_ACT_TO_TBL, .table = RT_TABLE_MAIN, )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET6, .priority = 30, )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET6, .priority = 50, .iifname = "t-iif-1", )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET6, .priority = 50, .iifname = "t-oif-1", )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET, .priority = 50, .iifname = "t-oif-2", )); + + g_ptr_array_add(objs, RR(.addr_family = AF_INET, .priority = 51, .iifname = DEVICE_NAME, )); + + if (TEST_IDX == 1) { + g_ptr_array_add(objs, RR(.addr_family = AF_INET, .table = 10000, )); + } + + if (TEST_IDX != 1) { + nmtst_rand_perm(NULL, objs->pdata, NULL, sizeof(gpointer), objs->len); + g_ptr_array_set_size(objs, nmtst_get_rand_uint32() % (objs->len + 1)); + } + + n = (TEST_IDX != 1) ? nmtst_get_rand_uint32() % 50u : 0u; + for (i = 0; i < n; i++) { + nm_auto_nmpobj const NMPObject *o = NULL; + guint try = 0; + +again: + o = _rule_create_random(platform); + for (j = 0; j < objs->len; j++) { + if (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(o), + NMP_OBJECT_CAST_ROUTING_RULE(objs->pdata[j]), + NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) + == 0) { + try++; + g_assert(try < 200); + nm_clear_pointer(&o, nmp_object_unref); + goto again; + } + } + g_ptr_array_add(objs, (gpointer) g_steal_pointer(&o)); + } + + if (TEST_IDX != 1) + nmtst_rand_perm(NULL, objs->pdata, NULL, sizeof(gpointer), objs->len); + + if (TEST_SYNC) { + gs_unref_hashtable GHashTable *unique_priorities = g_hash_table_new(NULL, NULL); + nm_auto_unref_rules_manager NMPRulesManager *rules_manager = + nmp_rules_manager_new(platform); + gs_unref_ptrarray GPtrArray *objs_sync = NULL; + gconstpointer USER_TAG_1 = &platform; + gconstpointer USER_TAG_2 = &unique_priorities; + + objs_sync = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + + /* ensure that priorities are unique. Otherwise, it confuses the test, because + * kernel may wrongly be unable to add/delete routes based on a wrong match + * (rh#1685816, rh#1685816). */ + for (i = 0; i < objs->len; i++) { + const NMPObject *obj = objs->pdata[i]; + guint32 prio = NMP_OBJECT_CAST_ROUTING_RULE(obj)->priority; + + if (!NM_IN_SET(prio, 0, 32766, 32767) + && !g_hash_table_contains(unique_priorities, GUINT_TO_POINTER(prio))) { + g_hash_table_add(unique_priorities, GUINT_TO_POINTER(prio)); + g_ptr_array_add(objs_sync, (gpointer) nmp_object_ref(obj)); + } + } + + for (i = 0; i < objs_sync->len; i++) { + nmp_rules_manager_track(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + 1, + USER_TAG_1, + NULL); + if (nmtst_get_rand_bool()) { + /* this has no effect, because a negative priority (of same absolute value) + * has lower priority than the positive priority above. */ + nmp_rules_manager_track(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + -1, + USER_TAG_2, + NULL); + } + if (nmtst_get_rand_uint32() % objs_sync->len == 0) { + nmp_rules_manager_sync(rules_manager, FALSE); + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + i + 1); + } + } + + nmp_rules_manager_sync(rules_manager, FALSE); + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + objs_sync->len); + + for (i = 0; i < objs_sync->len; i++) { + switch (nmtst_get_rand_uint32() % 3) { + case 0: + nmp_rules_manager_untrack(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + USER_TAG_1); + nmp_rules_manager_untrack(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + USER_TAG_1); + break; + case 1: + nmp_rules_manager_track(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + -1, + USER_TAG_1, + NULL); + break; + case 2: + nmp_rules_manager_track(rules_manager, + NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]), + -2, + USER_TAG_2, + NULL); + break; + } + if (nmtst_get_rand_uint32() % objs_sync->len == 0) { + nmp_rules_manager_sync(rules_manager, FALSE); + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + objs_sync->len - i - 1); + } + } + + nmp_rules_manager_sync(rules_manager, FALSE); + + } else { + for (i = 0; i < objs->len;) { + const NMPObject *obj = objs->pdata[i]; + + for (j = 0; j < objs->len; j++) + g_assert((j < i) == (!!_platform_has_routing_rule(platform, objs->pdata[j]))); + + r = nm_platform_routing_rule_add(platform, + NMP_NLM_FLAG_ADD, + NMP_OBJECT_CAST_ROUTING_RULE(obj)); + + if (r == -EEXIST) { + g_assert(!_platform_has_routing_rule(platform, obj)); + /* this should not happen, but there are bugs in kernel (rh#1686075). */ + for (j = 0; j < i; j++) { + const NMPObject *obj2 = objs->pdata[j]; + + g_assert(_platform_has_routing_rule(platform, obj2)); + + if (_rule_fuzzy_equal(obj, obj2, RTM_NEWRULE)) { + r = 0; + break; + } + } + if (r == 0) { + /* OK, the rule is shadowed by another rule, and kernel does not allow + * us to add this one (rh#1686075). Drop this from the test. */ + g_ptr_array_remove_index(objs, i); + had_an_issue_exist = TRUE; + continue; + } + } + + if (r != 0) { + NMPLookup lookup; + const NMDedupMultiHeadEntry *head_entry; + NMDedupMultiIter iter; + const NMPObject * o; + + g_print(">>> failing... errno=%d, rule=%s\n", + r, + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + + nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE); + head_entry = nm_platform_lookup(platform, &lookup); + nmp_cache_iter_for_each (&iter, head_entry, &o) { + char ch = ' '; + + if (NMP_OBJECT_CAST_ROUTING_RULE(o)->addr_family + == NMP_OBJECT_CAST_ROUTING_RULE(obj)->addr_family + && NMP_OBJECT_CAST_ROUTING_RULE(o)->priority + == NMP_OBJECT_CAST_ROUTING_RULE(obj)->priority) + ch = '*'; + g_print(">>> existing rule: %c %s\n", + ch, + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + } + + nmtstp_run_command_check("ip rule"); + nmtstp_run_command_check("ip -6 rule"); + g_assert_cmpint(r, ==, 0); + } + + g_assert(_platform_has_routing_rule(platform, obj)); + + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + i + 1); + + i++; + } + + if (TEST_IDX != 1) + nmtst_rand_perm(NULL, objs->pdata, NULL, sizeof(gpointer), objs->len); + + if (_LOGD_ENABLED()) { + nmtstp_run_command_check("ip rule"); + nmtstp_run_command_check("ip -6 rule"); + } + + for (i = 0; i < objs->len; i++) { + const NMPObject *obj = objs->pdata[i]; + const NMPObject *obj2; + + for (j = 0; j < objs->len; j++) + g_assert((j < i) == (!_platform_has_routing_rule(platform, objs->pdata[j]))); + + g_assert(_platform_has_routing_rule(platform, obj)); + + r = nm_platform_object_delete(platform, obj); + g_assert_cmpint(r, ==, TRUE); + + obj2 = _platform_has_routing_rule(platform, obj); + + if (obj2) { + guint k; + + /* When deleting a rule, kernel does a fuzzy match, ignoring for example: + * - action, if it is FR_ACT_UNSPEC + * - iifname,oifname if it is unspecified + * rh#1685816 + * + * That means, we may have deleted the wrong rule. Which one? */ + k = i; + for (j = i + 1; j < objs->len; j++) { + if (!_platform_has_routing_rule(platform, objs->pdata[j])) { + g_assert_cmpint(k, ==, i); + k = j; + } + } + g_assert_cmpint(k, >, i); + + if (!_rule_fuzzy_equal(obj, objs->pdata[k], RTM_DELRULE)) { + g_print(">>> failing...\n"); + g_print(">>> no fuzzy match between: %s\n", + nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + g_print( + ">>> and: %s\n", + nmp_object_to_string(objs->pdata[k], NMP_OBJECT_TO_STRING_ALL, NULL, 0)); + g_assert_not_reached(); + } + + objs->pdata[i] = objs->pdata[k]; + objs->pdata[k] = (gpointer) obj; + obj2 = NULL; + } + + g_assert(!obj2); + + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + objs->len - i - 1); + } + } + + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), ==, 0); + + for (i = 0; i < objs_initial->len; i++) { + const NMPObject *obj = objs_initial->pdata[i]; + + for (j = 0; j < objs_initial->len; j++) + g_assert((j < i) == (!!_platform_has_routing_rule(platform, objs_initial->pdata[j]))); + + r = nm_platform_routing_rule_add(platform, + NMP_NLM_FLAG_ADD, + NMP_OBJECT_CAST_ROUTING_RULE(obj)); + g_assert_cmpint(r, ==, 0); + } + for (j = 0; j < objs_initial->len; j++) + g_assert(_platform_has_routing_rule(platform, objs_initial->pdata[j])); + g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC), + ==, + objs_initial->len); + + /* the tests passed as good as we could (as good as we implemented workarounds for them). + * Still, with this kernel, not all features were fully tested. Mark the test as skipped. */ + if (had_an_issue_exist) + g_test_skip("adding a rule failed with EEXIST although it should not (rh#1686075)"); + else if (!_rule_check_kernel_support(platform, -1)) + g_test_skip("some kernel features were not available and skipped for the test"); +} + +/*****************************************************************************/ + +NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ +#define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, TRUE) +#define add_test_func_data(testpath, test_func, arg) \ + nmtstp_env1_add_test_func_data(testpath, test_func, arg, TRUE) + add_test_func("/route/ip4", test_ip4_route); + add_test_func("/route/ip6", test_ip6_route); + add_test_func("/route/ip4_metric0", test_ip4_route_metric0); + add_test_func_data("/route/ip4_options/1", test_ip4_route_options, GINT_TO_POINTER(1)); + if (nmtstp_is_root_test()) + add_test_func_data("/route/ip4_options/2", test_ip4_route_options, GINT_TO_POINTER(2)); + add_test_func_data("/route/ip6_options/1", test_ip6_route_options, GINT_TO_POINTER(1)); + add_test_func_data("/route/ip6_options/2", test_ip6_route_options, GINT_TO_POINTER(2)); + add_test_func_data("/route/ip6_options/3", test_ip6_route_options, GINT_TO_POINTER(3)); + + if (nmtstp_is_root_test()) { + add_test_func_data("/route/ip/1", test_ip, GINT_TO_POINTER(1)); + add_test_func("/route/ip4_route_get", test_ip4_route_get); + add_test_func("/route/ip6_route_get", test_ip6_route_get); + add_test_func("/route/ip4_zero_gateway", test_ip4_zero_gateway); + } + + if (nmtstp_is_root_test()) { + add_test_func_data("/route/rule/1", test_rule, GINT_TO_POINTER(1)); + add_test_func_data("/route/rule/2", test_rule, GINT_TO_POINTER(2)); + add_test_func_data("/route/rule/3", test_rule, GINT_TO_POINTER(3)); + add_test_func_data("/route/rule/4", test_rule, GINT_TO_POINTER(4)); + } +} diff --git a/src/core/platform/tests/test-tc.c b/src/core/platform/tests/test-tc.c new file mode 100644 index 00000000..d15c3d76 --- /dev/null +++ b/src/core/platform/tests/test-tc.c @@ -0,0 +1,222 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "src/core/nm-default-daemon.h" + +#include <linux/pkt_sched.h> + +#include "nm-test-utils-core.h" +#include "platform/nmp-object.h" +#include "nm-platform/nmp-netns.h" +#include "nm-platform/nm-platform-utils.h" +#include "test-common.h" + +static NMPObject * +qdisc_new(int ifindex, const char *kind, guint32 parent) +{ + NMPObject *obj; + + obj = nmp_object_new(NMP_OBJECT_TYPE_QDISC, NULL); + obj->qdisc = (NMPlatformQdisc){ + .ifindex = ifindex, + .kind = kind, + .parent = parent, + }; + + return obj; +} + +static GPtrArray * +qdiscs_lookup(int ifindex) +{ + NMPLookup lookup; + + return nm_platform_lookup_clone(NM_PLATFORM_GET, + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_QDISC, ifindex), + NULL, + NULL); +} + +static void +test_qdisc1(void) +{ + int ifindex; + gs_unref_ptrarray GPtrArray *known = NULL; + gs_unref_ptrarray GPtrArray *plat = NULL; + NMPObject * obj; + NMPlatformQdisc * qdisc; + + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert_cmpint(ifindex, >, 0); + + nmtstp_run_command("tc qdisc del dev %s root", DEVICE_NAME); + nmtstp_run_command_check("tc qdisc add dev %s root sfq", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 0); + + known = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + g_ptr_array_add(known, qdisc_new(ifindex, "fq_codel", TC_H_ROOT)); + g_ptr_array_add(known, qdisc_new(ifindex, "ingress", TC_H_INGRESS)); + + g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known)); + plat = qdiscs_lookup(ifindex); + g_assert(plat); + g_assert_cmpint(plat->len, ==, 2); + + obj = plat->pdata[0]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpint(qdisc->parent, ==, TC_H_ROOT); + g_assert_cmpstr(qdisc->kind, ==, "fq_codel"); + + obj = plat->pdata[1]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpint(qdisc->parent, ==, TC_H_INGRESS); + g_assert_cmpstr(qdisc->kind, ==, "ingress"); +} + +static void +test_qdisc_fq_codel(void) +{ + int ifindex; + gs_unref_ptrarray GPtrArray *known = NULL; + gs_unref_ptrarray GPtrArray *plat = NULL; + NMPObject * obj; + NMPlatformQdisc * qdisc; + + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert_cmpint(ifindex, >, 0); + + nmtstp_run_command("tc qdisc del dev %s root", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 0); + + known = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + obj = qdisc_new(ifindex, "fq_codel", TC_H_ROOT); + obj->qdisc.handle = TC_H_MAKE(0x8142 << 16, 0); + obj->qdisc.fq_codel.limit = 2048; + obj->qdisc.fq_codel.flows = 64; + obj->qdisc.fq_codel.quantum = 1000; + g_ptr_array_add(known, obj); + + g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known)); + plat = qdiscs_lookup(ifindex); + g_assert(plat); + g_assert_cmpint(plat->len, ==, 1); + + obj = plat->pdata[0]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpstr(qdisc->kind, ==, "fq_codel"); + g_assert_cmpint(qdisc->handle, ==, TC_H_MAKE(0x8142 << 16, 0)); + g_assert_cmpint(qdisc->parent, ==, TC_H_ROOT); + g_assert_cmpint(qdisc->fq_codel.limit, ==, 2048); + g_assert_cmpint(qdisc->fq_codel.flows, ==, 64); + g_assert_cmpint(qdisc->fq_codel.quantum, ==, 1000); +} + +static void +test_qdisc_sfq(void) +{ + int ifindex; + gs_unref_ptrarray GPtrArray *known = NULL; + gs_unref_ptrarray GPtrArray *plat = NULL; + NMPObject * obj; + NMPlatformQdisc * qdisc; + + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert_cmpint(ifindex, >, 0); + + nmtstp_run_command("tc qdisc del dev %s root", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 0); + + known = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + obj = qdisc_new(ifindex, "sfq", TC_H_ROOT); + obj->qdisc.handle = TC_H_MAKE(0x8143 << 16, 0); + obj->qdisc.sfq.perturb_period = 10; + obj->qdisc.sfq.quantum = 1540; + obj->qdisc.sfq.flows = 256; + g_ptr_array_add(known, obj); + + g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known)); + plat = qdiscs_lookup(ifindex); + g_assert(plat); + g_assert_cmpint(plat->len, ==, 1); + + obj = plat->pdata[0]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpstr(qdisc->kind, ==, "sfq"); + g_assert_cmpint(qdisc->handle, ==, TC_H_MAKE(0x8143 << 16, 0)); + g_assert_cmpint(qdisc->parent, ==, TC_H_ROOT); + g_assert_cmpint(qdisc->sfq.perturb_period, ==, 10); + g_assert_cmpint(qdisc->sfq.quantum, ==, 1540); + g_assert_cmpint(qdisc->sfq.flows, ==, 256); +} + +static void +test_qdisc_tbf(void) +{ + int ifindex; + gs_unref_ptrarray GPtrArray *known = NULL; + gs_unref_ptrarray GPtrArray *plat = NULL; + NMPObject * obj; + NMPlatformQdisc * qdisc; + + ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, DEVICE_NAME); + g_assert_cmpint(ifindex, >, 0); + + nmtstp_run_command("tc qdisc del dev %s root", DEVICE_NAME); + + nmtstp_wait_for_signal(NM_PLATFORM_GET, 0); + + known = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + obj = qdisc_new(ifindex, "tbf", TC_H_ROOT); + obj->qdisc.handle = TC_H_MAKE(0x8143 << 16, 0); + obj->qdisc.tbf.rate = 1000000; + obj->qdisc.tbf.burst = 2000; + obj->qdisc.tbf.limit = 3000; + g_ptr_array_add(known, obj); + + obj = qdisc_new(ifindex, "sfq", TC_H_MAKE(0x8143 << 16, 0)); + obj->qdisc.handle = TC_H_MAKE(0x8005 << 16, 0); + g_ptr_array_add(known, obj); + + g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known)); + plat = qdiscs_lookup(ifindex); + g_assert(plat); + g_assert_cmpint(plat->len, ==, 2); + + obj = plat->pdata[0]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpstr(qdisc->kind, ==, "tbf"); + g_assert_cmpint(qdisc->handle, ==, TC_H_MAKE(0x8143 << 16, 0)); + g_assert_cmpint(qdisc->parent, ==, TC_H_ROOT); + g_assert_cmpint(qdisc->tbf.rate, ==, 1000000); + g_assert_cmpint(qdisc->tbf.burst, ==, 2000); + g_assert_cmpint(qdisc->tbf.limit, ==, 3000); + + obj = plat->pdata[1]; + qdisc = NMP_OBJECT_CAST_QDISC(obj); + g_assert_cmpstr(qdisc->kind, ==, "sfq"); + g_assert_cmpint(qdisc->parent, ==, TC_H_MAKE(0x8143 << 16, 0)); + g_assert_cmpint(qdisc->handle, ==, TC_H_MAKE(0x8005 << 16, 0)); +} + +/*****************************************************************************/ + +NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP; + +void +_nmtstp_init_tests(int *argc, char ***argv) +{ + nmtst_init_with_logging(argc, argv, NULL, "ALL"); +} + +void +_nmtstp_setup_tests(void) +{ + if (nmtstp_is_root_test()) { + nmtstp_env1_add_test_func("/link/qdisc/1", test_qdisc1, TRUE); + nmtstp_env1_add_test_func("/link/qdisc/fq_codel", test_qdisc_fq_codel, TRUE); + nmtstp_env1_add_test_func("/link/qdisc/sfq", test_qdisc_sfq, TRUE); + nmtstp_env1_add_test_func("/link/qdisc/tbf", test_qdisc_tbf, TRUE); + } +} |