platform/tests: add nmtstp_ensure_module() helper

This will make sure that the IP tunnel module is loaded. It does so by
creating (and deleting) a tunnel interface.

That is important, because those modules will create additional interfaces
that show up in `ip link` (like "gre0"), and those interfaces can interfere
with the tests.

Also add nmtstp_link_is_iptunnel_special() to detect whether an
interface is one of those special interfaces.
This commit is contained in:
Thomas Haller 2023-01-25 15:41:22 +01:00
parent 4966f9d784
commit 451cedf2bf
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
2 changed files with 270 additions and 0 deletions

View file

@ -15,6 +15,7 @@
#include <linux/rtnetlink.h>
#include "n-acd/src/n-acd.h"
#include "libnm-platform/nm-platform-utils.h"
#define SIGNAL_DATA_FMT "'%s-%s' ifindex %d%s%s%s (%d times received)"
#define SIGNAL_DATA_ARG(data) \
@ -33,6 +34,52 @@ int NMTSTP_ENV1_EX = -1;
/*****************************************************************************/
typedef struct {
const char *module_name;
NMLinkType iftype;
/* These modules create additional interfaces, like
* "gre0" for "ip_gre" module.
*
* - actually some modules create multiple interfaces, like "ip_gre"
* creating "gre0", "gretap0", "erspan0".
* - if already an interface with such name exist, kernel would create
* names like "gre1" or "gretap1". We don't care for that, because
* we run in our own namespace and we control which interfaces are there.
* We wouldn't create an interface with such a conflicting name.
*
* Anyway. This is the name of *one* of the interfaces that the module would
* create. */
const char *ifname;
/* This only gets set, if iftype is NM_LINK_TYPE_UNKNOWN. It corresponds to
* NMPlatformLink.kind. */
const char *ifkind;
} IPTunnelModInfo;
#define INF(_module_name, _iftype, _ifname, ...) \
{ \
.module_name = ""_module_name, .iftype = _iftype, .ifname = ""_ifname, __VA_ARGS__ \
}
static const IPTunnelModInfo ip_tunnel_mod_infos[] = {
INF("ip_gre", NM_LINK_TYPE_GRE, "gre0"),
INF("ip_gre", NM_LINK_TYPE_GRETAP, "gretap0"),
INF("ip_gre", NM_LINK_TYPE_UNKNOWN, "erspan0", .ifkind = "erspan"),
INF("ipip", NM_LINK_TYPE_IPIP, "tunl0"),
INF("ip6_tunnel", NM_LINK_TYPE_IP6TNL, "ip6tnl0"),
INF("ip6_gre", NM_LINK_TYPE_IP6GRE, "ip6gre0"),
INF("sit", NM_LINK_TYPE_SIT, "sit0"),
INF("ip_vti", NM_LINK_TYPE_VTI, "ip_vti0"),
INF("ip6_vti", NM_LINK_TYPE_VTI6, "ip6_vti0"),
};
#undef INF
/*****************************************************************************/
void
nmtstp_setup_platform(void)
{
@ -912,6 +959,225 @@ _assert_platform_compare_arr(NMPObjectType obj_type,
}
}
/*****************************************************************************/
gboolean
nmtstp_link_is_iptunnel_special(const NMPlatformLink *link)
{
int i;
g_assert(link);
/* These interfaces are autogenerated when loading the ip tunnel
* modules. For example, loading "ip_gre" results in interfaces
* "gre0", "gretap0", "erspan0".
*
* Actually, if the interface names are already taken ("gre0" already
* exists), it will create "gre1" and so on. We don't care about that,
* because in our test's netns that is not happening. */
for (i = 0; i < (int) G_N_ELEMENTS(ip_tunnel_mod_infos); i++) {
const IPTunnelModInfo *module_info = &ip_tunnel_mod_infos[i];
if (module_info->iftype != link->type)
continue;
if (!nm_streq(module_info->ifname, link->name))
continue;
if (module_info->ifkind && !nm_streq(module_info->ifkind, link->kind))
continue;
return TRUE;
}
return FALSE;
}
/*****************************************************************************/
gboolean
nmtstp_ensure_module(const char *module_name)
{
/* using iproute2 seems to fail sometimes? Force use of platform code. */
const int EX = 0;
gs_free char *test_ifname = NULL;
const NMPlatformLink *link;
static int module_state[G_N_ELEMENTS(ip_tunnel_mod_infos)] = {0};
int i_module_info;
const IPTunnelModInfo *module_info = NULL;
int i;
int ifindex;
gboolean result;
if (!module_name) {
result = TRUE;
for (i = 0; i < (int) G_N_ELEMENTS(ip_tunnel_mod_infos); i++) {
if (!nmtstp_ensure_module(ip_tunnel_mod_infos[i].module_name))
result = FALSE;
}
return result;
}
for (i_module_info = 0; i_module_info < (int) G_N_ELEMENTS(ip_tunnel_mod_infos);
i_module_info++) {
if (nm_streq(module_name, ip_tunnel_mod_infos[i_module_info].module_name)) {
module_info = &ip_tunnel_mod_infos[i_module_info];
break;
}
}
if (!module_info)
g_error("%s:%d: Module name \"%s\" not implemented!", __FILE__, __LINE__, module_name);
test_ifname = g_strdup_printf("nm-mod-%s", module_info->ifname);
g_assert(nm_utils_ifname_valid_kernel(test_ifname, NULL));
again:
i = g_atomic_int_get(&module_state[i_module_info]);
if (i != 0)
return i > 0;
/* When tunnel modules get loaded, then interfaces like "gre0", "gretap0"
* and "erspan0" (for "ip_gre" module) appear. For other modules, the interfaces
* are named differently. Of course, unless those interface name are already taken,
* in which case it will create "gre1", etc). So ugly.
*
* Anyway. as we run unit tests in parallel (`make check -j`), another
* test might just load the module just now, which results in the creation of
* those interfaces in our current namespace. That can break the test.
*/
link = nmtstp_link_get_typed(NM_PLATFORM_GET, 0, test_ifname, module_info->iftype);
g_assert(!link);
link = nmtstp_link_get_typed(NM_PLATFORM_GET, 0, module_info->ifname, module_info->iftype);
if (link) {
g_assert(nmtstp_link_is_iptunnel_special(link));
/* An interface with this name exists. While technically this could not be the interface
* generated by the kernel module, in our test netns we can assume that it is. This is
* good enough. */
result = TRUE;
goto out;
}
/* Try to load the module. It probably won't work, because we don't have permissions.
* Ignore any failure. */
nmp_utils_modprobe(NULL, TRUE, module_info->module_name, NULL);
if (nm_streq(module_name, "ip_gre")) {
link = nmtstp_link_gre_add(NULL,
EX,
test_ifname,
&((const NMPlatformLnkGre){
.local = nmtst_inet4_from_string("192.168.233.204"),
.remote = nmtst_inet4_from_string("172.168.10.25"),
.parent_ifindex = 0,
.ttl = 174,
.tos = 37,
.path_mtu_discovery = TRUE,
}));
} else if (nm_streq(module_name, "ipip")) {
link = nmtstp_link_ipip_add(NULL,
EX,
test_ifname,
&((const NMPlatformLnkIpIp){
.local = nmtst_inet4_from_string("1.2.3.4"),
.remote = nmtst_inet4_from_string("5.6.7.8"),
.parent_ifindex = 0,
.tos = 32,
.path_mtu_discovery = FALSE,
}));
} else if (nm_streq(module_name, "ip6_tunnel")) {
link = nmtstp_link_ip6tnl_add(NULL,
EX,
test_ifname,
&((const NMPlatformLnkIp6Tnl){
.local = nmtst_inet6_from_string("fd01::15"),
.remote = nmtst_inet6_from_string("fd01::16"),
.tclass = 20,
.encap_limit = 6,
.flow_label = 1337,
.proto = IPPROTO_IPV6,
}));
} else if (nm_streq(module_name, "ip6_gre")) {
link = nmtstp_link_ip6gre_add(NULL,
EX,
test_ifname,
&((const NMPlatformLnkIp6Tnl){
.local = nmtst_inet6_from_string("fd01::42"),
.remote = nmtst_inet6_from_string("fd01::aaaa"),
.tclass = 21,
.flow_label = 1338,
.is_gre = TRUE,
}));
} else if (nm_streq(module_name, "sit")) {
link = nmtstp_link_sit_add(NULL,
EX,
test_ifname,
&((const NMPlatformLnkSit){
.local = nmtst_inet4_from_string("192.168.200.1"),
.remote = nmtst_inet4_from_string("172.25.100.14"),
.ttl = 0,
.tos = 31,
.path_mtu_discovery = FALSE,
}));
} else if (nm_streq(module_name, "ip_vti")) {
link = nmtstp_link_vti_add(NULL,
EX,
test_ifname,
&((const NMPlatformLnkVti){
.local = nmtst_inet4_from_string("192.168.212.204"),
.remote = nmtst_inet4_from_string("172.168.11.25"),
.ikey = 12,
.okey = 13,
}));
} else if (nm_streq(module_name, "ip6_vti")) {
link = nmtstp_link_vti6_add(NULL,
EX,
test_ifname,
&((const NMPlatformLnkVti6){
.local = nmtst_inet6_from_string("fd01::1"),
.remote = nmtst_inet6_from_string("fd02::2"),
.ikey = 13,
.okey = 14,
}));
} else
g_error("%s:%d: Module name \"%s\" not implemented!", __FILE__, __LINE__, module_name);
if (!link) {
/* We might be unable to add the interface, if the kernel module does not exist.
* Be graceful about that. */
ifindex = 0;
g_assert(!nmtstp_link_get_typed(NM_PLATFORM_GET, 0, test_ifname, module_info->iftype));
g_assert(
!nmtstp_link_get_typed(NM_PLATFORM_GET, 0, module_info->ifname, module_info->iftype));
} else {
ifindex = link->ifindex;
g_assert(link);
g_assert(
link
== nmtstp_link_get_typed(NM_PLATFORM_GET, ifindex, test_ifname, module_info->iftype));
nmtstp_link_delete(NULL, -1, link->ifindex, test_ifname, TRUE);
link = nmtstp_link_get_typed(NM_PLATFORM_GET, 0, module_info->ifname, module_info->iftype);
g_assert(nmtstp_link_is_iptunnel_special(link));
}
result = ifindex > 0 ? 1 : -1;
out:
if (!g_atomic_int_compare_and_exchange(&module_state[i_module_info], 0, result))
goto again;
if (!result) {
/* The function aims to be graceful about missing kernel modules. */
/* g_error("Failure to ensure module \"%s\"", module_name); */
}
return result;
}
void
nmtstp_assert_platform(NMPlatform *platform, guint32 obj_type_flags)
{

View file

@ -530,6 +530,10 @@ void nmtstp_link_delete(NMPlatform *platform,
const char *name,
gboolean require_exist);
gboolean nmtstp_link_is_iptunnel_special(const NMPlatformLink *link);
gboolean nmtstp_ensure_module(const char *module_name);
/*****************************************************************************/
#define nmtst_object_new_mptcp_addr(...) \