summary refs log tree commit diff
path: root/src/platform/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/tests')
-rw-r--r--src/platform/tests/monitor.c2
-rw-r--r--src/platform/tests/test-common.c2
-rw-r--r--src/platform/tests/test-common.h52
-rw-r--r--src/platform/tests/test-link.c36
-rw-r--r--src/platform/tests/test-nmp-object.c4
-rw-r--r--src/platform/tests/test-route.c864
6 files changed, 952 insertions, 8 deletions
diff --git a/src/platform/tests/monitor.c b/src/platform/tests/monitor.c
index f0e3e6cf..fff5967b 100644
--- a/src/platform/tests/monitor.c
+++ b/src/platform/tests/monitor.c
@@ -77,8 +77,6 @@ main (int argc, char **argv)
 
 	nm_linux_platform_setup ();
 
-	nm_platform_check_kernel_support (NM_PLATFORM_GET, ~((NMPlatformKernelSupportFlags) 0));
-
 	if (global_opt.persist)
 		g_main_loop_run (loop);
 
diff --git a/src/platform/tests/test-common.c b/src/platform/tests/test-common.c
index c510710b..7ebda9ff 100644
--- a/src/platform/tests/test-common.c
+++ b/src/platform/tests/test-common.c
@@ -900,7 +900,7 @@ _ip_address_add (NMPlatform *platform,
 	}
 
 	/* Let's wait until we see the address. */
-	end_time = nm_utils_get_monotonic_timestamp_ms () + 250;
+	end_time = nm_utils_get_monotonic_timestamp_ms () + 500;
 	do {
 
 		if (external_command)
diff --git a/src/platform/tests/test-common.h b/src/platform/tests/test-common.h
index aa1f5460..e4eaec29 100644
--- a/src/platform/tests/test-common.h
+++ b/src/platform/tests/test-common.h
@@ -273,6 +273,58 @@ nmtstp_ip6_route_get_all (NMPlatform *platform,
 GArray *nmtstp_platform_ip4_address_get_all (NMPlatform *self, int ifindex);
 GArray *nmtstp_platform_ip6_address_get_all (NMPlatform *self, int ifindex);
 
+/*****************************************************************************/
+
+static inline gboolean
+_nmtstp_platform_routing_rules_get_all_predicate (const NMPObject *obj,
+                                                  gpointer user_data)
+{
+	int addr_family = GPOINTER_TO_INT (user_data);
+
+	g_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_ROUTING_RULE);
+
+	return    addr_family == AF_UNSPEC
+	       || NMP_OBJECT_CAST_ROUTING_RULE (obj)->addr_family == addr_family;
+}
+
+static inline GPtrArray *
+nmtstp_platform_routing_rules_get_all (NMPlatform *platform, int addr_family)
+{
+	NMPLookup lookup;
+
+	g_assert (NM_IS_PLATFORM (platform));
+	g_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
+	return nm_platform_lookup_clone (platform,
+	                                 &lookup,
+	                                 _nmtstp_platform_routing_rules_get_all_predicate,
+	                                 GINT_TO_POINTER (addr_family));
+}
+
+static inline guint
+nmtstp_platform_routing_rules_get_count (NMPlatform *platform, int addr_family)
+{
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+	const NMPObject *obj;
+	NMPLookup lookup;
+	guint n;
+
+	g_assert (NM_IS_PLATFORM (platform));
+	g_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
+	head_entry = nm_platform_lookup (platform, &lookup);
+
+	n = 0;
+	nmp_cache_iter_for_each (&iter, head_entry, &obj) {
+		if (_nmtstp_platform_routing_rules_get_all_predicate (obj, GINT_TO_POINTER (addr_family)))
+			n++;
+	}
+	return n;
+}
+
 gboolean nmtstp_platform_ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric);
 gboolean nmtstp_platform_ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric);
 
diff --git a/src/platform/tests/test-link.c b/src/platform/tests/test-link.c
index 71324301..a0650cc1 100644
--- a/src/platform/tests/test-link.c
+++ b/src/platform/tests/test-link.c
@@ -25,7 +25,7 @@
 #include <sys/types.h>
 #include <linux/if_tun.h>
 
-#include "nm-utils/nm-io-utils.h"
+#include "nm-glib-aux/nm-io-utils.h"
 #include "platform/nmp-object.h"
 #include "platform/nmp-netns.h"
 #include "platform/nm-platform-utils.h"
@@ -521,8 +521,7 @@ test_bridge_addr (void)
 	plink = nm_platform_link_get (NM_PLATFORM_GET, link.ifindex);
 	g_assert (plink);
 
-	if (nm_platform_check_kernel_support (NM_PLATFORM_GET,
-	                                      NM_PLATFORM_KERNEL_SUPPORT_USER_IPV6LL)) {
+	if (nm_platform_kernel_support_get (NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) {
 		g_assert (!nm_platform_link_get_user_ipv6ll_enabled (NM_PLATFORM_GET, link.ifindex));
 		g_assert_cmpint (_nm_platform_uint8_inv (plink->inet6_addr_gen_mode_inv), ==, NM_IN6_ADDR_GEN_MODE_EUI64);
 
@@ -2963,6 +2962,37 @@ test_sysctl_netns_switch (void)
 	g_assert_cmpint (ifindex, ==, (gint32) nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_NETDIR (dirfd, s ?: "<unknown>", "ifindex"), -1));
 	g_assert_cmpint (ifindex, ==, (gint32) nm_platform_sysctl_get_int32 (PL, NMP_SYSCTL_PATHID_ABSOLUTE (nm_sprintf_bufa (100, "/sys/class/net/%s/ifindex", IFNAME)), -1));
 
+	/* also test that nm_platform_sysctl_get() sets errno to ENOENT for non-existing paths. */
+	{
+		gint64 i64;
+		int errsv;
+		char *v;
+
+		errno = ESRCH;
+		v = nm_platform_sysctl_get (PL, NMP_SYSCTL_PATHID_ABSOLUTE ("/sys/devices/virtual/net/not-existing/ifindex"));
+		errsv = errno;
+		g_assert (!v);
+		g_assert_cmpint (errsv, ==, ENOENT);
+
+		errno = ESRCH;
+		i64 = nm_platform_sysctl_get_int_checked (PL, NMP_SYSCTL_PATHID_ABSOLUTE ("/sys/devices/virtual/net/not-existing/ifindex"), 10, 1, G_MAXINT, -1);
+		errsv = errno;
+		g_assert_cmpint (i64, ==, -1);
+		g_assert_cmpint (errsv, ==, ENOENT);
+
+		errno = ESRCH;
+		v = nm_platform_sysctl_get (PL, NMP_SYSCTL_PATHID_ABSOLUTE ("/sys/devices/virtual/net/lo/not-existing"));
+		errsv = errno;
+		g_assert (!v);
+		g_assert_cmpint (errsv, ==, ENOENT);
+
+		errno = ESRCH;
+		i64 = nm_platform_sysctl_get_int_checked (PL, NMP_SYSCTL_PATHID_ABSOLUTE ("/sys/devices/virtual/net/lo/not-existing"), 10, 1, G_MAXINT, -1);
+		errsv = errno;
+		g_assert_cmpint (i64, ==, -1);
+		g_assert_cmpint (errsv, ==, ENOENT);
+	}
+
 	/* accessing the path directly, only succeeds iff the current namespace happens to be the namespace
 	 * in which we created the link. */
 	{
diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c
index 12acf4a5..e2019e96 100644
--- a/src/platform/tests/test-nmp-object.c
+++ b/src/platform/tests/test-nmp-object.c
@@ -24,7 +24,7 @@
 #include <linux/pkt_sched.h>
 
 #include "platform/nmp-object.h"
-#include "nm-utils/nm-udev-utils.h"
+#include "nm-udev-aux/nm-udev-utils.h"
 
 #include "nm-test-utils-core.h"
 
@@ -223,7 +223,7 @@ _nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, const NMPObject **ou
 	g_assert (cache);
 	g_assert (NMP_OBJECT_IS_VALID (obj));
 
-	obj_prev = nmp_cache_lookup_link (cache, obj->object.ifindex);
+	obj_prev = nmp_cache_lookup_link (cache, NMP_OBJECT_CAST_LINK (obj)->ifindex);
 	obj_new_expected = nmp_object_clone (obj, FALSE);
 	if (obj_prev && obj_prev->_link.udev.device)
 		obj_new_expected->_link.udev.device = udev_device_ref (obj_prev->_link.udev.device);
diff --git a/src/platform/tests/test-route.c b/src/platform/tests/test-route.c
index 2619ec52..e6ffe1ae 100644
--- a/src/platform/tests/test-route.c
+++ b/src/platform/tests/test-route.c
@@ -20,9 +20,11 @@
 #include "nm-default.h"
 
 #include <linux/rtnetlink.h>
+#include <linux/fib_rules.h>
 
 #include "nm-core-utils.h"
 #include "platform/nm-platform-utils.h"
+#include "platform/nmp-rules-manager.h"
 
 #include "test-common.h"
 
@@ -873,6 +875,861 @@ again_find_idx:
 
 /*****************************************************************************/
 
+#define FRA_SUPPRESS_IFGROUP         13
+#define FRA_SUPPRESS_PREFIXLEN       14
+#define FRA_L3MDEV                   19
+#define FRA_UID_RANGE                20
+#define FRA_PROTOCOL                 21
+#define FRA_IP_PROTO                 22
+#define FRA_SPORT_RANGE              23
+#define FRA_DPORT_RANGE              24
+
+static const NMPObject *
+_rule_find_by_priority (NMPlatform *platform,
+                        guint32 priority)
+{
+	const NMDedupMultiHeadEntry *head_entry;
+	NMDedupMultiIter iter;
+	const NMPObject *o;
+	const NMPObject *obj = NULL;
+	NMPLookup lookup;
+
+	nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
+	head_entry = nm_platform_lookup (platform, &lookup);
+	nmp_cache_iter_for_each (&iter, head_entry, &o) {
+		if (NMP_OBJECT_CAST_ROUTING_RULE (o)->priority != priority)
+			continue;
+		g_assert (!obj);
+		obj = o;
+	}
+	return obj;
+}
+
+static const NMPObject *
+_rule_check_kernel_support_one (NMPlatform *platform,
+                                const NMPlatformRoutingRule *rr)
+{
+	nm_auto_nmpobj const NMPObject *obj = NULL;
+	int r;
+
+	g_assert (!_rule_find_by_priority (platform, rr->priority));
+
+	r = nm_platform_routing_rule_add (platform, NMP_NLM_FLAG_ADD, rr);
+	g_assert_cmpint (r, ==, 0);
+
+	obj = nmp_object_ref (_rule_find_by_priority (platform, rr->priority));
+	g_assert (obj);
+
+	r = nm_platform_object_delete (platform, obj);
+	g_assert_cmpint (r, ==, TRUE);
+
+	g_assert (!_rule_find_by_priority (platform, rr->priority));
+
+	return g_steal_pointer (&obj);
+}
+
+static gboolean
+_rule_check_kernel_support (NMPlatform *platform,
+                            int attribute)
+{
+	static int support[] = {
+		[FRA_SUPPRESS_IFGROUP]   = 0,
+		[FRA_SUPPRESS_PREFIXLEN] = 0,
+		[FRA_L3MDEV]             = 0,
+		[FRA_UID_RANGE]          = 0,
+		[FRA_PROTOCOL]           = 0,
+		[FRA_IP_PROTO]           = 0,
+		[FRA_SPORT_RANGE]        = 0,
+		[FRA_DPORT_RANGE]        = 0,
+	};
+	const guint32 PROBE_PRORITY = 12033;
+	gboolean sup;
+	int i;
+
+	g_assert (NM_IS_PLATFORM (platform));
+
+	if (attribute == -1) {
+		for (i = 0; i < G_N_ELEMENTS (support); i++) {
+			if (support[i] < 0) {
+				/* indicate that some test was skipped. */
+				return FALSE;
+			}
+		}
+		return TRUE;
+	}
+
+	g_assert (attribute >= 0 && attribute < G_N_ELEMENTS (support));
+
+	if (support[attribute] != 0)
+		return support[attribute] >= 0;
+
+	switch (attribute) {
+	case FRA_SUPPRESS_IFGROUP: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family              = AF_INET,
+			.priority                 = PROBE_PRORITY,
+			.suppress_ifgroup_inverse = ~((guint32) 1245),
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->suppress_prefixlen_inverse == rr.suppress_ifgroup_inverse;
+		break;
+	}
+	case FRA_SUPPRESS_PREFIXLEN: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family                = AF_INET,
+			.priority                   = PROBE_PRORITY,
+			.suppress_prefixlen_inverse = ~((guint32) 1245),
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->suppress_prefixlen_inverse == rr.suppress_prefixlen_inverse;
+		break;
+	}
+	case FRA_L3MDEV: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family = AF_INET,
+			.priority    = PROBE_PRORITY,
+			.l3mdev      = TRUE,
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->l3mdev != 0;
+		break;
+	}
+	case FRA_UID_RANGE: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family  = AF_INET,
+			.priority     = PROBE_PRORITY,
+			.uid_range    = {
+				.start = 0,
+				.end   = 0,
+			},
+			.uid_range_has = TRUE,
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->uid_range_has;
+		break;
+	}
+	case FRA_PROTOCOL: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family = AF_INET,
+			.priority    = PROBE_PRORITY,
+			.protocol    = 30,
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->protocol == 30;
+		break;
+	}
+	case FRA_IP_PROTO: {
+		nm_auto_nmpobj const NMPObject *obj = NULL;
+		const NMPlatformRoutingRule rr = {
+			.addr_family = AF_INET,
+			.priority    = PROBE_PRORITY,
+			.ip_proto    = 30,
+		};
+
+		obj = _rule_check_kernel_support_one (platform, &rr);
+
+		sup = NMP_OBJECT_CAST_ROUTING_RULE (obj)->ip_proto == 30;
+		break;
+	}
+	case FRA_SPORT_RANGE:
+	case FRA_DPORT_RANGE:
+		/* these were added at the same time as FRA_IP_PROTO. */
+		sup = _rule_check_kernel_support (platform, FRA_IP_PROTO);
+		break;
+	default:
+		g_assert_not_reached ();
+		return FALSE;
+	}
+
+	support[attribute] = sup ? 1 : -1;
+
+	_LOGD ("kernel support for routing rule attribute #%d %s", attribute, sup ? "detected" : "not detected");
+	return sup;
+}
+
+static const NMPObject *
+_platform_has_routing_rule (NMPlatform *platform,
+                            const NMPObject *obj)
+{
+	const NMPObject *o;
+
+	g_assert (NM_IS_PLATFORM (platform));
+	g_assert (NMP_OBJECT_IS_VALID (obj));
+	g_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_ROUTING_RULE);
+
+	o = nm_platform_lookup_obj (platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj);
+	if (o)
+		g_assert (nm_platform_routing_rule_cmp (NMP_OBJECT_CAST_ROUTING_RULE (obj), NMP_OBJECT_CAST_ROUTING_RULE (o), NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0);
+
+	return o;
+}
+
+static guint32
+_rr_rand_choose_u32 (guint32 p)
+{
+	/* mostly, we just return zero. We want that each rule only has few
+	 * fields set -- having most fields at zero. */
+	if ((p % 10000u) < 7500u)
+		return 0;
+
+	/* give 0xFFFFFFFFu extra probability. */
+	if ((p % 10000u) < 8250u)
+		return 0xFFFFFFFFu;
+
+	/* choose a small number. */
+	if ((p % 10000u) < 9125u)
+		return (~p) % 10;
+
+	/* finally, full random number. */
+	return ~p;
+}
+
+#define _rr_rand_choose_u8(p)  ((guint8) _rr_rand_choose_u32 ((p)))
+
+static const NMPObject *
+_rule_create_random (NMPlatform *platform)
+{
+	NMPObject *obj;
+	NMPlatformRoutingRule *rr;
+	guint32 p;
+	int addr_size;
+	guint i;
+	char saddr[NM_UTILS_INET_ADDRSTRLEN];
+	static struct {
+		guint32 uid;
+		guint32 euid;
+		bool initialized;
+	} uids;
+
+	if (G_UNLIKELY (!uids.initialized)) {
+		uids.uid = getuid ();
+		uids.euid = geteuid ();
+		uids.initialized = TRUE;
+	}
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_ROUTING_RULE, NULL);
+	rr = NMP_OBJECT_CAST_ROUTING_RULE (obj);
+
+	rr->addr_family = nmtst_rand_select (AF_INET, AF_INET6);
+
+	addr_size = nm_utils_addr_family_to_size (rr->addr_family);
+
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 50)
+		rr->priority = 10000 + ((~p) % 20u);
+
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 40)
+		nm_sprintf_buf (rr->iifname, "t-iif-%u", (~p) % 20);
+	else if ((p % 1000u) < 80)
+		nm_sprintf_buf (rr->iifname, "%s", DEVICE_NAME);
+
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 40)
+		nm_sprintf_buf (rr->oifname, "t-oif-%d", (~p) % 20);
+	else if ((p % 1000u) < 80)
+		nm_sprintf_buf (rr->oifname, "%s", DEVICE_NAME);
+
+	for (i = 0; i < 2; i++) {
+		NMIPAddr *p_addr = i ? &rr->src     : &rr->dst;
+		guint8 *p_len    = i ? &rr->src_len : &rr->dst_len;
+
+		p = nmtst_get_rand_int ();
+		if ((p % 1000u) < 100) {
+			/* if we set src_len/dst_len to zero, the src/dst is actually ignored.
+			 *
+			 * For fuzzying, still set the address. It shall have no further effect.
+			 * */
+			*p_len = (~p) % (addr_size * 8 + 1);
+			p = nmtst_get_rand_int ();
+			if ((p % 3u) == 0) {
+				if (rr->addr_family == AF_INET)
+					p_addr->addr4 = nmtst_inet4_from_string (nm_sprintf_buf (saddr, "192.192.5.%u", (~p) % 256u));
+				else
+					p_addr->addr6 = *nmtst_inet6_from_string (nm_sprintf_buf (saddr, "1:2:3:4::f:%02x", (~p) % 256u));
+			} else if ((p % 3u) == 1)
+				nmtst_rand_buf (NULL, p_addr, addr_size);
+		}
+	}
+
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 50)
+		rr->tun_id = 10000 + ((~p) % 20);
+
+again_action:
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 500)
+		rr->action = FR_ACT_UNSPEC;
+	else if ((p % 1000u) < 750)
+		rr->action = (~p) % 12u;
+	else
+		rr->action = (~p) % 0x100u;
+
+	rr->priority = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+
+	if (   rr->action == FR_ACT_GOTO
+	    && rr->priority == G_MAXINT32)
+		goto again_action;
+
+	p = nmtst_get_rand_int ();
+	if ((p % 10000u) < 100)
+		rr->goto_target = rr->priority + 1;
+	else
+		rr->goto_target = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+	if (   rr->action == FR_ACT_GOTO
+	    && rr->goto_target <= rr->priority)
+		goto again_action;
+
+	p = nmtst_get_rand_int ();
+	if ((p % 1000u) < 25) {
+		if (_rule_check_kernel_support (platform, FRA_L3MDEV)) {
+			rr->l3mdev = TRUE;
+			rr->table = RT_TABLE_UNSPEC;
+		}
+	}
+
+again_table:
+	if (!rr->l3mdev) {
+		p = nmtst_get_rand_int ();
+		if ((p % 1000u) < 700)
+			rr->table = RT_TABLE_UNSPEC;
+		else if ((p % 1000u) < 850)
+			rr->table = RT_TABLE_MAIN;
+		else
+			rr->table = 10000 + ((~p) % 10);
+		if (   rr->action == FR_ACT_TO_TBL
+		    && rr->table == RT_TABLE_UNSPEC)
+			goto again_table;
+	}
+
+	rr->fwmark = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+	rr->fwmask = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+
+	rr->flow = _rr_rand_choose_u32 (nmtst_get_rand_int ());
+
+	if (_rule_check_kernel_support (platform, FRA_PROTOCOL))
+		rr->protocol = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+
+#define IPTOS_TOS_MASK 0x1E
+
+again_tos:
+	rr->tos = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+	if (   rr->addr_family == AF_INET
+	    && rr->tos & ~IPTOS_TOS_MASK)
+		goto again_tos;
+
+	if (_rule_check_kernel_support (platform, FRA_IP_PROTO))
+		rr->ip_proto = _rr_rand_choose_u8 (nmtst_get_rand_int ());
+
+	if (_rule_check_kernel_support (platform, FRA_SUPPRESS_PREFIXLEN))
+		rr->suppress_prefixlen_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_int ());
+
+	if (_rule_check_kernel_support (platform, FRA_SUPPRESS_IFGROUP))
+		rr->suppress_ifgroup_inverse = ~_rr_rand_choose_u32 (nmtst_get_rand_int ());
+
+	if (_rule_check_kernel_support (platform, FRA_UID_RANGE)) {
+		p = nmtst_get_rand_int ();
+		rr->uid_range_has = (p % 10000u) < 200;
+	}
+
+again_uid_range:
+	rr->uid_range.start = nmtst_rand_select (0u, uids.uid, uids.euid);
+	rr->uid_range.end   = nmtst_rand_select (0u, uids.uid, uids.euid);
+	if (rr->uid_range_has) {
+		if (rr->uid_range.end < rr->uid_range.start)
+			NMTST_SWAP (rr->uid_range.start, rr->uid_range.end);
+		if (   rr->uid_range.start == ((guint32) -1)
+		    || rr->uid_range.end   == ((guint32) -1))
+			goto again_uid_range;
+	}
+
+	for (i = 0; i < 2; i++) {
+		NMFibRulePortRange *range = i ? &rr->sport_range : &rr->dport_range;
+		int attribute             = i ? FRA_SPORT_RANGE  : FRA_DPORT_RANGE;
+
+		if (!_rule_check_kernel_support (platform, attribute))
+			continue;
+
+		p = nmtst_get_rand_int ();
+		if ((p % 10000u) < 300) {
+			while (range->start == 0) {
+				p = p ^ nmtst_get_rand_int ();
+				range->start = nmtst_rand_select (1u, 0xFFFEu, ((p      ) % 0xFFFEu) + 1);
+				range->end   = nmtst_rand_select (1u, 0xFFFEu, ((p >> 16) % 0xFFFEu) + 1, range->start);
+				if (range->end < range->start)
+					NMTST_SWAP (range->start, range->end);
+			}
+		}
+	}
+
+	p = nmtst_get_rand_int () % 1000u;
+	if (p < 100)
+		rr->flags |= FIB_RULE_INVERT;
+
+	return obj;
+}
+
+static gboolean
+_rule_fuzzy_equal (const NMPObject *obj,
+                   const NMPObject *obj_comp,
+                   int op_type)
+{
+	const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE (obj);
+	NMPlatformRoutingRule rr_co = *NMP_OBJECT_CAST_ROUTING_RULE (obj_comp);
+
+	switch (op_type) {
+	case RTM_NEWRULE:
+		/* when adding rules with RTM_NEWRULE, kernel checks whether an existing
+		 * rule already exists and may fail with EEXIST. This check has issues
+		 * and reject legitimate rules (rh#1686075).
+		 *
+		 * Work around that. */
+		if (rr->src_len == 0)
+			rr_co.src_len = 0;
+		if (rr->dst_len == 0)
+			rr_co.dst_len = 0;
+		if (rr->flow == 0)
+			rr_co.flow = 0;
+		if (rr->tos == 0)
+			rr_co.tos = 0;
+		rr_co.suppress_prefixlen_inverse = rr->suppress_prefixlen_inverse;
+		rr_co.suppress_ifgroup_inverse = rr->suppress_ifgroup_inverse;
+		if (!NM_FLAGS_HAS (rr->flags, FIB_RULE_INVERT))
+			rr_co.flags &= ~((guint32) FIB_RULE_INVERT);
+		else
+			rr_co.flags |= ((guint32) FIB_RULE_INVERT);
+		break;
+	case RTM_DELRULE:
+		/* when deleting a rule with RTM_DELRULE, kernel tries to find the
+		 * cadidate to delete. It might delete the wrong rule (rh#1685816). */
+		if (rr->action == FR_ACT_UNSPEC)
+			rr_co.action = FR_ACT_UNSPEC;
+		if (rr->iifname[0] == '\0')
+			rr_co.iifname[0] = '\0';
+		if (rr->oifname[0] == '\0')
+			rr_co.oifname[0] = '\0';
+		if (rr->src_len == 0)
+			rr_co.src_len = 0;
+		if (rr->dst_len == 0)
+			rr_co.dst_len = 0;
+		if (rr->tun_id == 0)
+			rr_co.tun_id = 0;
+		if (rr->fwmark == 0)
+			rr_co.fwmark = 0;
+		if (rr->fwmask == 0)
+			rr_co.fwmask = 0;
+		if (rr->flow == 0)
+			rr_co.flow = 0;
+		if (rr->protocol == 0)
+			rr_co.protocol = 0;
+		if (rr->table == RT_TABLE_UNSPEC)
+			rr_co.table = RT_TABLE_UNSPEC;
+		if (rr->l3mdev == 0)
+			rr_co.l3mdev = 0;
+		if (rr->tos == 0)
+			rr_co.tos = 0;
+		if (rr->ip_proto == 0)
+			rr_co.ip_proto = 0;
+		if (rr->suppress_prefixlen_inverse == 0)
+			rr_co.suppress_prefixlen_inverse = 0;
+		if (rr->suppress_ifgroup_inverse == 0)
+			rr_co.suppress_ifgroup_inverse = 0;
+		if (!rr->uid_range_has)
+			rr_co.uid_range_has = FALSE;
+		if (rr->sport_range.start == 0 && rr->sport_range.end == 0) {
+			rr_co.sport_range.start = 0;
+			rr_co.sport_range.end   = 0;
+		}
+		if (rr->dport_range.start == 0 && rr->dport_range.end == 0) {
+			rr_co.dport_range.start = 0;
+			rr_co.dport_range.end   = 0;
+		}
+		if (!NM_FLAGS_HAS (rr->flags, FIB_RULE_INVERT))
+			rr_co.flags &= ~((guint32) FIB_RULE_INVERT);
+		else
+			rr_co.flags |= ((guint32) FIB_RULE_INVERT);
+		break;
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+
+	return nm_platform_routing_rule_cmp (rr, &rr_co, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0;
+}
+
+static void
+test_rule (gconstpointer test_data)
+{
+	const int TEST_IDX = GPOINTER_TO_INT (test_data);
+	const gboolean TEST_SYNC = (TEST_IDX == 4);
+	gs_unref_ptrarray GPtrArray *objs = NULL;
+	gs_unref_ptrarray GPtrArray *objs_initial = NULL;
+	NMPlatform *platform = NM_PLATFORM_GET;
+	guint i, j, n;
+	int r;
+	gboolean had_an_issue_exist = FALSE;
+
+	nm_platform_process_events (platform);
+
+	objs_initial = nmtstp_platform_routing_rules_get_all (platform, AF_UNSPEC);
+	g_assert (objs_initial);
+	g_assert_cmpint (objs_initial->len, ==, 5);
+
+	nmtstp_run_command_check ("ip rule add table 766");
+	nm_platform_process_events (platform);
+
+	for (i = 6; i > 0; i--) {
+		gs_unref_ptrarray GPtrArray *objs_extern = NULL;
+		const NMPObject *obj;
+
+		objs_extern = nmtstp_platform_routing_rules_get_all (platform, AF_UNSPEC);
+
+		g_assert (objs_extern);
+		g_assert_cmpint (objs_extern->len, ==, i);
+
+		if (TEST_IDX != 1)
+			nmtst_rand_perm (NULL, objs_extern->pdata, NULL, sizeof (gpointer), objs_extern->len);
+
+		obj = objs_extern->pdata[0];
+
+		r = nm_platform_object_delete (platform, obj);
+		g_assert_cmpint (r, ==, TRUE);
+
+		g_assert (!_platform_has_routing_rule (platform, obj));
+	}
+
+	g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, 0);
+
+#define RR(...) \
+	nmp_object_new (NMP_OBJECT_TYPE_ROUTING_RULE, \
+	                (const NMPlatformObject *) &((NMPlatformRoutingRule) { \
+	                  __VA_ARGS__ \
+	                }))
+
+	objs = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET,
+		.priority    = 10,
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET,
+		.priority    = 400,
+		.action      = FR_ACT_GOTO,
+		.goto_target = 10000,
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET6,
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET6,
+		.action      = FR_ACT_TO_TBL,
+		.table       = RT_TABLE_MAIN,
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET6,
+		.priority    = 30,
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET6,
+		.priority    = 50,
+		.iifname     = "t-iif-1",
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET6,
+		.priority    = 50,
+		.iifname     = "t-oif-1",
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET,
+		.priority    = 50,
+		.iifname     = "t-oif-2",
+	));
+
+	g_ptr_array_add (objs, RR (
+		.addr_family = AF_INET,
+		.priority    = 51,
+		.iifname     = DEVICE_NAME,
+	));
+
+	if (TEST_IDX == 1) {
+		g_ptr_array_add (objs, RR (
+			.addr_family = AF_INET,
+			.table       = 10000,
+		));
+	}
+
+	if (TEST_IDX != 1) {
+		nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len);
+		g_ptr_array_set_size (objs, nmtst_get_rand_int () % (objs->len + 1));
+	}
+
+	n = (TEST_IDX != 1) ? nmtst_get_rand_int () % 50u : 0u;
+	for (i = 0; i < n; i++) {
+		nm_auto_nmpobj const NMPObject *o = NULL;
+		guint try = 0;
+
+again:
+		o = _rule_create_random (platform);
+		for (j = 0; j < objs->len; j++) {
+			if (nm_platform_routing_rule_cmp (NMP_OBJECT_CAST_ROUTING_RULE (o),
+			                                  NMP_OBJECT_CAST_ROUTING_RULE (objs->pdata[j]),
+			                                  NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0)  {
+				try++;
+				g_assert (try < 200);
+				nm_clear_pointer (&o, nmp_object_unref);
+				goto again;
+			}
+		}
+		g_ptr_array_add (objs, (gpointer) g_steal_pointer (&o));
+	}
+
+	if (TEST_IDX != 1)
+		nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len);
+
+	if (TEST_SYNC) {
+		gs_unref_hashtable GHashTable *unique_priorities = g_hash_table_new (NULL, NULL);
+		nm_auto_unref_rules_manager NMPRulesManager *rules_manager = nmp_rules_manager_new (platform);
+		gs_unref_ptrarray GPtrArray *objs_sync = NULL;
+		gconstpointer USER_TAG_1 = &platform;
+		gconstpointer USER_TAG_2 = &unique_priorities;
+
+		objs_sync = g_ptr_array_new_with_free_func ((GDestroyNotify) nmp_object_unref);
+
+		/* ensure that priorities are unique. Otherwise it confuses the test, because
+		 * kernel may wrongly be unable to add/delete routes based on a wrong match
+		 * (rh#1685816, rh#1685816). */
+		for (i = 0; i < objs->len; i++) {
+			const NMPObject *obj = objs->pdata[i];
+			guint32 prio = NMP_OBJECT_CAST_ROUTING_RULE (obj)->priority;
+
+			if (   !NM_IN_SET (prio, 0, 32766, 32767)
+			    && !g_hash_table_contains (unique_priorities, GUINT_TO_POINTER (prio))) {
+				g_hash_table_add (unique_priorities, GUINT_TO_POINTER (prio));
+				g_ptr_array_add (objs_sync, (gpointer) nmp_object_ref (obj));
+			}
+		}
+
+		for (i = 0; i < objs_sync->len; i++) {
+			nmp_rules_manager_track (rules_manager,
+			                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+			                         1,
+			                         USER_TAG_1);
+			if (nmtst_get_rand_bool ()) {
+				/* this has no effect, because a negative priority (of same absolute value)
+				 * has lower priority than the positive priority above. */
+				nmp_rules_manager_track (rules_manager,
+				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+				                         -1,
+				                         USER_TAG_2);
+			}
+			if (nmtst_get_rand_int () % objs_sync->len == 0) {
+				nmp_rules_manager_sync (rules_manager, FALSE);
+				g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, i + 1);
+			}
+		}
+
+		nmp_rules_manager_sync (rules_manager, FALSE);
+		g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_sync->len);
+
+		for (i = 0; i < objs_sync->len; i++) {
+			switch (nmtst_get_rand_int () % 3) {
+			case 0:
+				nmp_rules_manager_untrack (rules_manager,
+				                           NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+				                           USER_TAG_1);
+				nmp_rules_manager_untrack (rules_manager,
+				                           NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+				                           USER_TAG_1);
+				break;
+			case 1:
+				nmp_rules_manager_track (rules_manager,
+				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+				                         -1,
+				                         USER_TAG_1);
+				break;
+			case 2:
+				nmp_rules_manager_track (rules_manager,
+				                         NMP_OBJECT_CAST_ROUTING_RULE (objs_sync->pdata[i]),
+				                         -2,
+				                         USER_TAG_2);
+				break;
+			}
+			if (nmtst_get_rand_int () % objs_sync->len == 0) {
+				nmp_rules_manager_sync (rules_manager, FALSE);
+				g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_sync->len - i - 1);
+			}
+		}
+
+		nmp_rules_manager_sync (rules_manager, FALSE);
+
+	} else {
+		for (i = 0; i < objs->len;) {
+			const NMPObject *obj = objs->pdata[i];
+
+			for (j = 0; j < objs->len; j++)
+				g_assert ((j < i) == (!!_platform_has_routing_rule (platform, objs->pdata[j])));
+
+			r = nm_platform_routing_rule_add (platform, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_ROUTING_RULE (obj));
+
+			if (r == -EEXIST) {
+				g_assert (!_platform_has_routing_rule (platform, obj));
+				/* this should not happen, but there are bugs in kernel (rh#1686075). */
+				for (j = 0; j < i; j++) {
+					const NMPObject *obj2 = objs->pdata[j];
+
+					g_assert (_platform_has_routing_rule (platform, obj2));
+
+					if (_rule_fuzzy_equal (obj, obj2, RTM_NEWRULE)) {
+						r = 0;
+						break;
+					}
+				}
+				if (r == 0) {
+					/* OK, the rule is shadowed by another rule, and kernel does not allow
+					 * us to add this one (rh#1686075). Drop this from the test. */
+					g_ptr_array_remove_index (objs, i);
+					had_an_issue_exist = TRUE;
+					continue;
+				}
+			}
+
+			if (r != 0) {
+				NMPLookup lookup;
+				const NMDedupMultiHeadEntry *head_entry;
+				NMDedupMultiIter iter;
+				const NMPObject *o;
+
+				g_print (">>> failing... errno=%d, rule=%s\n", r, nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+
+				nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
+				head_entry = nm_platform_lookup (platform, &lookup);
+				nmp_cache_iter_for_each (&iter, head_entry, &o) {
+					char ch = ' ';
+
+					if (   NMP_OBJECT_CAST_ROUTING_RULE (o)->addr_family == NMP_OBJECT_CAST_ROUTING_RULE (obj)->addr_family
+					    && NMP_OBJECT_CAST_ROUTING_RULE (o)->priority == NMP_OBJECT_CAST_ROUTING_RULE (obj)->priority)
+						ch = '*';
+					g_print (">>> existing rule: %c %s\n", ch, nmp_object_to_string (o, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+				}
+
+				nmtstp_run_command_check ("ip rule");
+				nmtstp_run_command_check ("ip -6 rule");
+				g_assert_cmpint (r, ==, 0);
+			}
+
+			g_assert (_platform_has_routing_rule (platform, obj));
+
+			g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, i + 1);
+
+			i++;
+		}
+
+		if (TEST_IDX != 1)
+			nmtst_rand_perm (NULL, objs->pdata, NULL, sizeof (gpointer), objs->len);
+
+		if (_LOGD_ENABLED ()) {
+			nmtstp_run_command_check ("ip rule");
+			nmtstp_run_command_check ("ip -6 rule");
+		}
+
+		for (i = 0; i < objs->len; i++) {
+			const NMPObject *obj = objs->pdata[i];
+			const NMPObject *obj2;
+
+			for (j = 0; j < objs->len; j++)
+				g_assert ((j < i) == (!_platform_has_routing_rule (platform, objs->pdata[j])));
+
+			g_assert (_platform_has_routing_rule (platform, obj));
+
+			r = nm_platform_object_delete (platform, obj);
+			g_assert_cmpint (r, ==, TRUE);
+
+			obj2 = _platform_has_routing_rule (platform, obj);
+
+			if (obj2) {
+				guint k;
+
+				/* When deleting a rule, kernel does a fuzzy match, ignoring for example:
+				 *  - action, if it is FR_ACT_UNSPEC
+				 *  - iifname,oifname if it is unspecified
+				 * rh#1685816
+				 *
+				 * That means, we may have deleted the wrong rule. Which one? */
+				k = i;
+				for (j = i + 1; j < objs->len; j++) {
+					if (!_platform_has_routing_rule (platform, objs->pdata[j])) {
+						g_assert_cmpint (k, ==, i);
+						k = j;
+					}
+				}
+				g_assert_cmpint (k, >, i);
+
+				if (!_rule_fuzzy_equal (obj, objs->pdata[k], RTM_DELRULE)) {
+					g_print (">>> failing...\n");
+					g_print (">>> no fuzzy match between: %s\n", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+					g_print (">>>                    and: %s\n", nmp_object_to_string (objs->pdata[k], NMP_OBJECT_TO_STRING_ALL, NULL, 0));
+					g_assert_not_reached ();
+				}
+
+				objs->pdata[i] = objs->pdata[k];
+				objs->pdata[k] = (gpointer) obj;
+				obj2 = NULL;
+			}
+
+			g_assert (!obj2);
+
+			g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs->len -i - 1);
+		}
+	}
+
+	g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, 0);
+
+	for (i = 0; i < objs_initial->len; i++) {
+		const NMPObject *obj = objs_initial->pdata[i];
+
+		for (j = 0; j < objs_initial->len; j++)
+			g_assert ((j < i) == (!!_platform_has_routing_rule (platform, objs_initial->pdata[j])));
+
+		r = nm_platform_routing_rule_add (platform, NMP_NLM_FLAG_ADD, NMP_OBJECT_CAST_ROUTING_RULE (obj));
+		g_assert_cmpint (r, ==, 0);
+	}
+	for (j = 0; j < objs_initial->len; j++)
+		g_assert (_platform_has_routing_rule (platform, objs_initial->pdata[j]));
+	g_assert_cmpint (nmtstp_platform_routing_rules_get_count (platform, AF_UNSPEC), ==, objs_initial->len);
+
+	/* the tests passed as good as we could (as good as we implemented workarounds for them).
+	 * Still, with this kernel, not all features were fully tested. Mark the test as skipped. */
+	if (had_an_issue_exist)
+		g_test_skip ("adding a rule failed with EEXIST although it should not (rh#1686075)");
+	else if (!_rule_check_kernel_support (platform, -1))
+		g_test_skip ("some kernel features were not available and skipped for the test");
+}
+
+/*****************************************************************************/
+
 NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
 
 void
@@ -902,4 +1759,11 @@ _nmtstp_setup_tests (void)
 		add_test_func ("/route/ip6_route_get", test_ip6_route_get);
 		add_test_func ("/route/ip4_zero_gateway", test_ip4_zero_gateway);
 	}
+
+	if (nmtstp_is_root_test ()) {
+		add_test_func_data ("/route/rule/1", test_rule, GINT_TO_POINTER (1));
+		add_test_func_data ("/route/rule/2", test_rule, GINT_TO_POINTER (2));
+		add_test_func_data ("/route/rule/3", test_rule, GINT_TO_POINTER (3));
+		add_test_func_data ("/route/rule/4", test_rule, GINT_TO_POINTER (4));
+	}
 }