diff options
Diffstat (limited to 'src/core/platform/tests/test-common.c')
| -rw-r--r-- | src/core/platform/tests/test-common.c | 2731 |
1 files changed, 2731 insertions, 0 deletions
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); +} |