summary refs log tree commit diff
path: root/src/core/platform
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/platform')
-rw-r--r--src/core/platform/nm-fake-platform.c9
-rw-r--r--src/core/platform/tests/monitor.c31
-rw-r--r--src/core/platform/tests/test-common.c16
-rw-r--r--src/core/platform/tests/test-common.h5
-rw-r--r--src/core/platform/tests/test-link.c9
-rw-r--r--src/core/platform/tests/test-nmp-object.c4
-rw-r--r--src/core/platform/tests/test-platform-general.c60
-rw-r--r--src/core/platform/tests/test-route.c275
-rw-r--r--src/core/platform/tests/test-tc.c9
9 files changed, 359 insertions, 59 deletions
diff --git a/src/core/platform/nm-fake-platform.c b/src/core/platform/nm-fake-platform.c
index 7d8986d7..a1ca5434 100644
--- a/src/core/platform/nm-fake-platform.c
+++ b/src/core/platform/nm-fake-platform.c
@@ -323,6 +323,15 @@ link_add(NMPlatform            *platform,
         dev_lnk = nmp_object_new(NMP_OBJECT_TYPE_LNK_BRIDGE, props);
         break;
     }
+    case NM_LINK_TYPE_BOND:
+    {
+        const NMPlatformLnkBond *props = extra_data;
+
+        nm_assert(props);
+
+        dev_lnk = nmp_object_new(NMP_OBJECT_TYPE_LNK_BOND, props);
+        break;
+    }
     case NM_LINK_TYPE_VETH:
         veth_peer = extra_data;
         g_assert(veth_peer);
diff --git a/src/core/platform/tests/monitor.c b/src/core/platform/tests/monitor.c
index ff6fae65..8d0e0107 100644
--- a/src/core/platform/tests/monitor.c
+++ b/src/core/platform/tests/monitor.c
@@ -9,6 +9,7 @@
 #include <syslog.h>
 
 #include "libnm-platform/nm-linux-platform.h"
+#include "libnm-platform/nmp-object.h"
 
 #include "nm-test-utils-core.h"
 
@@ -25,14 +26,14 @@ read_argv(int *argc, char ***argv)
 {
     GOptionContext *context;
     GOptionEntry    options[] = {
-        {"no-persist",
-         'P',
-         G_OPTION_FLAG_REVERSE,
-         G_OPTION_ARG_NONE,
-         &global_opt.persist,
-         "Exit after processing netlink messages",
-         NULL},
-        {0},
+           {"no-persist",
+            'P',
+            G_OPTION_FLAG_REVERSE,
+            G_OPTION_ARG_NONE,
+            &global_opt.persist,
+            "Exit after processing netlink messages",
+            NULL},
+           {0},
     };
     gs_free_error GError *error = NULL;
 
@@ -50,6 +51,18 @@ read_argv(int *argc, char ***argv)
     return TRUE;
 }
 
+/*****************************************************************************/
+
+static void
+mptcp_addr_dump(NMPlatform *platform)
+{
+    gs_unref_ptrarray GPtrArray *addrs = NULL;
+
+    addrs = nm_platform_mptcp_addrs_dump(platform);
+}
+
+/*****************************************************************************/
+
 int
 main(int argc, char **argv)
 {
@@ -69,6 +82,8 @@ main(int argc, char **argv)
 
     nm_linux_platform_setup();
 
+    mptcp_addr_dump(NM_PLATFORM_GET);
+
     if (global_opt.persist)
         g_main_loop_run(loop);
 
diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c
index d7fed220..34ec5cc2 100644
--- a/src/core/platform/tests/test-common.c
+++ b/src/core/platform/tests/test-common.c
@@ -70,7 +70,7 @@ _ipx_address_get_all(NMPlatform *self, int ifindex, NMPObjectType obj_type)
     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);
+    nmp_lookup_init_object_by_ifindex(&lookup, obj_type, ifindex);
     return nmp_cache_lookup_to_array(nm_platform_lookup(self, &lookup),
                                      obj_type,
                                      FALSE /*addresses are always visible. */);
@@ -103,7 +103,7 @@ nmtstp_platform_ip_address_find(NMPlatform *self, int ifindex, int addr_family,
     nm_assert(addr);
 
     if (ifindex > 0)
-        nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex);
+        nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex);
     else
         nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4));
 
@@ -187,7 +187,9 @@ _nmtstp_platform_ip_addresses_assert(const char        *filename,
 
         plat_addrs = nm_platform_lookup_clone(
             self,
-            nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex),
+            nmp_lookup_init_object_by_ifindex(&lookup,
+                                              NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4),
+                                              ifindex),
             NULL,
             NULL);
 
@@ -1936,10 +1938,10 @@ nmtstp_link_macvlan_add(NMPlatform                 *platform,
     if (external_command) {
         const char *dev;
         char       *modes[] = {
-            [MACVLAN_MODE_BRIDGE]   = "bridge",
-            [MACVLAN_MODE_VEPA]     = "vepa",
-            [MACVLAN_MODE_PRIVATE]  = "private",
-            [MACVLAN_MODE_PASSTHRU] = "passthru",
+                  [MACVLAN_MODE_BRIDGE]   = "bridge",
+                  [MACVLAN_MODE_VEPA]     = "vepa",
+                  [MACVLAN_MODE_PRIVATE]  = "private",
+                  [MACVLAN_MODE_PASSTHRU] = "passthru",
         };
 
         dev = nm_platform_link_get_name(platform, parent);
diff --git a/src/core/platform/tests/test-common.h b/src/core/platform/tests/test-common.h
index 77f2c50d..da30b0ed 100644
--- a/src/core/platform/tests/test-common.h
+++ b/src/core/platform/tests/test-common.h
@@ -518,6 +518,11 @@ void nmtstp_link_delete(NMPlatform *platform,
 
 /*****************************************************************************/
 
+#define nmtst_object_new_mptcp_addr(...) \
+    nmp_object_new(NMP_OBJECT_TYPE_MPTCP_ADDR, &((const NMPlatformMptcpAddr){__VA_ARGS__}))
+
+/*****************************************************************************/
+
 extern int NMTSTP_ENV1_IFINDEX;
 extern int NMTSTP_ENV1_EX;
 
diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c
index 48b321d2..b72bcb65 100644
--- a/src/core/platform/tests/test-link.c
+++ b/src/core/platform/tests/test-link.c
@@ -111,13 +111,16 @@ software_add(NMLinkType link_type, const char *name)
     {
         gboolean bond0_exists = !!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "bond0");
         int      r;
+        const NMPlatformLnkBond nm_platform_lnk_bond_default = {
+            .mode = 3,
+        };
 
-        r = nm_platform_link_bond_add(NM_PLATFORM_GET, name, NULL);
+        r = nm_platform_link_bond_add(NM_PLATFORM_GET, name, &nm_platform_lnk_bond_default, NULL);
 
         /* Check that bond0 is *not* automatically created. */
         if (!bond0_exists)
             g_assert(!nm_platform_link_get_by_ifname(NM_PLATFORM_GET, "bond0"));
-        return r >= 0;
+        return NMTST_NM_ERR_SUCCESS(r);
     }
     case NM_LINK_TYPE_TEAM:
         return NMTST_NM_ERR_SUCCESS(nm_platform_link_team_add(NM_PLATFORM_GET, name, NULL));
@@ -853,7 +856,7 @@ static void
 _test_wireguard_change(NMPlatform *platform, int ifindex, int test_mode)
 {
     const KeyPair self_key  = {"yOWEsaXFxX9/DOkQPzqB9RufZOpfSP4LZZCErP0N0Xo=",
-                              "s6pVT2xPwktor9O5bVOSzcPqBu9uzQOUzPQHXLU2jmk="};
+                               "s6pVT2xPwktor9O5bVOSzcPqBu9uzQOUzPQHXLU2jmk="};
     const KeyPair keys[100] = {
         {"+BDHMh11bkheGfvlQpqt8P/H7N1sPXtVi05XraZS0E8=",
          "QItu7PJadBVXFXGv55CMtVnbRHdrI6E2CGlu2N5oGx4=",
diff --git a/src/core/platform/tests/test-nmp-object.c b/src/core/platform/tests/test-nmp-object.c
index ddd5aa93..193e750f 100644
--- a/src/core/platform/tests/test-nmp-object.c
+++ b/src/core/platform/tests/test-nmp-object.c
@@ -551,7 +551,9 @@ test_cache_qdisc(void)
     g_assert(nmp_cache_lookup_obj(cache, obj1b) == obj1b);
     g_assert(nmp_cache_lookup_obj(cache, obj2) == obj2);
 
-    head_entry = nmp_cache_lookup(cache, nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_QDISC, 1));
+    head_entry =
+        nmp_cache_lookup(cache,
+                         nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_QDISC, 1));
     g_assert(head_entry->len == 2);
 
     nmp_cache_free(cache);
diff --git a/src/core/platform/tests/test-platform-general.c b/src/core/platform/tests/test-platform-general.c
index 908ad4dd..c18886c9 100644
--- a/src/core/platform/tests/test-platform-general.c
+++ b/src/core/platform/tests/test-platform-general.c
@@ -788,6 +788,64 @@ test_route_type_is_nodev(void)
 
 /*****************************************************************************/
 
+static void
+test_nmp_genl_family_type_from_name(void)
+{
+    int n_run;
+    int i;
+
+    for (i = 0; i < (int) _NMP_GENL_FAMILY_TYPE_NUM; i++) {
+        const char *name = nmp_genl_family_infos[i].name;
+
+        g_assert(name);
+        if (i > 0)
+            g_assert_cmpint(strcmp(nmp_genl_family_infos[i - 1].name, name), <, 0);
+    }
+
+    g_assert_cmpint(nmp_genl_family_type_from_name("ethtool"), ==, NMP_GENL_FAMILY_TYPE_ETHTOOL);
+    g_assert_cmpint(nmp_genl_family_type_from_name("mptcp_pm"), ==, NMP_GENL_FAMILY_TYPE_MPTCP_PM);
+    g_assert_cmpint(nmp_genl_family_type_from_name("nl80211"), ==, NMP_GENL_FAMILY_TYPE_NL80211);
+    g_assert_cmpint(nmp_genl_family_type_from_name("nl802154"), ==, NMP_GENL_FAMILY_TYPE_NL802154);
+    g_assert_cmpint(nmp_genl_family_type_from_name("wireguard"),
+                    ==,
+                    NMP_GENL_FAMILY_TYPE_WIREGUARD);
+
+    g_assert_cmpint(nmp_genl_family_type_from_name(NULL), ==, _NMP_GENL_FAMILY_TYPE_NONE);
+    g_assert_cmpint(nmp_genl_family_type_from_name("a"), ==, _NMP_GENL_FAMILY_TYPE_NONE);
+    g_assert_cmpint(nmp_genl_family_type_from_name("wireguara"), ==, _NMP_GENL_FAMILY_TYPE_NONE);
+    g_assert_cmpint(nmp_genl_family_type_from_name("wireguarb"), ==, _NMP_GENL_FAMILY_TYPE_NONE);
+    g_assert_cmpint(nmp_genl_family_type_from_name(""), ==, _NMP_GENL_FAMILY_TYPE_NONE);
+    g_assert_cmpint(nmp_genl_family_type_from_name("z"), ==, _NMP_GENL_FAMILY_TYPE_NONE);
+
+    for (n_run = 0; n_run < 20; n_run++) {
+        for (i = 0; i < (int) _NMP_GENL_FAMILY_TYPE_NUM; i++) {
+            const char *cname  = nmp_genl_family_infos[i].name;
+            const int   ch_idx = nmtst_get_rand_uint() % strlen(cname);
+            char        name[200];
+            char        ch;
+            gsize       l;
+
+            l = g_strlcpy(name, cname, sizeof(name));
+            g_assert_cmpint(l, <, sizeof(name));
+
+            if (n_run == 0) {
+                g_assert_cmpint(nmp_genl_family_type_from_name(cname), ==, i);
+                g_assert_cmpint(nmp_genl_family_type_from_name(name), ==, i);
+            }
+
+            /* randomly change one character in the name. Such a name becomes invalid.
+             * There are no two valid names which only differ by one characters. */
+            do {
+                ch = nmtst_get_rand_uint() % 256;
+            } while (cname[ch_idx] == ch);
+            name[ch_idx] = ch;
+            g_assert_cmpint(nmp_genl_family_type_from_name(name), ==, _NMP_GENL_FAMILY_TYPE_NONE);
+        }
+    }
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -808,6 +866,8 @@ main(int argc, char **argv)
                          GINT_TO_POINTER(2),
                          test_platform_ip_address_pretty_sort_cmp);
     g_test_add_func("/general/test_route_type_is_nodev", test_route_type_is_nodev);
+    g_test_add_func("/nm-platform/test_nmp_genl_family_type_from_name",
+                    test_nmp_genl_family_type_from_name);
 
     return g_test_run();
 }
diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c
index 85838267..148ed792 100644
--- a/src/core/platform/tests/test-route.c
+++ b/src/core/platform/tests/test-route.c
@@ -10,7 +10,7 @@
 
 #include "nm-core-utils.h"
 #include "libnm-platform/nm-platform-utils.h"
-#include "libnm-platform/nmp-route-manager.h"
+#include "libnm-platform/nmp-global-tracker.h"
 
 #include "test-common.h"
 
@@ -35,8 +35,9 @@ _wait_for_ipv4_addr_device_route(NMPlatform *platform,
 
         nmp_cache_iter_for_each (
             &iter,
-            nm_platform_lookup(platform,
-                               nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex)),
+            nm_platform_lookup(
+                platform,
+                nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex)),
             &o) {
             const NMPlatformIP4Route *r = NMP_OBJECT_CAST_IP4_ROUTE(o);
 
@@ -646,6 +647,9 @@ test_ip4_route_options(gconstpointer test_data)
             .initrwnd  = 50,
             .mtu       = 1350,
             .lock_cwnd = TRUE,
+            .mss       = 1300,
+            .quickack  = TRUE,
+            .rto_min   = 1000,
         });
         break;
     case 2:
@@ -1167,7 +1171,7 @@ _rule_check_kernel_support(NMPlatform *platform, int attribute)
                  .addr_family = AF_INET,
                  .priority    = PROBE_PRORITY,
                  .uid_range =
-                {
+                     {
                          .start = 0,
                          .end   = 0,
                 },
@@ -1643,8 +1647,8 @@ again:
 
     if (TEST_SYNC) {
         gs_unref_hashtable GHashTable *unique_priorities = g_hash_table_new(NULL, NULL);
-        nm_auto_unref_route_manager NMPRouteManager *route_manager =
-            nmp_route_manager_new(platform);
+        nm_auto_unref_global_tracker NMPGlobalTracker *global_tracker =
+            nmp_global_tracker_new(platform);
         gs_unref_ptrarray GPtrArray *objs_sync  = NULL;
         gconstpointer                USER_TAG_1 = &platform;
         gconstpointer                USER_TAG_2 = &unique_priorities;
@@ -1666,29 +1670,29 @@ again:
         }
 
         for (i = 0; i < objs_sync->len; i++) {
-            nmp_route_manager_track_rule(route_manager,
-                                         NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
-                                         1,
-                                         USER_TAG_1,
-                                         NULL);
+            nmp_global_tracker_track_rule(global_tracker,
+                                          NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
+                                          1,
+                                          USER_TAG_1,
+                                          NULL);
             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_route_manager_track_rule(route_manager,
-                                             NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
-                                             -1,
-                                             USER_TAG_2,
-                                             NULL);
+                nmp_global_tracker_track_rule(global_tracker,
+                                              NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
+                                              -1,
+                                              USER_TAG_2,
+                                              NULL);
             }
             if (nmtst_get_rand_uint32() % objs_sync->len == 0) {
-                nmp_route_manager_sync(route_manager, NMP_OBJECT_TYPE_ROUTING_RULE, FALSE);
+                nmp_global_tracker_sync(global_tracker, NMP_OBJECT_TYPE_ROUTING_RULE, FALSE);
                 g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC),
                                 ==,
                                 i + 1);
             }
         }
 
-        nmp_route_manager_sync(route_manager, NMP_OBJECT_TYPE_ROUTING_RULE, FALSE);
+        nmp_global_tracker_sync(global_tracker, NMP_OBJECT_TYPE_ROUTING_RULE, FALSE);
         g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC),
                         ==,
                         objs_sync->len);
@@ -1696,37 +1700,37 @@ again:
         for (i = 0; i < objs_sync->len; i++) {
             switch (nmtst_get_rand_uint32() % 3) {
             case 0:
-                nmp_route_manager_untrack_rule(route_manager,
-                                               NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
-                                               USER_TAG_1);
-                nmp_route_manager_untrack_rule(route_manager,
-                                               NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
-                                               USER_TAG_1);
+                nmp_global_tracker_untrack_rule(global_tracker,
+                                                NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
+                                                USER_TAG_1);
+                nmp_global_tracker_untrack_rule(global_tracker,
+                                                NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
+                                                USER_TAG_1);
                 break;
             case 1:
-                nmp_route_manager_track_rule(route_manager,
-                                             NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
-                                             -1,
-                                             USER_TAG_1,
-                                             NULL);
+                nmp_global_tracker_track_rule(global_tracker,
+                                              NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
+                                              -1,
+                                              USER_TAG_1,
+                                              NULL);
                 break;
             case 2:
-                nmp_route_manager_track_rule(route_manager,
-                                             NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
-                                             -2,
-                                             USER_TAG_2,
-                                             NULL);
+                nmp_global_tracker_track_rule(global_tracker,
+                                              NMP_OBJECT_CAST_ROUTING_RULE(objs_sync->pdata[i]),
+                                              -2,
+                                              USER_TAG_2,
+                                              NULL);
                 break;
             }
             if (nmtst_get_rand_uint32() % objs_sync->len == 0) {
-                nmp_route_manager_sync(route_manager, NMP_OBJECT_TYPE_ROUTING_RULE, FALSE);
+                nmp_global_tracker_sync(global_tracker, NMP_OBJECT_TYPE_ROUTING_RULE, FALSE);
                 g_assert_cmpint(nmtstp_platform_routing_rules_get_count(platform, AF_UNSPEC),
                                 ==,
                                 objs_sync->len - i - 1);
             }
         }
 
-        nmp_route_manager_sync(route_manager, NMP_OBJECT_TYPE_ROUTING_RULE, FALSE);
+        nmp_global_tracker_sync(global_tracker, NMP_OBJECT_TYPE_ROUTING_RULE, FALSE);
 
     } else {
         for (i = 0; i < objs->len;) {
@@ -1957,6 +1961,200 @@ test_blackhole(gconstpointer test_data)
 
 /*****************************************************************************/
 
+static gboolean
+_mptcp_has_permissions(void)
+{
+    static int has_permissions = -1;
+    int        p;
+
+    /* We create a new netns for testing, where we also have CAP_NET_ADMIN.
+     * However, that is not enough for configuring MPTCP endpoints. Probably
+     * you can only create them, by running the test as root. Detect the
+     * inability, to skip the test.
+     *
+     * See https://lore.kernel.org/mptcp/20220805115020.525181-1-thaller@redhat.com/T/#u */
+
+again:
+    p = g_atomic_int_get(&has_permissions);
+
+    if (p == -1) {
+        static gsize              lock;
+        const NMPlatformMptcpAddr mptcp_addr = (NMPlatformMptcpAddr){
+            .id          = 1,
+            .addr_family = AF_INET,
+            .addr.addr4  = nmtst_inet4_from_string("1.2.3.4"),
+        };
+        int r;
+
+        if (!g_once_init_enter(&lock))
+            goto again;
+
+        if (nmtst_get_rand_one_case_in(3)) {
+            gs_unref_ptrarray GPtrArray *arr = NULL;
+
+            arr = nm_platform_mptcp_addrs_dump(NM_PLATFORM_GET);
+            g_assert_cmpint(nm_g_ptr_array_len(arr), ==, 0);
+        }
+
+        r = nm_platform_mptcp_addr_update(NM_PLATFORM_GET, TRUE, &mptcp_addr);
+        if (r == 0)
+            p = TRUE;
+        else if (r == -EPERM)
+            p = FALSE;
+        else
+            g_assert_cmpint(r, ==, 0);
+
+        if (p) {
+            if (nmtst_get_rand_one_case_in(3)) {
+                gs_unref_ptrarray GPtrArray *arr = NULL;
+
+                arr = nm_platform_mptcp_addrs_dump(NM_PLATFORM_GET);
+                g_assert_cmpint(nm_g_ptr_array_len(arr), ==, 1);
+            }
+
+            r = nm_platform_mptcp_addr_update(NM_PLATFORM_GET, FALSE, &mptcp_addr);
+            g_assert_cmpint(r, ==, 0);
+        }
+
+        if (nmtst_get_rand_one_case_in(3)) {
+            gs_unref_ptrarray GPtrArray *arr = NULL;
+
+            arr = nm_platform_mptcp_addrs_dump(NM_PLATFORM_GET);
+            g_assert_cmpint(nm_g_ptr_array_len(arr), ==, 0);
+        }
+
+        g_atomic_int_set(&has_permissions, p);
+        g_once_init_leave(&lock, 1);
+    }
+
+    return p;
+}
+
+static gboolean
+_mptcp_skip_test(void)
+{
+    if (nm_platform_genl_get_family_id(NM_PLATFORM_GET, NMP_GENL_FAMILY_TYPE_MPTCP_PM) == 0) {
+        g_test_skip("mptcp not available");
+        return TRUE;
+    }
+
+    if (!_mptcp_has_permissions()) {
+        g_test_skip("No permissions to create MPTCP endpoints");
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void
+test_mptcp(gconstpointer test_data)
+{
+    const int                                      TEST_IDX = GPOINTER_TO_INT(test_data);
+    gs_unref_object NMPlatform                    *platform = g_object_ref(NM_PLATFORM_GET);
+    nm_auto_unref_global_tracker NMPGlobalTracker *global_tracker =
+        nmp_global_tracker_new(platform);
+    gconstpointer const          USER_TAG = &TEST_IDX;
+    const int                    IFINDEX  = nm_platform_link_get_ifindex(platform, DEVICE_NAME);
+    guint                        i;
+    guint                        j;
+    int                          r;
+    gs_unref_ptrarray GPtrArray *arr_external =
+        g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+    gs_unref_ptrarray GPtrArray *arr_tracked =
+        g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+    const NMPObject *obj;
+    gboolean         delete_extra;
+
+    g_assert_cmpint(IFINDEX, >, 0);
+
+    if (_mptcp_skip_test())
+        return;
+
+    j = nmtst_get_rand_uint32() % 5;
+    for (i = 0; i < j; i++) {
+        obj = nmtst_object_new_mptcp_addr(.id          = i + 1,
+                                          .ifindex     = IFINDEX,
+                                          .addr_family = AF_INET,
+                                          .addr.addr4  = htonl(0xC0A80001u + i));
+        g_ptr_array_add(arr_external, (gpointer) obj);
+        r = nm_platform_mptcp_addr_update(platform, TRUE, NMP_OBJECT_CAST_MPTCP_ADDR(obj));
+        g_assert_cmpint(r, ==, 0);
+    }
+
+    j = nmtst_get_rand_uint32() % 10;
+    for (i = 0; i < j; i++) {
+        obj = nmtst_object_new_mptcp_addr(.ifindex     = IFINDEX,
+                                          .addr_family = AF_INET,
+                                          .addr.addr4  = htonl(0xC0A80001u + i));
+        g_ptr_array_add(arr_tracked, (gpointer) obj);
+        nmp_global_tracker_track(global_tracker,
+                                 NMP_OBJECT_TYPE_MPTCP_ADDR,
+                                 NMP_OBJECT_CAST_MPTCP_ADDR(obj),
+                                 20 - i,
+                                 USER_TAG,
+                                 NULL);
+    }
+    for (i = 0; i < arr_tracked->len;) {
+        if (nmtst_get_rand_bool()) {
+            nmp_global_tracker_untrack(global_tracker,
+                                       NMP_OBJECT_TYPE_MPTCP_ADDR,
+                                       NMP_OBJECT_CAST_MPTCP_ADDR(arr_tracked->pdata[i]),
+                                       USER_TAG);
+            g_ptr_array_remove_index(arr_tracked, i);
+        } else
+            i++;
+    }
+
+    if (arr_tracked->len == 0 || nmtst_get_rand_bool()) {
+        NMPlatformMptcpAddr a;
+
+        /* Track a dummy object that marks the ifindex as managed. */
+        nmp_global_tracker_track(global_tracker,
+                                 NMP_OBJECT_TYPE_MPTCP_ADDR,
+                                 nmp_global_tracker_mptcp_addr_init_for_ifindex(&a, IFINDEX),
+                                 10,
+                                 USER_TAG,
+                                 NULL);
+    }
+
+    nmp_global_tracker_sync_mptcp_addrs(global_tracker, FALSE);
+
+    if (nmtst_get_rand_bool()) {
+        gboolean reapply;
+
+        nmp_global_tracker_untrack_all(global_tracker, USER_TAG, TRUE, FALSE);
+        reapply = nmtst_get_rand_bool();
+        nmp_global_tracker_sync_mptcp_addrs(global_tracker, reapply);
+
+        delete_extra = !reapply;
+    } else
+        delete_extra = TRUE;
+
+    if (delete_extra) {
+        gs_unref_ptrarray GPtrArray *arr = NULL;
+
+        /* We need to delete all MPTCP address again, because the next test uses the
+         * same netns (this test setup doesn't create a netns per test). */
+        arr = nm_platform_mptcp_addrs_dump(platform);
+        for (i = 0; i < nm_g_ptr_array_len(arr); i++) {
+            r = nm_platform_mptcp_addr_update(platform,
+                                              FALSE,
+                                              NMP_OBJECT_CAST_MPTCP_ADDR(arr->pdata[i]));
+            g_assert(NMTST_NM_ERR_SUCCESS(r));
+        }
+    }
+
+    {
+        gs_unref_ptrarray GPtrArray *arr = NULL;
+
+        arr = nm_platform_mptcp_addrs_dump(platform);
+        g_assert(arr);
+        g_assert_cmpint(arr->len, ==, 0);
+    }
+}
+
+/*****************************************************************************/
+
 NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
 
 void
@@ -1971,6 +2169,7 @@ _nmtstp_setup_tests(void)
 #define add_test_func(testpath, test_func) nmtstp_env1_add_test_func(testpath, test_func, TRUE)
 #define add_test_func_data(testpath, test_func, arg) \
     nmtstp_env1_add_test_func_data(testpath, test_func, arg, TRUE)
+
     add_test_func("/route/ip4", test_ip4_route);
     add_test_func("/route/ip6", test_ip6_route);
     add_test_func("/route/ip4_metric0", test_ip4_route_metric0);
@@ -1998,4 +2197,8 @@ _nmtstp_setup_tests(void)
         add_test_func_data("/route/blackhole/1", test_blackhole, GINT_TO_POINTER(1));
         add_test_func_data("/route/blackhole/2", test_blackhole, GINT_TO_POINTER(2));
     }
+    if (nmtstp_is_root_test()) {
+        add_test_func_data("/route/mptcp/1", test_mptcp, GINT_TO_POINTER(1));
+        add_test_func_data("/route/mptcp/2", test_mptcp, GINT_TO_POINTER(2));
+    }
 }
diff --git a/src/core/platform/tests/test-tc.c b/src/core/platform/tests/test-tc.c
index db23a932..6a2019c8 100644
--- a/src/core/platform/tests/test-tc.c
+++ b/src/core/platform/tests/test-tc.c
@@ -30,10 +30,11 @@ qdiscs_lookup(int ifindex)
 {
     NMPLookup lookup;
 
-    return nm_platform_lookup_clone(NM_PLATFORM_GET,
-                                    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_QDISC, ifindex),
-                                    NULL,
-                                    NULL);
+    return nm_platform_lookup_clone(
+        NM_PLATFORM_GET,
+        nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_QDISC, ifindex),
+        NULL,
+        NULL);
 }
 
 static void