about summary refs log tree commit diff
path: root/src/tests/test-route-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests/test-route-manager.c')
-rw-r--r--src/tests/test-route-manager.c917
1 files changed, 917 insertions, 0 deletions
diff --git a/src/tests/test-route-manager.c b/src/tests/test-route-manager.c
new file mode 100644
index 00000000..0164930d
--- /dev/null
+++ b/src/tests/test-route-manager.c
@@ -0,0 +1,917 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <arpa/inet.h>
+#include <linux/rtnetlink.h>
+
+#include "test-common.h"
+
+#include "nm-platform.h"
+#include "nm-route-manager.h"
+#include "nm-logging.h"
+
+#include "nm-test-utils.h"
+
+typedef struct {
+	int ifindex0, ifindex1;
+} test_fixture;
+
+/*****************************************************************************/
+
+static void
+setup_dev0_ip4 (int ifindex, guint mss_of_first_route, guint32 metric_of_second_route)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
+	NMPlatformIP4Route route = { 0 };
+
+	route.ifindex = ifindex;
+	route.mss = 0;
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "6.6.6.0", &route.network);
+	route.plen = 24;
+	route.gateway = INADDR_ANY;
+	route.metric = 20;
+	route.mss = mss_of_first_route;
+	g_array_append_val (routes, route);
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "7.0.0.0", &route.network);
+	route.plen = 8;
+	inet_pton (AF_INET, "6.6.6.1", &route.gateway);
+	route.metric = metric_of_second_route;
+	route.mss = 0;
+	g_array_append_val (routes, route);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+static void
+setup_dev1_ip4 (int ifindex)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
+	NMPlatformIP4Route route = { 0 };
+
+	route.ifindex = ifindex;
+	route.mss = 0;
+
+	/* Add some route outside of route manager. The route manager
+	 * should get rid of it upon sync. */
+	nm_platform_ip4_route_add (NM_PLATFORM_GET,
+	                           route.ifindex,
+	                           NM_IP_CONFIG_SOURCE_USER,
+	                           nmtst_inet4_from_string ("9.0.0.0"),
+	                           8,
+	                           INADDR_ANY,
+	                           0,
+	                           10,
+	                           route.mss);
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "6.6.6.0", &route.network);
+	route.plen = 24;
+	route.gateway = INADDR_ANY;
+	route.metric = 20;
+	g_array_append_val (routes, route);
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "7.0.0.0", &route.network);
+	route.plen = 8;
+	route.gateway = INADDR_ANY;
+	route.metric = 22;
+	g_array_append_val (routes, route);
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "8.0.0.0", &route.network);
+	route.plen = 8;
+	inet_pton (AF_INET, "6.6.6.2", &route.gateway);
+	route.metric = 22;
+	g_array_append_val (routes, route);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+static void
+update_dev0_ip4 (int ifindex)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
+	NMPlatformIP4Route route = { 0 };
+
+	route.ifindex = ifindex;
+	route.mss = 0;
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "6.6.6.0", &route.network);
+	route.plen = 24;
+	route.gateway = INADDR_ANY;
+	route.metric = 20;
+	g_array_append_val (routes, route);
+
+	route.source = NM_IP_CONFIG_SOURCE_USER;
+	inet_pton (AF_INET, "7.0.0.0", &route.network);
+	route.plen = 8;
+	route.gateway = INADDR_ANY;
+	route.metric = 21;
+	g_array_append_val (routes, route);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+
+static GArray *
+ip4_routes (test_fixture *fixture)
+{
+	GArray *routes = nm_platform_ip4_route_get_all (NM_PLATFORM_GET,
+	                                                fixture->ifindex0,
+	                                                NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	GArray *routes1 = nm_platform_ip4_route_get_all (NM_PLATFORM_GET,
+	                                                 fixture->ifindex1,
+	                                                 NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+
+	g_array_append_vals (routes, routes1->data, routes1->len);
+	g_array_free (routes1, TRUE);
+
+	return routes;
+}
+
+static void
+test_ip4 (test_fixture *fixture, gconstpointer user_data)
+{
+	GArray *routes;
+
+	NMPlatformIP4Route state1[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex0,
+			.gateway = INADDR_ANY,
+			.metric = 20,
+			.mss = 1000,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("7.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex0,
+			.gateway = nmtst_inet4_from_string ("6.6.6.1"),
+			.metric = 21021,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("7.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 21,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("8.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = nmtst_inet4_from_string ("6.6.6.2"),
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
+		},
+	};
+
+	NMPlatformIP4Route state2[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex0,
+			.gateway = INADDR_ANY,
+			.metric = 20,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("7.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex0,
+			.gateway = INADDR_ANY,
+			.metric = 21,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("7.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 21,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("8.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = nmtst_inet4_from_string ("6.6.6.2"),
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
+		},
+	};
+
+	NMPlatformIP4Route state3[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("7.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			.metric = 20,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("8.0.0.0"),
+			.plen = 8,
+			.ifindex = fixture->ifindex1,
+			.gateway = nmtst_inet4_from_string ("6.6.6.2"),
+			.metric = 22,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_UNIVERSE),
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = nmtst_inet4_from_string ("6.6.6.0"),
+			.plen = 24,
+			.ifindex = fixture->ifindex1,
+			.gateway = INADDR_ANY,
+			/* this is a ghost entry because we synced ifindex0 and restore the route
+			 * with metric 20 (above). But we don't remove the metric 21. */
+			.metric = 21,
+			.mss = 0,
+			.scope_inv = nm_platform_route_scope_inv (RT_SCOPE_LINK),
+		},
+	};
+
+	setup_dev0_ip4 (fixture->ifindex0, 1000, 21021);
+	setup_dev1_ip4 (fixture->ifindex1);
+	g_test_assert_expected_messages ();
+
+	/* - 6.6.6.0/24 on dev0 won over 6.6.6.0/24 on dev1
+	 * - 6.6.6.0/24 on dev1 has metric bumped.
+	 * - 7.0.0.0/8 route, metric 21021 added
+	 * - 7.0.0.0/8 route, metric 22 added
+	 * - 8.0.0.0/8 could be added. */
+	routes = ip4_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
+	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state1, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	setup_dev1_ip4 (fixture->ifindex1);
+	g_test_assert_expected_messages ();
+
+	setup_dev0_ip4 (fixture->ifindex0, 0, 21);
+
+	/* Ensure nothing changed. */
+	routes = ip4_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
+	state1[0].mss = 0;
+	state1[1].metric = 21;
+	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state1, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	update_dev0_ip4 (fixture->ifindex0);
+
+	/* minor changes in the routes. Quite similar to state1. */
+	routes = ip4_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state2));
+	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state2, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	nm_route_manager_route_flush (nm_route_manager_get (), fixture->ifindex0);
+
+	/* 6.6.6.0/24 is now on dev1
+	 * 6.6.6.0/24 is also still on dev1 with bumped metric 21.
+	 * 7.0.0.0/8 gone from dev0, still present on dev1
+	 * 8.0.0.0/8 is present on dev1
+	 * No dev0 routes left. */
+	routes = ip4_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state3));
+	nmtst_platform_ip4_routes_equal ((NMPlatformIP4Route *) routes->data, state3, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	nm_route_manager_route_flush (nm_route_manager_get (), fixture->ifindex1);
+
+	/* No routes left. */
+	routes = ip4_routes (fixture);
+	g_assert_cmpint (routes->len, ==, 0);
+	g_array_free (routes, TRUE);
+}
+
+static void
+setup_dev0_ip6 (int ifindex)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
+	NMPlatformIP6Route *route;
+
+	/* Add an address so that a route to the gateway below gets added. */
+	nm_platform_ip6_address_add (NM_PLATFORM_GET,
+	                             ifindex,
+	                             *nmtst_inet6_from_string ("2001:db8:8086::2"),
+	                             in6addr_any,
+	                             64,
+	                             3600,
+	                             3600,
+	                             0);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:8086::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       20,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:1337::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       0,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:abad:c0de::",
+	                                       64,
+	                                       "2001:db8:8086::1",
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       21,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	nm_route_manager_ip6_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+static void
+setup_dev1_ip6 (int ifindex)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
+	NMPlatformIP6Route *route;
+
+	/* Add some route outside of route manager. The route manager
+	 * should get rid of it upon sync. */
+	nm_platform_ip6_route_add (NM_PLATFORM_GET,
+	                           ifindex,
+	                           NM_IP_CONFIG_SOURCE_USER,
+	                           *nmtst_inet6_from_string ("2001:db8:8088::"),
+	                           48,
+	                           in6addr_any,
+	                           10,
+	                           0);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:8086::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       20,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:1337::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       1024,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:d34d::",
+	                                       64,
+	                                       "2001:db8:8086::2",
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       20,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:abad:c0de::",
+	                                       64,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       22,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	nm_route_manager_ip6_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+static void
+update_dev0_ip6 (int ifindex)
+{
+	GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
+	NMPlatformIP6Route *route;
+
+	/* Add an address so that a route to the gateway below gets added. */
+	nm_platform_ip6_address_add (NM_PLATFORM_GET,
+	                             ifindex,
+	                             *nmtst_inet6_from_string ("2001:db8:8086::2"),
+	                             in6addr_any,
+	                             64,
+	                             3600,
+	                             3600,
+	                             0);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:8086::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       20,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:1337::",
+	                                       48,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       0,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	route = nmtst_platform_ip6_route_full ("2001:db8:abad:c0de::",
+	                                       64,
+	                                       NULL,
+	                                       ifindex,
+	                                       NM_IP_CONFIG_SOURCE_USER,
+	                                       21,
+	                                       0);
+	g_array_append_val (routes, *route);
+
+	nm_route_manager_ip6_route_sync (nm_route_manager_get (), ifindex, routes, TRUE, TRUE);
+	g_array_free (routes, TRUE);
+}
+
+static GArray *
+ip6_routes (test_fixture *fixture)
+{
+	GArray *routes = nm_platform_ip6_route_get_all (NM_PLATFORM_GET,
+	                                                fixture->ifindex0,
+	                                                NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+	GArray *routes1 = nm_platform_ip6_route_get_all (NM_PLATFORM_GET,
+	                                                 fixture->ifindex1,
+	                                                 NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT);
+
+	g_array_append_vals (routes, routes1->data, routes1->len);
+	g_array_free (routes1, TRUE);
+
+	return routes;
+}
+
+static void
+test_ip6 (test_fixture *fixture, gconstpointer user_data)
+{
+	GArray *routes;
+
+	NMPlatformIP6Route state1[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex0,
+			.gateway = in6addr_any,
+			.metric = 20,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex0,
+			.gateway = in6addr_any,
+			.metric = 1024,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex0,
+			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::1"),
+			.metric = 21,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 22,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 1025,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 21,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:d34d::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::2"),
+			.metric = 20,
+			.mss = 0,
+		},
+	};
+
+	NMPlatformIP6Route state2[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex0,
+			.gateway = in6addr_any,
+			.metric = 20,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex0,
+			.gateway = in6addr_any,
+			.metric = 1024,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex0,
+			.gateway = in6addr_any,
+			.metric = 21,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 22,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 1025,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 21,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:d34d::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::2"),
+			.metric = 20,
+			.mss = 0,
+		},
+	};
+
+	NMPlatformIP6Route state3[] = {
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:abad:c0de::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 22,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 20,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 1024,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:1337::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 1025,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:8086::"),
+			.plen = 48,
+			.ifindex = fixture->ifindex1,
+			.gateway = in6addr_any,
+			.metric = 21,
+			.mss = 0,
+		},
+		{
+			.source = NM_IP_CONFIG_SOURCE_USER,
+			.network = *nmtst_inet6_from_string ("2001:db8:d34d::"),
+			.plen = 64,
+			.ifindex = fixture->ifindex1,
+			.gateway = *nmtst_inet6_from_string ("2001:db8:8086::2"),
+			.metric = 20,
+			.mss = 0,
+		},
+	};
+
+	setup_dev0_ip6 (fixture->ifindex0);
+	setup_dev1_ip6 (fixture->ifindex1);
+	g_test_assert_expected_messages ();
+
+	/* 2001:db8:8086::/48 on dev0 won over 2001:db8:8086::/48 on dev1
+	 * 2001:db8:d34d::/64 on dev1 could not be added
+	 * 2001:db8:1337::/48 on dev0 won over 2001:db8:1337::/48 on dev1 and has metric 1024
+	 * 2001:db8:abad:c0de::/64 routes did not clash */
+	routes = ip6_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
+	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state1, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+
+	setup_dev1_ip6 (fixture->ifindex1);
+	g_test_assert_expected_messages ();
+	setup_dev0_ip6 (fixture->ifindex0);
+
+	/* Ensure nothing changed. */
+	routes = ip6_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state1));
+	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state1, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	update_dev0_ip6 (fixture->ifindex0);
+
+	/* 2001:db8:abad:c0de::/64 on dev0 was updated for gateway removal*/
+	routes = ip6_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state2));
+	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state2, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	nm_route_manager_route_flush (nm_route_manager_get (), fixture->ifindex0);
+
+	/* 2001:db8:abad:c0de::/64 on dev1 is still there, went away from dev0
+	 * 2001:db8:8086::/48 is now on dev1
+	 * 2001:db8:1337::/48 is now on dev1, metric of 1024 still applies
+	 * 2001:db8:d34d::/64 is present now that 2001:db8:8086::/48 is on dev1
+	 * No dev0 routes left. */
+	routes = ip6_routes (fixture);
+	g_assert_cmpint (routes->len, ==, G_N_ELEMENTS (state3));
+	nmtst_platform_ip6_routes_equal ((NMPlatformIP6Route *) routes->data, state3, routes->len, TRUE);
+	g_array_free (routes, TRUE);
+
+	nm_route_manager_route_flush (nm_route_manager_get (), fixture->ifindex1);
+
+	/* No routes left. */
+	routes = ip6_routes (fixture);
+	g_assert_cmpint (routes->len, ==, 0);
+	g_array_free (routes, TRUE);
+}
+
+/*****************************************************************************/
+
+static void
+_assert_route_check (const NMPlatformVTableRoute *vtable, gboolean has, const NMPlatformIPXRoute *route)
+{
+	const NMPlatformIPXRoute *r;
+
+	g_assert (route);
+
+	if (vtable->is_ip4)
+		r = (const NMPlatformIPXRoute *) nm_platform_ip4_route_get (NM_PLATFORM_GET, route->rx.ifindex, route->r4.network, route->rx.plen, route->rx.metric);
+	else
+		r = (const NMPlatformIPXRoute *) nm_platform_ip6_route_get (NM_PLATFORM_GET, route->rx.ifindex, route->r6.network, route->rx.plen, route->rx.metric);
+
+	if (!has) {
+		g_assert (!r);
+	} else {
+		if (!r || vtable->route_cmp (route, r) != 0)
+			g_error ("Invalid route. Expect %s, has %s",
+			         nmtst_static_1024_01 (vtable->route_to_string (route)),
+			         nmtst_static_1024_02 (vtable->route_to_string (r)));
+		g_assert (r);
+	}
+}
+
+static void
+test_ip4_full_sync (test_fixture *fixture, gconstpointer user_data)
+{
+	const NMPlatformVTableRoute *vtable = &nm_platform_vtable_route_v4;
+	gs_unref_array GArray *routes = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
+	NMPlatformIP4Route r01, r02, r03;
+
+	nm_log_dbg (LOGD_CORE, "TEST start test_ip4_full_sync(): start");
+
+	r01 = *nmtst_platform_ip4_route_full ("12.3.4.0", 24, NULL,
+	                                      fixture->ifindex0, NM_IP_CONFIG_SOURCE_USER,
+	                                      100, 0, RT_SCOPE_LINK, NULL);
+	r02 = *nmtst_platform_ip4_route_full ("13.4.5.6", 32, "12.3.4.1",
+	                                      fixture->ifindex0, NM_IP_CONFIG_SOURCE_USER,
+	                                      100, 0, RT_SCOPE_UNIVERSE, NULL);
+	r03 = *nmtst_platform_ip4_route_full ("14.5.6.7", 32, "12.3.4.1",
+	                                      fixture->ifindex0, NM_IP_CONFIG_SOURCE_USER,
+	                                      110, 0, RT_SCOPE_UNIVERSE, NULL);
+	g_array_set_size (routes, 2);
+	g_array_index (routes, NMPlatformIP4Route, 0) = r01;
+	g_array_index (routes, NMPlatformIP4Route, 1) = r02;
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), fixture->ifindex0, routes, TRUE, TRUE);
+
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r02);
+	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r03);
+
+	vtable->route_add (NM_PLATFORM_GET, 0, (const NMPlatformIPXRoute *) &r03, -1);
+
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r02);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r03);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), fixture->ifindex0, routes, TRUE, FALSE);
+
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r02);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r03);
+
+	g_array_set_size (routes, 1);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), fixture->ifindex0, routes, TRUE, FALSE);
+
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
+	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r02);
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r03);
+
+	nm_route_manager_ip4_route_sync (nm_route_manager_get (), fixture->ifindex0, routes, TRUE, TRUE);
+
+	_assert_route_check (vtable, TRUE,  (const NMPlatformIPXRoute *) &r01);
+	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r02);
+	_assert_route_check (vtable, FALSE, (const NMPlatformIPXRoute *) &r03);
+
+	nm_log_dbg (LOGD_CORE, "TEST test_ip4_full_sync(): done");
+}
+
+/*****************************************************************************/
+
+static void
+fixture_setup (test_fixture *fixture, gconstpointer user_data)
+{
+	SignalData *link_added;
+
+	link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED,
+	                                NM_PLATFORM_SIGNAL_ADDED,
+	                                link_callback,
+	                                "nm-test-device0");
+	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device0"));
+	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, "nm-test-device0", NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	accept_signal (link_added);
+	free_signal (link_added);
+	fixture->ifindex0 = nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device0");
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex0, NULL));
+
+	link_added = add_signal_ifname (NM_PLATFORM_SIGNAL_LINK_CHANGED,
+	                                NM_PLATFORM_SIGNAL_ADDED,
+	                                link_callback,
+	                                "nm-test-device1");
+	nm_platform_link_delete (NM_PLATFORM_GET, nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device1"));
+	g_assert (nm_platform_dummy_add (NM_PLATFORM_GET, "nm-test-device1", NULL) == NM_PLATFORM_ERROR_SUCCESS);
+	accept_signal (link_added);
+	free_signal (link_added);
+	fixture->ifindex1 = nm_platform_link_get_ifindex (NM_PLATFORM_GET, "nm-test-device1");
+	g_assert (nm_platform_link_set_up (NM_PLATFORM_GET, fixture->ifindex1, NULL));
+}
+
+static void
+fixture_teardown (test_fixture *fixture, gconstpointer user_data)
+{
+	nm_platform_link_delete (NM_PLATFORM_GET, fixture->ifindex0);
+	nm_platform_link_delete (NM_PLATFORM_GET, fixture->ifindex1);
+}
+
+/*****************************************************************************/
+
+void
+init_tests (int *argc, char ***argv)
+{
+	nmtst_init_assert_logging (argc, argv, "WARN", "ALL");
+}
+
+void
+setup_tests (void)
+{
+	g_test_add ("/route-manager/ip4", test_fixture, NULL, fixture_setup, test_ip4, fixture_teardown);
+	g_test_add ("/route-manager/ip6", test_fixture, NULL, fixture_setup, test_ip6, fixture_teardown);
+
+	g_test_add ("/route-manager/ip4-full-sync", test_fixture, NULL, fixture_setup, test_ip4_full_sync, fixture_teardown);
+}