/* 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 #include #include #include #include #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 /* Due to rounding errors with clock_t_to_jiffies()/jiffies_to_clock_t(), kernel cannot * store all requested values. That means, when we try to configure a bridge with * the @requested values, the actually configured settings are slightly off, as * @kernel. * * This function takes @requested and returns it as @dst output. All fields * that might be mangled by kernel (according to @kernel) are adjusted. The * result is almost identical to @requested, but some fields might be adjusted * to their @kernel value. */ const NMPlatformLnkBridge * nmtstp_link_bridge_normalize_jiffies_time(const NMPlatformLnkBridge *requested, const NMPlatformLnkBridge *kernel, NMPlatformLnkBridge * dst) { if (dst != requested) *dst = *requested; #define _normalize_field(dst, kernel, field) \ G_STMT_START \ { \ (dst)->field = nmtstp_normalize_jiffies_time((dst)->field, (kernel)->field); \ } \ G_STMT_END _normalize_field(dst, kernel, forward_delay); _normalize_field(dst, kernel, hello_time); _normalize_field(dst, kernel, max_age); _normalize_field(dst, kernel, ageing_time); _normalize_field(dst, kernel, mcast_last_member_interval); _normalize_field(dst, kernel, mcast_membership_interval); _normalize_field(dst, kernel, mcast_querier_interval); _normalize_field(dst, kernel, mcast_query_interval); _normalize_field(dst, kernel, mcast_query_response_interval); _normalize_field(dst, kernel, mcast_startup_query_interval); return dst; } 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; NMPlatformLnkBridge lnk_normalized; 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); lnk = nmtstp_link_bridge_normalize_jiffies_time(lnk, ll, &lnk_normalized); 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_change_flags(platform, ifindex, IFF_UP, TRUE) >= 0); else g_assert(nm_platform_link_change_flags(platform, ifindex, IFF_UP, FALSE) >= 0); } /* 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; if (errsv == EPERM) { #ifdef REQUIRE_ROOT_TESTS g_print("Fail test: unshare(CLONE_NEWNET|CLONE_NEWNS) failed with %s (%d)\n", nm_strerror_native(errsv), errsv); return EXIT_FAILURE; #else g_print("Skipping test: unshare(CLONE_NEWNET|CLONE_NEWNS) failed with %s (%d)\n", nm_strerror_native(errsv), errsv); return g_test_run(); #endif } g_error("Fail test: 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); }