diff options
Diffstat (limited to 'src/platform/tests/test-common.c')
| -rw-r--r-- | src/platform/tests/test-common.c | 1222 |
1 files changed, 1194 insertions, 28 deletions
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c index b0eae63b..09a57f87 100644 --- a/src/platform/tests/test-common.c +++ b/src/platform/tests/test-common.c @@ -2,6 +2,8 @@ #include <sys/mount.h> #include <sched.h> +#include <sys/wait.h> +#include <fcntl.h> #include "test-common.h" @@ -10,9 +12,8 @@ #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)->ifname ? "'" : "", (data)->received_count - gboolean -nmtst_platform_is_root_test (void) +nmtstp_is_root_test (void) { NM_PRAGMA_WARNING_DISABLE("-Wtautological-compare") return (SETUP == nm_linux_platform_setup); @@ -20,9 +21,9 @@ nmtst_platform_is_root_test (void) } gboolean -nmtst_platform_is_sysfs_writable (void) +nmtstp_is_sysfs_writable (void) { - return !nmtst_platform_is_root_test () + return !nmtstp_is_root_test () || (access ("/sys/devices", W_OK) == 0); } @@ -34,7 +35,7 @@ add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCall 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->handler_id = g_signal_connect (NM_PLATFORM_GET, name, callback, data); data->ifindex = ifindex; data->ifname = ifname; @@ -46,7 +47,7 @@ add_signal_full (const char *name, NMPlatformSignalChangeType change_type, GCall void _accept_signal (const char *file, int line, const char *func, SignalData *data) { - debug ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal one time: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (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; @@ -55,7 +56,7 @@ _accept_signal (const char *file, int line, const char *func, SignalData *data) void _accept_signals (const char *file, int line, const char *func, SignalData *data, int min, int max) { - debug ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal [%d,%d] times: "SIGNAL_DATA_FMT, file, line, func, min, max, SIGNAL_DATA_ARG (data)); + _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; @@ -64,15 +65,28 @@ _accept_signals (const char *file, int line, const char *func, SignalData *data, void _ensure_no_signal (const char *file, int line, const char *func, SignalData *data) { - debug ("NMPlatformSignalAssert: %s:%d, %s(): Accepting signal 0 times: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (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); + g_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) { - debug ("NMPlatformSignalAssert: %s:%d, %s(): wait signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (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)); @@ -86,16 +100,16 @@ _wait_signal (const char *file, int line, const char *func, SignalData *data) void _free_signal (const char *file, int line, const char *func, SignalData *data) { - debug ("NMPlatformSignalAssert: %s:%d, %s(): free signal: "SIGNAL_DATA_FMT, file, line, func, SIGNAL_DATA_ARG (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_signal_handler_disconnect (NM_PLATFORM_GET, data->handler_id); g_free (data); } void -link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, NMPlatformReason reason, SignalData *data) +link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlatformLink *received, NMPlatformSignalChangeType change_type, SignalData *data) { GArray *links; NMPlatformLink *cached; @@ -114,12 +128,12 @@ link_callback (NMPlatform *platform, NMPObjectType obj_type, int ifindex, NMPlat return; if (data->loop) { - debug ("Quitting main loop."); + _LOGD ("Quitting main loop."); g_main_loop_quit (data->loop); } data->received_count++; - debug ("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); + _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)); @@ -253,21 +267,1176 @@ _assert_ip4_route_exists (const char *file, guint line, const char *func, gboole } } -void -run_command (const char *format, ...) +int +nmtstp_run_command (const char *format, ...) { - char *command; + int result; + gs_free char *command = NULL; va_list ap; va_start (ap, format); command = g_strdup_vprintf (format, ap); va_end (ap); - debug ("Running command: %s", command); - g_assert (!system (command)); - debug ("Command finished."); - g_free (command); + + _LOGD ("Running command: %s", command); + result = system (command); + _LOGD ("Command finished: result=%d", result); + + return result; +} + +/*****************************************************************************/ + +typedef struct { + GMainLoop *loop; + gboolean timeout; + guint id; +} WaitForSignalData; + +static void +_wait_for_signal_cb (NMPlatform *platform, + NMPObjectType obj_type, + int ifindex, + NMPlatformLink *plink, + NMPlatformSignalChangeType change_type, + gpointer user_data) +{ + WaitForSignalData *data = user_data; + + g_main_loop_quit (data->loop); +} + +static gboolean +_wait_for_signal_timeout (gpointer user_data) +{ + WaitForSignalData *data = user_data; + + data->timeout = TRUE; + data->id = 0; + g_main_loop_quit (data->loop); + return G_SOURCE_REMOVE; +} + +gboolean +nmtstp_wait_for_signal (guint timeout_ms) +{ + WaitForSignalData data = { 0 }; + + gulong id_link, id_ip4_address, id_ip6_address, id_ip4_route, id_ip6_route; + + data.loop = g_main_loop_new (NULL, FALSE); + + id_link = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data); + id_ip4_address = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data); + id_ip6_address = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data); + id_ip4_route = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data); + id_ip6_route = g_signal_connect (NM_PLATFORM_GET, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_wait_for_signal_cb), &data); + + if (timeout_ms != 0) + data.id = g_timeout_add (timeout_ms, _wait_for_signal_timeout, &data); + + g_main_loop_run (data.loop); + + g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_link)); + g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip4_address)); + g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip6_address)); + g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip4_route)); + g_assert (nm_clear_g_signal_handler (NM_PLATFORM_GET, &id_ip6_route)); + + if (nm_clear_g_source (&data.id)) + g_assert (timeout_ms != 0 && !data.timeout); + + g_clear_pointer (&data.loop, g_main_loop_unref); + + return !data.timeout; +} + +gboolean +nmtstp_wait_for_signal_until (gint64 until_ms) +{ + gint64 now; + + while (TRUE) { + now = nm_utils_get_monotonic_timestamp_ms (); + + if (until_ms < now) + return FALSE; + + if (nmtstp_wait_for_signal (MAX (1, until_ms - now))) + return TRUE; + } +} + +const NMPlatformLink * +nmtstp_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms) +{ + return nmtstp_wait_for_link_until (ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms); +} + +const NMPlatformLink * +nmtstp_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms) +{ + const NMPlatformLink *plink; + gint64 now; + + while (TRUE) { + now = nm_utils_get_monotonic_timestamp_ms (); + + plink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, ifname); + if ( plink + && (expected_link_type == NM_LINK_TYPE_NONE || plink->type == expected_link_type)) + return plink; + + if (until_ms < now) + return NULL; + + nmtstp_wait_for_signal (MAX (1, until_ms - now)); + } +} + +const NMPlatformLink * +nmtstp_assert_wait_for_link (const char *ifname, NMLinkType expected_link_type, guint timeout_ms) +{ + return nmtstp_assert_wait_for_link_until (ifname, expected_link_type, nm_utils_get_monotonic_timestamp_ms () + timeout_ms); +} + +const NMPlatformLink * +nmtstp_assert_wait_for_link_until (const char *ifname, NMLinkType expected_link_type, gint64 until_ms) +{ + const NMPlatformLink *plink; + + plink = nmtstp_wait_for_link_until (ifname, expected_link_type, until_ms); + g_assert (plink); + return plink; +} + +int +nmtstp_run_command_check_external_global (void) +{ + if (!nmtstp_is_root_test ()) + return FALSE; + switch (nmtst_get_rand_int () % 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_int () % 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_s (); + g_assert (now > 0); + + g_assert (expected_preferred <= expected_lifetime); + + if ( expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT + && expected_lifetime == 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 ( adr - offset <= lft - CHECK_LIFETIME_MAX_DIFF + || adr - offset >= 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_s (); + g_assert (now > 0); + + g_assert (expected_preferred <= expected_lifetime); + + if ( expected_lifetime == NM_PLATFORM_LIFETIME_PERMANENT + && expected_lifetime == 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, <=, lft); + g_assert_cmpint (offset, <=, adr); + 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 (gboolean external_command, + gboolean is_v4, + int ifindex, + const NMIPAddr *address, + int plen, + const NMIPAddr *peer_address, + guint32 lifetime, + guint32 preferred, + const char *label, + guint flags) +{ + gint64 end_time; + + external_command = nmtstp_run_command_check_external (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], b2[NM_UTILS_INET_ADDRSTRLEN]; + + ifname = nm_platform_link_get_name (NM_PLATFORM_GET, 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[100]; + + g_assert (flags == 0); + + if ( peer_address->addr4 != address->addr4 + || nmtst_get_rand_int () % 2) { + /* If the peer is the same as the local address, we can omit it. The result should be identical */ + g_snprintf (s_peer, sizeof (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) { + g_assert (flags == 0); + success = nm_platform_ip4_address_add (NM_PLATFORM_GET, + ifindex, + address->addr4, + plen, + peer_address->addr4, + lifetime, + preferred, + label); + } else { + g_assert (label == NULL); + success = nm_platform_ip6_address_add (NM_PLATFORM_GET, + 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_ms () + 250; + do { + + if (external_command) + nm_platform_process_events (NM_PLATFORM_GET); + + /* 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 (NM_PLATFORM_GET, 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 (NM_PLATFORM_GET, ifindex, address->addr6, plen); + 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); + + g_assert (nmtstp_wait_for_signal_until (end_time)); + } while (TRUE); +} + +#define _assert_pllink(success, pllink, name, type) \ + G_STMT_START { \ + const NMPlatformLink *_pllink = (pllink); \ + \ + if ((success)) { \ + g_assert (_pllink); \ + g_assert (_pllink == nmtstp_link_get_typed (_pllink->ifindex, (name), (type))); \ + } else { \ + g_assert (!_pllink); \ + g_assert (!nmtstp_link_get (0, (name))); \ + } \ + } G_STMT_END + +const NMPlatformLink * +nmtstp_link_dummy_add (gboolean external_command, + const char *name) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + + g_assert (nm_utils_iface_valid_name (name)); + + external_command = nmtstp_run_command_check_external (external_command); + + if (external_command) { + success = !nmtstp_run_command ("ip link add %s type dummy", + name); + if (success) + pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_DUMMY, 100); + } else + success = nm_platform_link_dummy_add (NM_PLATFORM_GET, name, &pllink) == NM_PLATFORM_ERROR_SUCCESS; + + g_assert (success); + _assert_pllink (success, pllink, name, NM_LINK_TYPE_DUMMY); + return pllink; +} + +const NMPlatformLink * +nmtstp_link_gre_add (gboolean external_command, + const char *name, + const NMPlatformLnkGre *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char buffer[INET_ADDRSTRLEN]; + + g_assert (nm_utils_iface_valid_name (name)); + + external_command = nmtstp_run_command_check_external (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 (NM_PLATFORM_GET, lnk->parent_ifindex)); + + success = !nmtstp_run_command ("ip tunnel add %s mode gre %s local %s remote %s ttl %u tos %02x %s", + name, + dev ? dev : "", + nm_utils_inet4_ntop (lnk->local, NULL), + nm_utils_inet4_ntop (lnk->remote, buffer), + lnk->ttl, + lnk->tos, + lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc"); + if (success) + pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_GRE, 100); + } else + success = nm_platform_link_gre_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS; + + _assert_pllink (success, pllink, name, NM_LINK_TYPE_GRE); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_ip6tnl_add (gboolean external_command, + const char *name, + const NMPlatformLnkIp6Tnl *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char buffer[INET6_ADDRSTRLEN]; + + g_assert (nm_utils_iface_valid_name (name)); + + external_command = nmtstp_run_command_check_external (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 (NM_PLATFORM_GET, lnk->parent_ifindex)); + + switch (lnk->proto) { + case IPPROTO_IPIP: + mode = "ipip6"; + break; + case IPPROTO_IPV6: + mode = "ip6ip6"; + break; + default: + g_assert_not_reached (); + } + + success = !nmtstp_run_command ("ip -6 tunnel add %s mode %s %s local %s remote %s ttl %u tclass %02x encaplimit %u flowlabel %x", + name, + mode, + dev, + nm_utils_inet6_ntop (&lnk->local, NULL), + nm_utils_inet6_ntop (&lnk->remote, buffer), + lnk->ttl, + lnk->tclass, + lnk->encap_limit, + lnk->flow_label); + if (success) + pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_IP6TNL, 100); + } else + success = nm_platform_link_ip6tnl_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS; + + _assert_pllink (success, pllink, name, NM_LINK_TYPE_IP6TNL); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_ipip_add (gboolean external_command, + const char *name, + const NMPlatformLnkIpIp *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char buffer[INET_ADDRSTRLEN]; + + g_assert (nm_utils_iface_valid_name (name)); + + external_command = nmtstp_run_command_check_external (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 (NM_PLATFORM_GET, 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, NULL), + nm_utils_inet4_ntop (lnk->remote, buffer), + lnk->ttl, + lnk->tos, + lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc"); + if (success) + pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_IPIP, 100); + } else + success = nm_platform_link_ipip_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS; + + _assert_pllink (success, pllink, name, NM_LINK_TYPE_IPIP); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_macvlan_add (gboolean external_command, + const char *name, + int parent, + const NMPlatformLnkMacvlan *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + NMLinkType link_type; + + g_assert (nm_utils_iface_valid_name (name)); + + external_command = nmtstp_run_command_check_external (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 (NM_PLATFORM_GET, 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 (name, link_type, 100); + } else + success = nm_platform_link_macvlan_add (NM_PLATFORM_GET, name, parent, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS; + + _assert_pllink (success, pllink, name, link_type); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_sit_add (gboolean external_command, + const char *name, + const NMPlatformLnkSit *lnk) +{ + const NMPlatformLink *pllink = NULL; + gboolean success; + char buffer[INET_ADDRSTRLEN]; + + g_assert (nm_utils_iface_valid_name (name)); + + external_command = nmtstp_run_command_check_external (external_command); + + if (external_command) { + const char *dev = ""; + + if (lnk->parent_ifindex) { + const char *parent_name; + + parent_name = nm_platform_link_get_name (NM_PLATFORM_GET, 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, NULL), + nm_utils_inet4_ntop (lnk->remote, buffer), + lnk->ttl, + lnk->tos, + lnk->path_mtu_discovery ? "pmtudisc" : "nopmtudisc"); + if (success) + pllink = nmtstp_assert_wait_for_link (name, NM_LINK_TYPE_SIT, 100); + } else + success = nm_platform_link_sit_add (NM_PLATFORM_GET, name, lnk, &pllink) == NM_PLATFORM_ERROR_SUCCESS; + + _assert_pllink (success, pllink, name, NM_LINK_TYPE_SIT); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_vxlan_add (gboolean external_command, + const char *name, + const NMPlatformLnkVxlan *lnk) +{ + const NMPlatformLink *pllink = NULL; + NMPlatformError plerr; + int err; + + g_assert (nm_utils_iface_valid_name (name)); + + external_command = nmtstp_run_command_check_external (external_command); + + if (external_command) { + gs_free char *dev = NULL; + gs_free char *local = NULL, *remote = NULL; + + if (lnk->parent_ifindex) + dev = g_strdup_printf ("dev %s", nm_platform_link_get_name (NM_PLATFORM_GET, lnk->parent_ifindex)); + + if (lnk->local) + local = g_strdup_printf ("%s", nm_utils_inet4_ntop (lnk->local, NULL)); + else if (memcmp (&lnk->local6, &in6addr_any, sizeof (in6addr_any))) + local = g_strdup_printf ("%s", nm_utils_inet6_ntop (&lnk->local6, NULL)); + + if (lnk->group) + remote = g_strdup_printf ("%s", nm_utils_inet4_ntop (lnk->group, NULL)); + else if (memcmp (&lnk->group6, &in6addr_any, sizeof (in6addr_any))) + remote = g_strdup_printf ("%s", nm_utils_inet6_ntop (&lnk->group6, NULL)); + + 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 ? dev : "", + local, + remote, + 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 (name, NM_LINK_TYPE_VXLAN, 100); + else + _LOGI ("Adding vxlan device via iproute2 failed. Assume iproute2 is not up to the task."); + } + if (!pllink) { + plerr = nm_platform_link_vxlan_add (NM_PLATFORM_GET, name, lnk, &pllink); + g_assert_cmpint (plerr, ==, NM_PLATFORM_ERROR_SUCCESS); + g_assert (pllink); + } + + g_assert_cmpint (pllink->type, ==, NM_LINK_TYPE_VXLAN); + g_assert_cmpstr (pllink->name, ==, name); + return pllink; +} + +void +nmtstp_ip4_address_add (gboolean external_command, + int ifindex, + in_addr_t address, + int plen, + in_addr_t peer_address, + guint32 lifetime, + guint32 preferred, + const char *label) +{ + _ip_address_add (external_command, + TRUE, + ifindex, + (NMIPAddr *) &address, + plen, + (NMIPAddr *) &peer_address, + lifetime, + preferred, + label, + 0); +} + +void +nmtstp_ip6_address_add (gboolean external_command, + int ifindex, + struct in6_addr address, + int plen, + struct in6_addr peer_address, + guint32 lifetime, + guint32 preferred, + guint flags) +{ + _ip_address_add (external_command, + FALSE, + ifindex, + (NMIPAddr *) &address, + plen, + (NMIPAddr *) &peer_address, + lifetime, + preferred, + NULL, + flags); +} + +static void +_ip_address_del (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); + + if (external_command) { + const char *ifname; + char b1[NM_UTILS_INET_ADDRSTRLEN], b2[NM_UTILS_INET_ADDRSTRLEN]; + int success; + gboolean had_address; + + ifname = nm_platform_link_get_name (NM_PLATFORM_GET, 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 (NM_PLATFORM_GET, ifindex, address->addr4, plen, peer_address->addr4); + else + had_address = !!nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, address->addr6, plen); + + 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 (NM_PLATFORM_GET, + ifindex, + address->addr4, + plen, + peer_address->addr4); + } else { + g_assert (!peer_address); + success = nm_platform_ip6_address_delete (NM_PLATFORM_GET, + ifindex, + address->addr6, + plen); + } + g_assert (success); + } + + /* Let's wait until we get the result */ + end_time = nm_utils_get_monotonic_timestamp_ms () + 250; + do { + if (external_command) + nm_platform_process_events (NM_PLATFORM_GET); + + /* let's wait until we see the address as we added it. */ + if (is_v4) { + const NMPlatformIP4Address *a; + + a = nm_platform_ip4_address_get (NM_PLATFORM_GET, ifindex, address->addr4, plen, peer_address->addr4); + if (!a) + break; + } else { + const NMPlatformIP6Address *a; + + a = nm_platform_ip6_address_get (NM_PLATFORM_GET, ifindex, address->addr6, plen); + if (!a) + break; + } + + /* for internal command, we expect not to reach this line.*/ + g_assert (external_command); + + g_assert (nmtstp_wait_for_signal_until (end_time)); + } while (TRUE); +} + +void +nmtstp_ip4_address_del (gboolean external_command, + int ifindex, + in_addr_t address, + int plen, + in_addr_t peer_address) +{ + _ip_address_del (external_command, + TRUE, + ifindex, + (NMIPAddr *) &address, + plen, + (NMIPAddr *) &peer_address); +} + +void +nmtstp_ip6_address_del (gboolean external_command, + int ifindex, + struct in6_addr address, + int plen) +{ + _ip_address_del (external_command, + FALSE, + ifindex, + (NMIPAddr *) &address, + plen, + NULL); +} + +const NMPlatformLink * +nmtstp_link_get_typed (int ifindex, + const char *name, + NMLinkType link_type) +{ + const NMPlatformLink *pllink = NULL; + + if (ifindex > 0) { + pllink = nm_platform_link_get (NM_PLATFORM_GET, 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 (NM_PLATFORM_GET, name)); + } + } else { + g_assert (name); + + pllink = nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name); + + if (pllink) + g_assert_cmpstr (name, ==, pllink->name); + } + + g_assert (!name || nm_utils_iface_valid_name (name)); + + if (pllink && link_type != NM_LINK_TYPE_NONE) + g_assert_cmpint (pllink->type, ==, link_type); + + return pllink; +} + +const NMPlatformLink * +nmtstp_link_get (int ifindex, + const char *name) +{ + return nmtstp_link_get_typed (ifindex, name, NM_LINK_TYPE_NONE); +} + +void +nmtstp_link_del (gboolean external_command, + int ifindex, + const char *name) +{ + gint64 end_time; + const NMPlatformLink *pllink; + gboolean success; + gs_free char *name_copy = NULL; + + pllink = nmtstp_link_get (ifindex, name); + + g_assert (pllink); + + name = name_copy = g_strdup (pllink->name); + ifindex = pllink->ifindex; + + external_command = nmtstp_run_command_check_external (external_command); + + if (external_command) { + nmtstp_run_command_check ("ip link delete %s", name); + } else { + success = nm_platform_link_delete (NM_PLATFORM_GET, ifindex); + g_assert (success); + } + + /* Let's wait until we get the result */ + end_time = nm_utils_get_monotonic_timestamp_ms () + 250; + do { + if (external_command) + nm_platform_process_events (NM_PLATFORM_GET); + + if (!nm_platform_link_get (NM_PLATFORM_GET, ifindex)) { + g_assert (!nm_platform_link_get_by_ifname (NM_PLATFORM_GET, name)); + break; + } + + /* for internal command, we expect not to reach this line.*/ + g_assert (external_command); + + g_assert (nmtstp_wait_for_signal_until (end_time)); + } while (TRUE); } +void +nmtstp_link_set_updown (gboolean external_command, + int ifindex, + gboolean up) +{ + const NMPlatformLink *plink; + gint64 end_time; + + external_command = nmtstp_run_command_check_external (external_command); + + if (external_command) { + const char *ifname; + + ifname = nm_platform_link_get_name (NM_PLATFORM_GET, 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 (NM_PLATFORM_GET, ifindex, NULL)); + else + g_assert (nm_platform_link_set_down (NM_PLATFORM_GET, ifindex)); + } + + /* Let's wait until we get the result */ + end_time = nm_utils_get_monotonic_timestamp_ms () + 250; + do { + if (external_command) + nm_platform_process_events (NM_PLATFORM_GET); + + /* let's wait until we see the address as we added it. */ + plink = nm_platform_link_get (NM_PLATFORM_GET, ifindex); + g_assert (plink); + + if (NM_FLAGS_HAS (plink->flags, IFF_UP) == !!up) + break; + + /* for internal command, we expect not to reach this line.*/ + g_assert (external_command); + + g_assert (nmtstp_wait_for_signal_until (end_time)); + } while (TRUE); +} + +/*****************************************************************************/ + +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 = pipe (pipefd_c2p); + if (e != 0) { + errsv = errno; + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "pipe() failed with %d (%s)", errsv, strerror (errsv)); + return FALSE; + } + + e = pipe (pipefd_p2c); + if (e != 0) { + errsv = errno; + g_set_error (error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, + "pipe() failed with %d (%s)", errsv, strerror (errsv)); + close (pipefd_c2p[0]); + 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, strerror (errsv)); + close (pipefd_c2p[0]); + close (pipefd_c2p[1]); + close (pipefd_p2c[0]); + close (pipefd_p2c[1]); + return FALSE; + } + + if (pid == 0) { + char read_buf[1]; + + close (pipefd_c2p[0]); /* close read-end */ + 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; + } + 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); + } + close (pipefd_p2c[0]); + _exit (0); + } + + close (pipefd_c2p[1]); /* close write-end */ + 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); + + 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, strerror (errsv)); + } + 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); + + 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", (long unsigned) pid, ns_name); + + return open(p, O_RDONLY); +} + +/*****************************************************************************/ + NMTST_DEFINE(); static gboolean @@ -318,7 +1487,7 @@ main (int argc, char **argv) init_tests (&argc, &argv); - if ( nmtst_platform_is_root_test () + if ( nmtstp_is_root_test () && (geteuid () != 0 || getegid () != 0)) { if ( g_getenv ("NMTST_FORCE_REAL_ROOT") || !unshare_user ()) { @@ -335,7 +1504,7 @@ main (int argc, char **argv) } } - if (nmtst_platform_is_root_test () && !g_getenv ("NMTST_NO_UNSHARE")) { + if (nmtstp_is_root_test () && !g_getenv ("NMTST_NO_UNSHARE")) { int errsv; if (unshare (CLONE_NEWNET | CLONE_NEWNS) != 0) { @@ -345,10 +1514,7 @@ main (int argc, char **argv) /* Mount our /sys instance, so that gudev sees only our devices. * Needs to be read-only, because we don't run udev. */ - if (mount (NULL, "/sys", "sysfs", MS_SLAVE, NULL) != 0) { - errsv = errno; - g_error ("mount(\"/\", MS_SLAVE) failed with %s (%d)", strerror (errsv), errsv); - } + 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)", strerror (errsv), errsv); @@ -383,6 +1549,6 @@ main (int argc, char **argv) nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, DEVICE_NAME)); - g_object_unref (nm_platform_get ()); + g_object_unref (NM_PLATFORM_GET); return result; } |