summary refs log tree commit diff
path: root/src/nm-default-route-manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nm-default-route-manager.c')
-rw-r--r--src/nm-default-route-manager.c1611
1 files changed, 0 insertions, 1611 deletions
diff --git a/src/nm-default-route-manager.c b/src/nm-default-route-manager.c
deleted file mode 100644
index 1ab8f02d..00000000
--- a/src/nm-default-route-manager.c
+++ /dev/null
@@ -1,1611 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
-/* NetworkManager -- Network link manager
- *
- * 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 of the License, 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) 2014 Red Hat, Inc.
- */
-
-
-#include "nm-default.h"
-
-#include "nm-default-route-manager.h"
-
-#include <string.h>
-
-#include "devices/nm-device.h"
-#include "vpn/nm-vpn-connection.h"
-#include "platform/nm-platform.h"
-#include "platform/nm-platform-utils.h"
-#include "nm-manager.h"
-#include "nm-ip4-config.h"
-#include "nm-ip6-config.h"
-#include "nm-act-request.h"
-
-/*****************************************************************************/
-
-NM_GOBJECT_PROPERTIES_DEFINE_BASE (
-	PROP_LOG_WITH_PTR,
-	PROP_PLATFORM,
-);
-
-typedef struct {
-	GPtrArray *entries_ip4;
-	GPtrArray *entries_ip6;
-
-	NMPlatform *platform;
-
-	struct {
-		guint guard;
-		guint backoff_wait_time_ms;
-		guint idle_handle;
-		gboolean has_v4_changes;
-		gboolean has_v6_changes;
-	} resync;
-
-	/* During disposing, we unref the sources of all entries. This happens usually
-	 * during shutdown, which might call the final deletion of the object. That
-	 * again might cause calls back into NMDefaultRouteManager, which finds dangling
-	 * pointers.
-	 * Guard every publicly accessible function to return early if the instance
-	 * is already disposing. */
-	bool disposed;
-
-	bool log_with_ptr;
-} NMDefaultRouteManagerPrivate;
-
-struct _NMDefaultRouteManager {
-	GObject parent;
-	NMDefaultRouteManagerPrivate _priv;
-};
-
-struct _NMDefaultRouteManagerClass {
-	GObjectClass parent;
-};
-
-G_DEFINE_TYPE (NMDefaultRouteManager, nm_default_route_manager, G_TYPE_OBJECT)
-
-#define NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDefaultRouteManager, NM_IS_DEFAULT_ROUTE_MANAGER)
-
-/*****************************************************************************/
-
-#define _NMLOG_PREFIX_NAME   "default-route"
-#undef  _NMLOG_ENABLED
-#define _NMLOG_ENABLED(level, addr_family) \
-    ({ \
-        const int __addr_family = (addr_family); \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
-        \
-        nm_logging_enabled (__level, __domain); \
-    })
-#define _NMLOG(level, addr_family, ...) \
-    G_STMT_START { \
-        const int __addr_family = (addr_family); \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
-        \
-        if (nm_logging_enabled (__level, __domain)) { \
-            char __prefix_buf[100]; \
-            \
-            _nm_log (__level, __domain, 0, NULL, NULL, \
-                     "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-                     NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self)->log_with_ptr \
-                        ? nm_sprintf_buf (__prefix_buf, "%s%c[%p]", \
-                                          _NMLOG2_PREFIX_NAME, \
-                                          __addr_family == AF_INET ? '4' : (__addr_family == AF_INET6 ? '6' : '-'), \
-                                          self) \
-                        : _NMLOG2_PREFIX_NAME \
-                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
-        } \
-    } G_STMT_END
-
-#define _NMLOG2_PREFIX_NAME   _NMLOG_PREFIX_NAME
-#undef  _NMLOG2_ENABLED
-#define _NMLOG2_ENABLED       _NMLOG_ENABLED
-#define _NMLOG2(level, vtable, entry_idx, entry, ...) \
-    G_STMT_START { \
-        const int __addr_family = (vtable)->vt->addr_family; \
-        const NMLogLevel __level = (level); \
-        const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
-        \
-        if (nm_logging_enabled (__level, __domain)) { \
-            char __prefix_buf[100]; \
-            guint __entry_idx = (entry_idx); \
-            const Entry *const __entry = (entry); \
-            \
-            _nm_log (__level, __domain, 0, NULL, NULL, \
-                     "%s: entry[%u/%s:%p:%s:%chas:%csync]: "_NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-                     NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self)->log_with_ptr \
-                        ? nm_sprintf_buf (__prefix_buf, "%s%c[%p]", \
-                                          _NMLOG2_PREFIX_NAME, \
-                                          __addr_family == AF_INET ? '4' : (__addr_family == AF_INET6 ? '6' : '-'), \
-                                          self) \
-                        : _NMLOG2_PREFIX_NAME, \
-                     __entry_idx, \
-                     NM_IS_DEVICE (__entry->source.pointer) ? "dev" : "vpn", \
-                     __entry->source.pointer, \
-                     NM_IS_DEVICE (__entry->source.pointer) ? nm_device_get_iface (__entry->source.device) : nm_active_connection_get_settings_connection_id (NM_ACTIVE_CONNECTION (__entry->source.vpn)), \
-                     (__entry->never_default ? '-' : '+'), \
-                     (__entry->synced ? '+' : '-') \
-                     _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
-        } \
-    } G_STMT_END
-
-/*****************************************************************************/
-
-static void _resync_idle_cancel (NMDefaultRouteManager *self);
-
-/*****************************************************************************/
-
-typedef struct {
-	union {
-		void *pointer;
-		GObject *object;
-		NMDevice *device;
-		NMVpnConnection *vpn;
-	} source;
-	NMPlatformIPXRoute route;
-
-	/* Whether the route is synced to platform and has a default route.
-	 *
-	 * ( synced && !never_default): the interface gets a default route that
-	 *     is enforced and managed by NMDefaultRouteManager.
-	 *
-	 * (!synced && !never_default): the interface has this route, but it is assumed.
-	 *     Assumed interfaces are those that have no tracked entry or that only have
-	 *     (!synced && !never_default) entries. NMDefaultRouteManager will not touch
-	 *     default routes on these interfaces.
-	 *     This combination makes only sense for device sources.
-	 *     They are tracked so that assumed devices can also be the best device.
-	 *
-	 * ( synced &&  never_default): entries of this kind are a placeholder
-	 *     to indicate that the ifindex is managed but has no default-route.
-	 *     Missing entries also indicate that a certain ifindex has no default-route.
-	 *     The difference is that missing entries are considered assumed while on
-	 *     (synced && never_default) entries the absence of the default route
-	 *     is enforced. NMDefaultRouteManager will actively remove any default
-	 *     route on such ifindexes.
-	 *     Also, for VPN sources in addition we track them so that a never-default
-	 *     VPN connection can be choosen by get_best_config() to receive the DNS configuration.
-	 *
-	 * (!synced &&  never_default): this combination makes no sense.
-	 */
-	gboolean synced;
-	gboolean never_default;
-
-	guint32 effective_metric;
-} Entry;
-
-typedef struct {
-	const NMPlatformVTableRoute *vt;
-	GPtrArray *(*get_entries) (NMDefaultRouteManagerPrivate *priv);
-} VTableIP;
-
-static const VTableIP vtable_ip4, vtable_ip6;
-
-static NMPlatformIPRoute *
-_vt_route_index (const VTableIP *vtable, GArray *routes, guint index)
-{
-	if (vtable->vt->is_ip4)
-		return (NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP4Route, index);
-	else
-		return (NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP6Route, index);
-}
-
-static gboolean
-_vt_routes_has_entry (const VTableIP *vtable, GArray *routes, const Entry *entry)
-{
-	guint i;
-	NMPlatformIPXRoute route = entry->route;
-
-	route.rx.metric = entry->effective_metric;
-
-	if (vtable->vt->is_ip4) {
-		for (i = 0; i < routes->len; i++) {
-			NMPlatformIP4Route *r = &g_array_index (routes, NMPlatformIP4Route, i);
-
-			route.rx.rt_source = r->rt_source;
-			if (nm_platform_ip4_route_cmp (r, &route.r4) == 0)
-				return TRUE;
-		}
-	} else {
-		for (i = 0; i < routes->len; i++) {
-			NMPlatformIP6Route *r = &g_array_index (routes, NMPlatformIP6Route, i);
-
-			route.rx.rt_source = r->rt_source;
-			if (nm_platform_ip6_route_cmp (r, &route.r6) == 0)
-				return TRUE;
-		}
-	}
-	return FALSE;
-}
-
-static void
-_entry_free (Entry *entry)
-{
-	if (entry) {
-		g_object_unref (entry->source.object);
-		g_slice_free (Entry, entry);
-	}
-}
-
-static Entry *
-_entry_find_by_source (GPtrArray *entries, gpointer source, guint *out_idx)
-{
-	guint i;
-
-	for (i = 0; i < entries->len; i++) {
-		Entry *e = g_ptr_array_index (entries, i);
-
-		if (e->source.pointer == source) {
-			if (out_idx)
-				*out_idx = i;
-			return e;
-		}
-	}
-
-	if (out_idx)
-		*out_idx = G_MAXUINT;
-	return NULL;
-}
-
-static gboolean
-_platform_route_sync_add (const VTableIP *vtable, NMDefaultRouteManager *self, guint32 metric)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	GPtrArray *entries = vtable->get_entries (priv);
-	char buf1[sizeof (_nm_utils_to_string_buffer)];
-	char buf2[sizeof (_nm_utils_to_string_buffer)];
-	guint i;
-	Entry *entry_unsynced = NULL;
-	Entry *entry = NULL;
-	gboolean success;
-
-	/* Find the entries for the given metric.
-	 * The effective metric for synced entries is choosen in a way that it
-	 * is unique (except for G_MAXUINT32, where a clash is not solvable). */
-	for (i = 0; i < entries->len; i++) {
-		Entry *e = g_ptr_array_index (entries, i);
-
-		if (e->never_default)
-			continue;
-
-		if (e->effective_metric != metric)
-			continue;
-
-		if (e->synced) {
-			g_assert (!entry || metric == G_MAXUINT32);
-			if (!entry)
-				entry = e;
-		} else
-			entry_unsynced = e;
-	}
-
-	/* We don't expect to have an unsynced *and* a synced entry for the same metric.
-	 * Unless, (a) their metric is G_MAXUINT32, in which case we could not find an unused effective metric,
-	 * or (b) if we have an unsynced and a synced entry for the same ifindex.
-	 * The latter case happens for example when activating an openvpn connection (synced) and
-	 * assuming the corresponding tun0 interface (unsynced). */
-	g_assert (!entry || !entry_unsynced || (entry->route.rx.ifindex == entry_unsynced->route.rx.ifindex) || metric == G_MAXUINT32);
-
-	/* we only add the route, if we have an (to be synced) entry for it. */
-	if (!entry)
-		return FALSE;
-
-	if (vtable->vt->is_ip4) {
-		NMPlatformIP4Route rt = entry->route.r4;
-		const NMPlatformIP4Route *plat_rt;
-
-		rt.network = 0;
-		rt.plen = 0;
-		rt.metric = entry->effective_metric;
-		rt.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt.rt_source);
-
-		plat_rt = nm_platform_ip4_route_get (priv->platform,
-		                                     entry->route.r4.ifindex,
-		                                     0,
-		                                     0,
-		                                     entry->effective_metric);
-		if (plat_rt && nm_platform_ip4_route_cmp (plat_rt, &rt) == 0) {
-			_LOGt (AF_INET, "already exists: %s",
-			       nm_platform_ip4_route_to_string (&rt, NULL, 0));
-			return FALSE;
-		}
-
-		rt.rt_source = entry->route.r4.rt_source;
-
-		if (plat_rt) {
-			_LOGt (AF_INET, "update platform route: %s; with route: %s",
-			       nm_platform_ip4_route_to_string (plat_rt, buf1, sizeof (buf1)),
-			       nm_platform_ip4_route_to_string (&rt, buf2, sizeof (buf2)));
-		}
-
-		success = nm_platform_ip4_route_add (priv->platform, &rt);
-	} else {
-		NMPlatformIP6Route rt = entry->route.r6;
-		const NMPlatformIP6Route *plat_rt;
-
-		rt.network = in6addr_any;
-		rt.plen = 0;
-		rt.metric = entry->effective_metric;
-		rt.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (rt.rt_source);
-
-		plat_rt = nm_platform_ip6_route_get (priv->platform,
-		                                     entry->route.r6.ifindex,
-		                                     in6addr_any,
-		                                     0,
-		                                     entry->effective_metric);
-		if (plat_rt && nm_platform_ip6_route_cmp (plat_rt, &rt) == 0) {
-			_LOGt (AF_INET6, "already exists: %s",
-			       nm_platform_ip6_route_to_string (&rt, NULL, 0));
-			return FALSE;
-		}
-
-		rt.rt_source = entry->route.r6.rt_source;
-
-		if (plat_rt) {
-			_LOGt (AF_INET, "update platform route: %s; with route: %s",
-			       nm_platform_ip6_route_to_string (plat_rt, buf1, sizeof (buf1)),
-			       nm_platform_ip6_route_to_string (&rt, buf2, sizeof (buf2)));
-		}
-
-		success = nm_platform_ip6_route_add (priv->platform, &rt);
-	}
-
-	if (!success) {
-		_LOGW (vtable->vt->addr_family, "failed to add default route %s with effective metric %u",
-		       vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric);
-	}
-	return TRUE;
-}
-
-static gboolean
-_platform_route_sync_flush (const VTableIP *vtable, NMDefaultRouteManager *self, int ifindex_to_flush)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	GPtrArray *entries = vtable->get_entries (priv);
-	GArray *routes;
-	guint i, j;
-	gboolean changed = FALSE;
-
-	/* prune all other default routes from this device. */
-	routes = vtable->vt->route_get_all (priv->platform, 0, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
-
-	for (i = 0; i < routes->len; i++) {
-		const NMPlatformIPRoute *route;
-		gboolean has_ifindex_synced = FALSE;
-		Entry *entry = NULL;
-
-		route = _vt_route_index (vtable, routes, i);
-
-		/* look at all entries and see if the route for this ifindex pair is
-		 * a known entry. */
-		for (j = 0; j < entries->len; j++) {
-			Entry *e = g_ptr_array_index (entries, j);
-
-			if (   e->route.rx.ifindex == route->ifindex
-			    && e->synced) {
-				has_ifindex_synced = TRUE;
-				if (   !e->never_default
-				    && e->effective_metric == route->metric)
-					entry = e;
-			}
-		}
-
-		/* we only delete the route if we don't have a matching entry,
-		 * and there is at least one entry that references this ifindex
-		 * (indicating that the ifindex is managed by us -- not assumed).
-		 *
-		 * Otherwise, don't delete the route because it's configured
-		 * externally (and will be assumed -- or already is assumed).
-		 */
-		if (   !entry
-		    && (has_ifindex_synced || ifindex_to_flush == route->ifindex)) {
-			vtable->vt->route_delete_default (priv->platform, route->ifindex, route->metric);
-			changed = TRUE;
-		}
-	}
-	g_array_free (routes, TRUE);
-	return changed;
-}
-
-static int
-_sort_entries_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
-{
-	guint32 m_a, m_b;
-	const Entry *e_a = *((const Entry **) a);
-	const Entry *e_b = *((const Entry **) b);
-
-	/* when comparing routes, we consider the (original) metric. */
-	m_a = e_a->route.rx.metric;
-	m_b = e_b->route.rx.metric;
-
-	/* we normalize route.metric already in _ipx_update_default_route().
-	 * so we can just compare the metrics numerically */
-
-	if (m_a != m_b)
-		return (m_a < m_b) ? -1 : 1;
-
-	/* If the metrics are equal, we prefer the one that is !never_default */
-	if (!!e_a->never_default != !!e_b->never_default)
-		return e_a->never_default ? 1 : -1;
-
-	/* If the metrics are equal, we prefer the one that is assumed (!synced).
-	 * Entries that we sync, can be modified so that only the best
-	 * entry has a (deterministically) lowest metric.
-	 * With assumed devices we cannot increase/change the metric.
-	 * For example: two devices, both metric 0. One is assumed the other is
-	 * synced.
-	 * If we would choose the synced entry as best, we cannot
-	 * increase the metric of the assumed one and we would have non-determinism.
-	 * If we instead prefer the assumed device, we can increase the metric
-	 * of the synced device and the assumed device is (deterministically)
-	 * prefered.
-	 * If both devices are assumed, we also have non-determinism, but also
-	 * we don't reorder either.
-	 */
-	if (!!e_a->synced != !!e_b->synced)
-		return e_a->synced ? 1 : -1;
-
-	/* otherwise, do not reorder */
-	return 0;
-}
-
-static GHashTable *
-_get_assumed_interface_metrics (const VTableIP *vtable, NMDefaultRouteManager *self, GArray *routes)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	GPtrArray *entries;
-	guint i, j;
-	GHashTable *result;
-
-	/* create a list of all metrics that are currently assigned on an interface
-	 * that is *not* already covered by one of our synced entries.
-	 * IOW, returns the metrics that are in use by assumed interfaces
-	 * that we want to preserve. */
-
-	entries = vtable->get_entries (priv);
-
-	result = g_hash_table_new (NULL, NULL);
-
-	for (i = 0; i < routes->len; i++) {
-		gboolean ifindex_has_synced_entry = FALSE;
-		const NMPlatformIPRoute *route;
-
-		route = _vt_route_index (vtable, routes, i);
-
-		for (j = 0; j < entries->len; j++) {
-			Entry *e = g_ptr_array_index (entries, j);
-
-			if (   e->synced
-			    && e->route.rx.ifindex == route->ifindex) {
-				ifindex_has_synced_entry = TRUE;
-				break;
-			}
-		}
-
-		if (!ifindex_has_synced_entry)
-			g_hash_table_add (result, GUINT_TO_POINTER (vtable->vt->metric_normalize (route->metric)));
-	}
-
-	/* also add all non-synced metrics from our entries list. We might have there some metrics that
-	 * we track as non-synced but that are no longer part of platform routes. Anyway, for now
-	 * we still want to treat them as assumed. */
-	for (i = 0; i < entries->len; i++) {
-		gboolean ifindex_has_synced_entry = FALSE;
-		Entry *e_i = g_ptr_array_index (entries, i);
-
-		if (e_i->synced)
-			continue;
-
-		for (j = 0; j < entries->len; j++) {
-			Entry *e_j = g_ptr_array_index (entries, j);
-
-			if (   j != i
-			    && (e_j->synced && e_j->route.rx.ifindex == e_i->route.rx.ifindex)) {
-				ifindex_has_synced_entry = TRUE;
-				break;
-			}
-		}
-
-		if (!ifindex_has_synced_entry)
-			g_hash_table_add (result, GUINT_TO_POINTER (vtable->vt->metric_normalize (e_i->route.rx.metric)));
-	}
-
-	return result;
-}
-
-static gboolean
-_resync_all (const VTableIP *vtable, NMDefaultRouteManager *self, const Entry *changed_entry, const Entry *old_entry, gboolean external_change)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	Entry *entry;
-	guint i, j;
-	gint64 last_metric = -1;
-	guint32 expected_metric;
-	GPtrArray *entries;
-	GArray *changed_metrics = g_array_new (FALSE, FALSE, sizeof (guint32));
-	GHashTable *assumed_metrics;
-	GArray *routes;
-	gboolean changed = FALSE;
-	int ifindex_to_flush = 0;
-
-	g_assert (priv->resync.guard == 0);
-	priv->resync.guard++;
-
-	if (!external_change) {
-		if (vtable->vt->is_ip4)
-			priv->resync.has_v4_changes = FALSE;
-		else
-			priv->resync.has_v6_changes = FALSE;
-		if (!priv->resync.has_v4_changes && !priv->resync.has_v6_changes)
-			_resync_idle_cancel (self);
-	}
-
-	entries = vtable->get_entries (priv);
-
-	routes = vtable->vt->route_get_all (priv->platform, 0, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
-
-	assumed_metrics = _get_assumed_interface_metrics (vtable, self, routes);
-
-	if (old_entry && old_entry->synced && !old_entry->never_default) {
-		/* The old version obviously changed. */
-		g_array_append_val (changed_metrics, old_entry->effective_metric);
-	}
-
-	/* first iterate over all entries and adjust the effective metrics. */
-	for (i = 0; i < entries->len; i++) {
-		entry = g_ptr_array_index (entries, i);
-
-		if (entry->never_default)
-			continue;
-
-		if (!entry->synced) {
-			gboolean has_synced_entry = FALSE;
-
-			/* A non synced entry is completely ignored, if we have
-			 * a synced entry for the same if index.
-			 * Otherwise the metric of the entry is still remembered as
-			 * last_metric to avoid reusing it. */
-			for (j = 0; j < entries->len; j++) {
-				const Entry *e = g_ptr_array_index (entries, j);
-
-				if (   e->synced
-					&& e->route.rx.ifindex == entry->route.rx.ifindex) {
-					has_synced_entry = TRUE;
-					break;
-				}
-			}
-			if (!has_synced_entry)
-				last_metric = MAX (last_metric, (gint64) entry->effective_metric);
-			continue;
-		}
-
-		expected_metric = entry->route.rx.metric;
-		if ((gint64) expected_metric <= last_metric)
-			expected_metric = last_metric == G_MAXUINT32 ? G_MAXUINT32 : last_metric + 1;
-
-		while (   expected_metric < G_MAXUINT32
-		       && g_hash_table_contains (assumed_metrics, GUINT_TO_POINTER (expected_metric))) {
-			gboolean has_metric_for_ifindex = FALSE;
-
-			/* Check if there are assumed devices that have default routes with this metric.
-			 * If there are any, we have to pick another effective_metric. */
-
-			/* However, if there is a matching route (ifindex+metric) for our current entry, we are done. */
-			for (j = 0; j < routes->len; j++) {
-				const NMPlatformIPRoute *r = _vt_route_index (vtable, routes, i);
-
-				if (   r->metric == expected_metric
-				    && r->ifindex == entry->route.rx.ifindex) {
-					has_metric_for_ifindex = TRUE;
-					break;
-				}
-			}
-			if (has_metric_for_ifindex)
-				break;
-			expected_metric++;
-		}
-
-		if (changed_entry == entry) {
-			/* for the changed entry, the previous metric was either old_entry->effective_metric,
-			 * or none. Hence, we only have to remember what is going to change. */
-			g_array_append_val (changed_metrics, expected_metric);
-			if (!old_entry) {
-				_LOG2D (vtable, i, entry, "sync:add    %s (%u)",
-				        vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) expected_metric);
-			} else if (old_entry != changed_entry) {
-				_LOG2D (vtable, i, entry, "sync:update %s (%u -> %u)",
-				        vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) old_entry->effective_metric,
-				        (guint) expected_metric);
-			} else {
-				_LOG2D (vtable, i, entry, "sync:resync %s (%u)",
-				        vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) expected_metric);
-			}
-		} else if (entry->effective_metric != expected_metric) {
-			g_array_append_val (changed_metrics, entry->effective_metric);
-			g_array_append_val (changed_metrics, expected_metric);
-			_LOG2D (vtable, i, entry, "sync:metric %s (%u -> %u)",
-			        vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric,
-			        (guint) expected_metric);
-		} else {
-			if (!_vt_routes_has_entry (vtable, routes, entry)) {
-				g_array_append_val (changed_metrics, entry->effective_metric);
-				_LOG2D (vtable, i, entry, "sync:re-add %s (%u -> %u)",
-				        vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric,
-				        (guint) entry->effective_metric);
-			}
-		}
-
-		if (entry->effective_metric != expected_metric) {
-			entry->effective_metric = expected_metric;
-			changed = TRUE;
-		}
-		last_metric = expected_metric;
-	}
-
-	g_array_free (routes, TRUE);
-
-	g_array_sort_with_data (changed_metrics, nm_cmp_uint32_p_with_data, NULL);
-	last_metric = -1;
-	for (j = 0; j < changed_metrics->len; j++) {
-		expected_metric = g_array_index (changed_metrics, guint32, j);
-
-		if (last_metric == (gint64) expected_metric) {
-			/* skip duplicates. */
-			continue;
-		}
-		changed |= _platform_route_sync_add (vtable, self, expected_metric);
-		last_metric = expected_metric;
-	}
-
-	if (   old_entry
-	    && !changed_entry
-	    && old_entry->synced
-	    && !old_entry->never_default) {
-		/* If we entriely remove an entry that was synced before, we must make
-		 * sure to flush routes for this ifindex too. Otherwise they linger
-		 * around as "assumed" routes */
-		ifindex_to_flush = old_entry->route.rx.ifindex;
-	}
-
-	changed |= _platform_route_sync_flush (vtable, self, ifindex_to_flush);
-
-	g_array_free (changed_metrics, TRUE);
-	g_hash_table_unref (assumed_metrics);
-
-	priv->resync.guard--;
-	return changed;
-}
-
-static gboolean
-_entry_at_idx_update (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx, const Entry *old_entry)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	Entry *entry;
-	GPtrArray *entries;
-
-	entries = vtable->get_entries (priv);
-	g_assert (entry_idx < entries->len);
-
-	entry = g_ptr_array_index (entries, entry_idx);
-
-	g_assert (   !old_entry
-	          || (entry->source.pointer == old_entry->source.pointer && entry->route.rx.ifindex == old_entry->route.rx.ifindex));
-
-	if (!entry->synced && !entry->never_default)
-		entry->effective_metric = entry->route.rx.metric;
-
-	_LOG2D (vtable, entry_idx, entry, "%s %s (%"G_GUINT32_FORMAT")",
-	        old_entry
-	            ? (entry != old_entry
-	                   ? "record:update"
-	                   : "record:resync")
-	            : "record:add   ",
-	        vtable->vt->route_to_string (&entry->route, NULL, 0),
-	        entry->effective_metric);
-
-	g_ptr_array_sort_with_data (entries, _sort_entries_cmp, NULL);
-
-	return _resync_all (vtable, self, entry, old_entry, FALSE);
-}
-
-static gboolean
-_entry_at_idx_remove (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	Entry *entry;
-	GPtrArray *entries;
-	gboolean ret;
-
-	entries = vtable->get_entries (priv);
-
-	g_assert (entry_idx < entries->len);
-
-	entry = g_ptr_array_index (entries, entry_idx);
-
-	_LOG2D (vtable, entry_idx, entry, "record:remove %s (%u)",
-	       vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric);
-
-	/* Remove the entry from the list (but don't free it yet) */
-	g_ptr_array_index (entries, entry_idx) = NULL;
-	g_ptr_array_remove_index (entries, entry_idx);
-
-	ret = _resync_all (vtable, self, NULL, entry, FALSE);
-	_entry_free (entry);
-
-	return ret;
-}
-
-/*****************************************************************************/
-
-static gboolean
-_ipx_update_default_route (const VTableIP *vtable,
-                           NMDefaultRouteManager *self,
-                           gpointer source)
-{
-	NMDefaultRouteManagerPrivate *priv;
-	Entry *entry;
-	guint entry_idx;
-	const NMPlatformIPRoute *default_route = NULL;
-	NMPlatformIPXRoute rt;
-	int ip_ifindex;
-	GPtrArray *entries;
-	NMDevice *device = NULL;
-	NMVpnConnection *vpn = NULL;
-	gboolean never_default = FALSE;
-	gboolean synced = FALSE, ret;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), FALSE);
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	if (priv->disposed)
-		return FALSE;
-
-	if (NM_IS_DEVICE (source))
-		device = source;
-	else if (NM_IS_VPN_CONNECTION (source))
-		vpn = source;
-	else
-		g_return_val_if_reached (FALSE);
-
-	if (device)
-		ip_ifindex = nm_device_get_ip_ifindex (device);
-	else
-		ip_ifindex = nm_vpn_connection_get_ip_ifindex (vpn, TRUE);
-
-	entries = vtable->get_entries (priv);
-	entry = _entry_find_by_source (entries, source, &entry_idx);
-
-	if (   entry
-	    && entry->route.rx.ifindex != ip_ifindex) {
-		/* Strange... the ifindex changed... Remove the device and start again. */
-		_LOG2D (vtable, entry_idx, entry, "ifindex changed: %d -> %d",
-		        entry->route.rx.ifindex, ip_ifindex);
-
-		g_object_freeze_notify (G_OBJECT (self));
-		_entry_at_idx_remove (vtable, self, entry_idx);
-		g_assert (!_entry_find_by_source (entries, source, NULL));
-		ret = _ipx_update_default_route (vtable, self, source);
-		g_object_thaw_notify (G_OBJECT (self));
-		return ret;
-	}
-
-	/* get the @default_route from the device. */
-	if (ip_ifindex > 0) {
-		if (device) {
-			gboolean is_assumed = FALSE;
-
-			if (vtable->vt->is_ip4)
-				default_route = (const NMPlatformIPRoute *) nm_device_get_ip4_default_route (device, &is_assumed);
-			else
-				default_route = (const NMPlatformIPRoute *) nm_device_get_ip6_default_route (device, &is_assumed);
-			if (!default_route && !is_assumed) {
-				/* the device has no default route, but it is not assumed. That means, NMDefaultRouteManager
-				 * enforces that the device has no default route.
-				 *
-				 * Hence we have to keep track of this entry, otherwise a missing entry tells us
-				 * that the interface is assumed and NM would not remove the default routes on
-				 * the device. */
-				memset (&rt, 0, sizeof (rt));
-				rt.rx.ifindex = ip_ifindex;
-				rt.rx.rt_source = NM_IP_CONFIG_SOURCE_UNKNOWN;
-				rt.rx.metric = G_MAXUINT32;
-				default_route = &rt.rx;
-
-				never_default = TRUE;
-			}
-			synced = !is_assumed;
-		} else {
-			NMConnection *connection = nm_active_connection_get_applied_connection ((NMActiveConnection *) vpn);
-
-			if (   connection
-			    && nm_vpn_connection_get_vpn_state (vpn) == NM_VPN_CONNECTION_STATE_ACTIVATED) {
-
-				memset (&rt, 0, sizeof (rt));
-				if (vtable->vt->is_ip4) {
-					NMIP4Config *vpn_config;
-
-					vpn_config = nm_vpn_connection_get_ip4_config (vpn);
-					if (vpn_config) {
-						never_default = nm_ip4_config_get_never_default (vpn_config);
-						rt.r4.ifindex = ip_ifindex;
-						rt.r4.rt_source = NM_IP_CONFIG_SOURCE_VPN;
-						rt.r4.gateway = nm_ip4_config_get_gateway (vpn_config);
-						rt.r4.metric = nm_vpn_connection_get_ip4_route_metric (vpn);
-						rt.r4.mss = nm_ip4_config_get_mss (vpn_config);
-						default_route = &rt.rx;
-					}
-				} else {
-					NMIP6Config *vpn_config;
-
-					vpn_config = nm_vpn_connection_get_ip6_config (vpn);
-					if (vpn_config) {
-						const struct in6_addr *int_gw = nm_ip6_config_get_gateway (vpn_config);
-
-						never_default = nm_ip6_config_get_never_default (vpn_config);
-						rt.r6.ifindex = ip_ifindex;
-						rt.r6.rt_source = NM_IP_CONFIG_SOURCE_VPN;
-						rt.r6.gateway = int_gw ? *int_gw : in6addr_any;
-						rt.r6.metric = nm_vpn_connection_get_ip6_route_metric (vpn);
-						rt.r6.mss = nm_ip6_config_get_mss (vpn_config);
-						default_route = &rt.rx;
-					}
-				}
-			}
-			if (nm_vpn_connection_get_ip_ifindex (vpn, FALSE) > 0)
-				synced = TRUE;
-			else {
-				/* a VPN connection without tunnel device cannot have a non-synced, missing default route.
-				 * Either it has a default route (which is synced), or it has no entry. */
-				synced = default_route && !never_default;
-			}
-		}
-	}
-
-	g_assert (!default_route || default_route->plen == 0);
-
-	if (!synced && never_default) {
-		/* having a non-synced, never-default entry is non-sensical. Unset
-		 * @default_route so that we don't add such an entry below. */
-		default_route = NULL;
-	}
-
-	if (!entry && !default_route) {
-		/* nothing to do */
-		return FALSE;
-	} else if (!entry) {
-		/* add */
-		entry = g_slice_new0 (Entry);
-		entry->source.object = g_object_ref (source);
-
-		if (vtable->vt->is_ip4)
-			entry->route.r4 = *((const NMPlatformIP4Route *) default_route);
-		else
-			entry->route.r6 = *((const NMPlatformIP6Route *) default_route);
-
-		/* only use normalized metrics */
-		entry->route.rx.metric = vtable->vt->metric_normalize (entry->route.rx.metric);
-		entry->route.rx.ifindex = ip_ifindex;
-		entry->never_default = never_default;
-		entry->effective_metric = entry->route.rx.metric;
-		entry->synced = synced;
-
-		g_ptr_array_add (entries, entry);
-		return _entry_at_idx_update (vtable, self, entries->len - 1, NULL);
-	} else if (default_route) {
-		/* update */
-		Entry old_entry, new_entry;
-
-		new_entry = *entry;
-		if (vtable->vt->is_ip4)
-			new_entry.route.r4 = *((const NMPlatformIP4Route *) default_route);
-		else
-			new_entry.route.r6 = *((const NMPlatformIP6Route *) default_route);
-		/* only use normalized metrics */
-		new_entry.route.rx.metric = vtable->vt->metric_normalize (new_entry.route.rx.metric);
-		new_entry.route.rx.ifindex = ip_ifindex;
-		new_entry.never_default = never_default;
-		new_entry.synced = synced;
-
-		if (memcmp (entry, &new_entry, sizeof (new_entry)) == 0) {
-			if (!synced) {
-				/* the internal book-keeping doesn't change, so don't do a full
-				 * sync of the configured routes. */
-				return FALSE;
-			}
-			return _entry_at_idx_update (vtable, self, entry_idx, entry);
-		} else {
-			old_entry = *entry;
-			*entry = new_entry;
-			return _entry_at_idx_update (vtable, self, entry_idx, &old_entry);
-		}
-	} else {
-		/* delete */
-		return _entry_at_idx_remove (vtable, self, entry_idx);
-	}
-}
-
-gboolean
-nm_default_route_manager_ip4_update_default_route (NMDefaultRouteManager *self,
-                                                   gpointer source)
-{
-	return _ipx_update_default_route (&vtable_ip4, self, source);
-}
-
-gboolean
-nm_default_route_manager_ip6_update_default_route (NMDefaultRouteManager *self,
-                                                   gpointer source)
-{
-	return _ipx_update_default_route (&vtable_ip6, self, source);
-}
-
-/*****************************************************************************/
-
-static gboolean
-_ipx_connection_has_default_route (const VTableIP *vtable, NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
-{
-	const char *method;
-	NMSettingIPConfig *s_ip;
-	gboolean is_never_default = FALSE;
-	gboolean has_default_route = FALSE;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), FALSE);
-
-	if (!connection)
-		goto out;
-
-	if (vtable->vt->is_ip4)
-		s_ip = nm_connection_get_setting_ip4_config (connection);
-	else
-		s_ip = nm_connection_get_setting_ip6_config (connection);
-	if (!s_ip)
-		goto out;
-	if (nm_setting_ip_config_get_never_default (s_ip)) {
-		is_never_default = TRUE;
-		goto out;
-	}
-
-	if (vtable->vt->is_ip4) {
-		method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
-		if (   !method
-		    || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)
-		    || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
-			goto out;
-	} else {
-		method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
-		if (   !method
-		    || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)
-		    || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
-			goto out;
-	}
-
-	has_default_route = TRUE;
-out:
-	if (out_is_never_default)
-		*out_is_never_default = is_never_default;
-	return has_default_route;
-}
-
-gboolean
-nm_default_route_manager_ip4_connection_has_default_route (NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
-{
-	return _ipx_connection_has_default_route (&vtable_ip4, self, connection, out_is_never_default);
-}
-
-gboolean
-nm_default_route_manager_ip6_connection_has_default_route (NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
-{
-	return _ipx_connection_has_default_route (&vtable_ip6, self, connection, out_is_never_default);
-}
-
-/*****************************************************************************/
-
-static NMDevice *
-_ipx_get_best_device (const VTableIP *vtable, NMDefaultRouteManager *self, const GSList *devices)
-{
-	NMDefaultRouteManagerPrivate *priv;
-	GPtrArray *entries;
-	guint i;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
-
-	if (!devices)
-		return NULL;
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	if (priv->disposed)
-		return NULL;
-	entries = vtable->get_entries (priv);
-
-	for (i = 0; i < entries->len; i++) {
-		Entry *entry = g_ptr_array_index (entries, i);
-		NMDeviceState state;
-
-		if (!NM_IS_DEVICE (entry->source.pointer))
-			continue;
-
-		if (entry->never_default)
-			continue;
-
-		state = nm_device_get_state (entry->source.device);
-		if (   state <= NM_DEVICE_STATE_DISCONNECTED
-		    || state >= NM_DEVICE_STATE_DEACTIVATING) {
-			/* FIXME: we also track unmanaged devices with assumed default routes.
-			 * Skip them, they are (currently) no candidates for best-device.
-			 *
-			 * Later we also want to properly assume connections for unmanaged devices.
-			 *
-			 * Also, we don't want to have DEACTIVATING devices returned as best_device(). */
-			continue;
-		}
-
-		if (g_slist_find ((GSList *) devices, entry->source.device)) {
-			g_return_val_if_fail (nm_device_get_act_request (entry->source.pointer), entry->source.pointer);
-			return entry->source.pointer;
-		}
-	}
-	return NULL;
-}
-
-/** _ipx_get_best_activating_device:
- * @vtable: the virtual table
- * @self: #NMDefaultRouteManager
- * @devices: list of devices to be searched. Only devices from this list will be considered
- * @fully_activated: if #TRUE, only search for devices that are fully activated. Otherwise,
- *   search if there is a best device going to be activated. In the latter case, this will
- *   return NULL if the best device is already activated.
- * @preferred_device: if not-NULL, this device is preferred if there are more devices with
- *   the same priority.
- **/
-static NMDevice *
-_ipx_get_best_activating_device (const VTableIP *vtable, NMDefaultRouteManager *self, const GSList *devices, NMDevice *preferred_device)
-{
-	NMDefaultRouteManagerPrivate *priv;
-	const GSList *iter;
-	NMDevice *best_device = NULL;
-	guint32 best_prio = G_MAXUINT32;
-	NMDevice *best_activated_device;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	if (priv->disposed)
-		return NULL;
-
-	best_activated_device = _ipx_get_best_device (vtable, self, devices);
-
-	for (iter = devices; iter; iter = g_slist_next (iter)) {
-		NMDevice *device = NM_DEVICE (iter->data);
-		guint32 prio;
-		Entry *entry;
-
-		entry = _entry_find_by_source (vtable->get_entries (priv), device, NULL);
-
-		if (entry) {
-			/* of all the device that have an entry, we already know that best_activated_device
-			 * is the best. entry cannot be better. */
-			if (entry->source.device != best_activated_device)
-				continue;
-			prio = entry->effective_metric;
-		} else {
-			NMDeviceState state = nm_device_get_state (device);
-
-			if (   state <= NM_DEVICE_STATE_DISCONNECTED
-			    || state >= NM_DEVICE_STATE_DEACTIVATING)
-				continue;
-
-			if (!_ipx_connection_has_default_route (vtable, self, nm_device_get_applied_connection (device), NULL))
-				continue;
-
-			prio = nm_device_get_ip4_route_metric (device);
-		}
-		prio = vtable->vt->metric_normalize (prio);
-
-		if (   !best_device
-		    || prio < best_prio
-			|| (prio == best_prio && preferred_device == device)) {
-			best_device = device;
-			best_prio = prio;
-		}
-	}
-
-	/* There's only a best activating device if the best device
-	 * among all activating and already-activated devices is a
-	 * still-activating one.
-	 */
-	if (best_device && nm_device_get_state (best_device) >= NM_DEVICE_STATE_SECONDARIES)
-		return NULL;
-	return best_device;
-}
-
-NMDevice *
-nm_default_route_manager_ip4_get_best_device (NMDefaultRouteManager *self, const GSList *devices, gboolean fully_activated, NMDevice *preferred_device)
-{
-	if (fully_activated)
-		return _ipx_get_best_device (&vtable_ip4, self, devices);
-	else
-		return _ipx_get_best_activating_device (&vtable_ip4, self, devices, preferred_device);
-}
-
-NMDevice *
-nm_default_route_manager_ip6_get_best_device (NMDefaultRouteManager *self, const GSList *devices, gboolean fully_activated, NMDevice *preferred_device)
-{
-	if (fully_activated)
-		return _ipx_get_best_device (&vtable_ip6, self, devices);
-	else
-		return _ipx_get_best_activating_device (&vtable_ip6, self, devices, preferred_device);
-}
-
-/*****************************************************************************/
-
-static gpointer
-_ipx_get_best_config (const VTableIP *vtable,
-                      NMDefaultRouteManager *self,
-                      gboolean ignore_never_default,
-                      const char **out_ip_iface,
-                      NMActiveConnection **out_ac,
-                      NMDevice **out_device,
-                      NMVpnConnection **out_vpn)
-{
-	NMDefaultRouteManagerPrivate *priv;
-	GPtrArray *entries;
-	guint i;
-	gpointer config_result = NULL;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
-
-	if (out_ip_iface)
-		*out_ip_iface = NULL;
-	if (out_ac)
-		*out_ac = NULL;
-	if (out_device)
-		*out_device = NULL;
-	if (out_vpn)
-		*out_vpn = NULL;
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	if (priv->disposed)
-		return NULL;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-	entries = vtable->get_entries (priv);
-
-	for (i = 0; i < entries->len; i++) {
-		Entry *entry = g_ptr_array_index (entries, i);
-
-		if (!NM_IS_DEVICE (entry->source.pointer)) {
-			NMVpnConnection *vpn = NM_VPN_CONNECTION (entry->source.vpn);
-
-			if (entry->never_default && !ignore_never_default)
-				continue;
-
-			if (vtable->vt->is_ip4)
-				config_result = nm_vpn_connection_get_ip4_config (vpn);
-			else
-				config_result = nm_vpn_connection_get_ip6_config (vpn);
-			g_assert (config_result);
-
-			if (out_vpn)
-				*out_vpn = vpn;
-			if (out_ac)
-				*out_ac = NM_ACTIVE_CONNECTION (vpn);
-			if (out_ip_iface)
-				*out_ip_iface = nm_vpn_connection_get_ip_iface (vpn, TRUE);
-		} else {
-			NMDevice *device = entry->source.device;
-			NMActRequest *req;
-			NMDeviceState state;
-
-			if (entry->never_default)
-				continue;
-
-			state = nm_device_get_state (device);
-			if (   state <= NM_DEVICE_STATE_DISCONNECTED
-			    || state >= NM_DEVICE_STATE_DEACTIVATING) {
-				/* FIXME: the device has a default route, but we ignore it due to
-				 * unexpected state. That happens for example for unmanaged devices.
-				 *
-				 * In the future, we want unmanaged devices also assume a connection
-				 * if they are activated externally.
-				 *
-				 * Also, we don't want to have DEACTIVATING devices returned as best_config(). */
-				continue;
-			}
-
-			if (vtable->vt->is_ip4)
-				config_result = nm_device_get_ip4_config (device);
-			else
-				config_result = nm_device_get_ip6_config (device);
-			g_assert (config_result);
-			req = nm_device_get_act_request (device);
-			g_assert (req);
-
-			if (out_device)
-				*out_device = device;
-			if (out_ac)
-				*out_ac = NM_ACTIVE_CONNECTION (req);
-			if (out_ip_iface)
-				*out_ip_iface = nm_device_get_ip_iface (device);
-		}
-		break;
-	}
-
-	return config_result;
-}
-
-NMIP4Config *
-nm_default_route_manager_ip4_get_best_config (NMDefaultRouteManager *self,
-                                              gboolean ignore_never_default,
-                                              const char **out_ip_iface,
-                                              NMActiveConnection **out_ac,
-                                              NMDevice **out_device,
-                                              NMVpnConnection **out_vpn)
-{
-	return _ipx_get_best_config (&vtable_ip4,
-	                             self,
-	                             ignore_never_default,
-	                             out_ip_iface,
-	                             out_ac,
-	                             out_device,
-	                             out_vpn);
-}
-
-NMIP6Config *
-nm_default_route_manager_ip6_get_best_config (NMDefaultRouteManager *self,
-                                              gboolean ignore_never_default,
-                                              const char **out_ip_iface,
-                                              NMActiveConnection **out_ac,
-                                              NMDevice **out_device,
-                                              NMVpnConnection **out_vpn)
-{
-	return _ipx_get_best_config (&vtable_ip6,
-	                             self,
-	                             ignore_never_default,
-	                             out_ip_iface,
-	                             out_ac,
-	                             out_device,
-	                             out_vpn);
-}
-
-/*****************************************************************************/
-
-static GPtrArray *
-_v4_get_entries (NMDefaultRouteManagerPrivate *priv)
-{
-	return priv->entries_ip4;
-}
-
-static GPtrArray *
-_v6_get_entries (NMDefaultRouteManagerPrivate *priv)
-{
-	return priv->entries_ip6;
-}
-
-static const VTableIP vtable_ip4 = {
-	.vt                             = &nm_platform_vtable_route_v4,
-	.get_entries                    = _v4_get_entries,
-};
-
-static const VTableIP vtable_ip6 = {
-	.vt                             = &nm_platform_vtable_route_v6,
-	.get_entries                    = _v6_get_entries,
-};
-
-/*****************************************************************************/
-
-static gboolean
-_resync_now (NMDefaultRouteManager *self)
-{
-	gboolean has_v4_changes, has_v6_changes;
-	gboolean changed = FALSE;
-
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	has_v4_changes = priv->resync.has_v4_changes;
-	has_v6_changes = priv->resync.has_v6_changes;
-
-	_LOGD (0, "resync: sync now (%u) (IPv4 changes: %s, IPv6 changes: %s)", priv->resync.idle_handle,
-	       has_v4_changes ? "yes" : "no", has_v6_changes ? "yes" : "no");
-
-	priv->resync.has_v4_changes = FALSE;
-	priv->resync.has_v6_changes = FALSE;
-	nm_clear_g_source (&priv->resync.idle_handle);
-	priv->resync.backoff_wait_time_ms =
-	    priv->resync.backoff_wait_time_ms == 0
-	    ? 100
-	    : priv->resync.backoff_wait_time_ms * 2;
-
-	if (has_v4_changes)
-		changed |= _resync_all (&vtable_ip4, self, NULL, NULL, TRUE);
-
-	if (has_v6_changes)
-		changed |= _resync_all (&vtable_ip6, self, NULL, NULL, TRUE);
-
-	if (!changed) {
-		/* Nothing changed: reset the backoff wait time */
-		_resync_idle_cancel (self);
-	}
-
-	return changed;
-}
-
-/**
- * nm_default_route_manager_resync:
- * @self: the #NMDefaultRouteManager instance
- * @af_family: the address family to resync, can be
- *   AF_INET, AF_INET6 or AF_UNSPEC to sync both.
- *
- * #NMDefaultRouteManager keeps an internal list of configured
- * routes. Usually, it configures routes in the system only
- *  - when that internal list changes due to
- *    nm_default_route_manager_ip4_update_default_route() or
- *    nm_default_route_manager_ip6_update_default_route().
- *  - when platform notifies about changes, via _resync_idle_now().
- * This forces a resync to update the internal bookkeeping
- * with what is currently configured in the system, but also
- * reconfigure the system with all non-assumed default routes.
- *
- * Returns: %TRUE if anything changed during resync.
- */
-gboolean
-nm_default_route_manager_resync (NMDefaultRouteManager *self,
-                                 int af_family)
-{
-	NMDefaultRouteManagerPrivate *priv;
-
-	g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), FALSE);
-	g_return_val_if_fail (NM_IN_SET (af_family, AF_INET, AF_INET6, AF_UNSPEC), FALSE);
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	if (priv->disposed)
-		return FALSE;
-
-	switch (af_family) {
-	case AF_INET:
-		priv->resync.has_v4_changes = TRUE;
-		break;
-	case AF_INET6:
-		priv->resync.has_v6_changes = TRUE;
-		break;
-	default:
-		priv->resync.has_v4_changes = TRUE;
-		priv->resync.has_v6_changes = TRUE;
-		break;
-	}
-
-	return _resync_now (self);
-}
-
-static gboolean
-_resync_idle_now (NMDefaultRouteManager *self)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	priv->resync.idle_handle = 0;
-	_resync_now (self);
-	return G_SOURCE_REMOVE;
-}
-
-static void
-_resync_idle_cancel (NMDefaultRouteManager *self)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	if (priv->resync.idle_handle) {
-		_LOGD (0, "resync: cancelled (%u)", priv->resync.idle_handle);
-		g_source_remove (priv->resync.idle_handle);
-		priv->resync.idle_handle = 0;
-	}
-	priv->resync.backoff_wait_time_ms = 0;
-	priv->resync.has_v4_changes = FALSE;
-	priv->resync.has_v6_changes = FALSE;
-}
-
-static void
-_resync_idle_reschedule (NMDefaultRouteManager *self)
-{
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	/* since we react on external changes and re-add/remove default routes for
-	 * the interfaces we manage, there could be the erroneous situation where two applications
-	 * fight over a certain default route.
-	 * Avoid this, by increasingly wait longer to touch the system (backoff wait time). */
-
-	if (priv->resync.backoff_wait_time_ms == 0) {
-		/* for scheduling idle, always reschedule (to process all other events first) */
-		if (priv->resync.idle_handle)
-			g_source_remove (priv->resync.idle_handle);
-		else
-			_LOGD (0, "resync: schedule on idle");
-		/* Schedule this at low priority so that on an external change to platform
-		 * a NMDevice has a chance to picks up the changes first. */
-		priv->resync.idle_handle = g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) _resync_idle_now, self, NULL);
-	} else if (!priv->resync.idle_handle) {
-		priv->resync.idle_handle =  g_timeout_add (priv->resync.backoff_wait_time_ms, (GSourceFunc) _resync_idle_now, self);
-		_LOGD (0, "resync: schedule in %u.%03u seconds (%u)", priv->resync.backoff_wait_time_ms/1000,
-		       priv->resync.backoff_wait_time_ms%1000, priv->resync.idle_handle);
-	}
-}
-
-static void
-_platform_changed_cb (NMPlatform *platform,
-                      int obj_type_i,
-                      int ifindex,
-                      gpointer platform_object,
-                      int change_type_i,
-                      NMDefaultRouteManager *self)
-{
-	NMDefaultRouteManagerPrivate *priv;
-	const NMPObjectType obj_type = obj_type_i;
-	const VTableIP *vtable;
-
-	switch (obj_type) {
-	case NMP_OBJECT_TYPE_IP4_ADDRESS:
-		vtable = &vtable_ip4;
-		break;
-	case NMP_OBJECT_TYPE_IP6_ADDRESS:
-		vtable = &vtable_ip6;
-		break;
-	case NMP_OBJECT_TYPE_IP4_ROUTE:
-		if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (platform_object))
-			return;
-		vtable = &vtable_ip4;
-		break;
-	case NMP_OBJECT_TYPE_IP6_ROUTE:
-		if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT (platform_object))
-			return;
-		vtable = &vtable_ip6;
-		break;
-	default:
-		g_return_if_reached ();
-	}
-
-	priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	if (priv->resync.guard) {
-		/* callbacks while executing _resync_all() are ignored. */
-		return;
-	}
-
-	if (vtable->vt->is_ip4)
-		priv->resync.has_v4_changes = TRUE;
-	else
-		priv->resync.has_v6_changes = TRUE;
-
-	_resync_idle_reschedule (self);
-}
-
-/*****************************************************************************/
-
-static void
-set_property (GObject *object, guint prop_id,
-              const GValue *value, GParamSpec *pspec)
-{
-	NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	switch (prop_id) {
-	case PROP_LOG_WITH_PTR:
-		/* construct-only */
-		priv->log_with_ptr = g_value_get_boolean (value);
-		break;
-	case PROP_PLATFORM:
-		/* construct-only */
-		priv->platform = g_value_get_object (value) ? : NM_PLATFORM_GET;
-		if (!priv->platform)
-			g_return_if_reached ();
-		g_object_ref (priv->platform);
-		break;
-	default:
-		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-		break;
-	}
-}
-
-/*****************************************************************************/
-
-static void
-nm_default_route_manager_init (NMDefaultRouteManager *self)
-{
-}
-
-static void
-constructed (GObject *object)
-{
-	NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	priv->entries_ip4 = g_ptr_array_new_full (0, (GDestroyNotify) _entry_free);
-	priv->entries_ip6 = g_ptr_array_new_full (0, (GDestroyNotify) _entry_free);
-
-	g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_platform_changed_cb), self);
-	g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_platform_changed_cb), self);
-	g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_platform_changed_cb), self);
-	g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_platform_changed_cb), self);
-}
-
-NMDefaultRouteManager *
-nm_default_route_manager_new (gboolean log_with_ptr, NMPlatform *platform)
-{
-	return g_object_new (NM_TYPE_DEFAULT_ROUTE_MANAGER,
-	                     NM_DEFAULT_ROUTE_MANAGER_LOG_WITH_PTR, log_with_ptr,
-	                     NM_DEFAULT_ROUTE_MANAGER_PLATFORM, platform,
-	                     NULL);
-}
-
-static void
-dispose (GObject *object)
-{
-	NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
-	NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
-	priv->disposed = TRUE;
-
-	if (priv->platform) {
-		g_signal_handlers_disconnect_by_func (priv->platform, G_CALLBACK (_platform_changed_cb), self);
-		g_clear_object (&priv->platform);
-	}
-
-	_resync_idle_cancel (self);
-
-	/* g_ptr_array_free() invokes the free function for all entries without actually
-	 * removing them and having dangling pointers in the process. _entry_free()
-	 * will unref the source, which might cause the destruction of the object, which
-	 * might trigger calling into @self again. This is guarded by priv->dispose.
-	 * If you remove priv->dispose, you must refactor the lines below to remove enties
-	 * one-by-one.
-	 */
-	if (priv->entries_ip4) {
-		g_ptr_array_free (priv->entries_ip4, TRUE);
-		priv->entries_ip4 = NULL;
-	}
-	if (priv->entries_ip6) {
-		g_ptr_array_free (priv->entries_ip6, TRUE);
-		priv->entries_ip6 = NULL;
-	}
-
-	G_OBJECT_CLASS (nm_default_route_manager_parent_class)->dispose (object);
-}
-
-static void
-nm_default_route_manager_class_init (NMDefaultRouteManagerClass *klass)
-{
-	GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
-	object_class->constructed = constructed;
-	object_class->dispose = dispose;
-	object_class->set_property = set_property;
-
-	obj_properties[PROP_LOG_WITH_PTR] =
-	    g_param_spec_boolean (NM_DEFAULT_ROUTE_MANAGER_LOG_WITH_PTR, "", "",
-	                          TRUE,
-	                          G_PARAM_WRITABLE |
-	                          G_PARAM_CONSTRUCT_ONLY |
-	                          G_PARAM_STATIC_STRINGS);
-
-	obj_properties[PROP_PLATFORM] =
-	    g_param_spec_object (NM_DEFAULT_ROUTE_MANAGER_PLATFORM, "", "",
-	                         NM_TYPE_PLATFORM,
-	                         G_PARAM_WRITABLE |
-	                         G_PARAM_CONSTRUCT_ONLY |
-	                         G_PARAM_STATIC_STRINGS);
-
-	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
-}