summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2014-11-19 23:01:05 +0100
committerThomas Haller <thaller@redhat.com>2014-11-19 23:01:51 +0100
commit5338178d8161a2f37d756896be43859b3b86ff3e (patch)
treedeca8cdcc8eb37c2fe28fc8d179841d08447a33f
parent9f5cff0bb3429078cdbb9ada955a7dbe17d3c7b0 (diff)
parenta0f81f266bac2a65385ef0b252c461ac152e85b9 (diff)
downloadNetworkManager-5338178d8161a2f37d756896be43859b3b86ff3e.tar.gz
policy: merge branch 'th/bgo735512_route_metric_v2'
Further fixes to NMDefaultRouteManager. https://bugzilla.gnome.org/show_bug.cgi?id=735512
-rw-r--r--src/devices/nm-device.c71
-rw-r--r--src/devices/nm-device.h4
-rw-r--r--src/nm-default-route-manager.c689
-rw-r--r--src/nm-default-route-manager.h3
-rw-r--r--src/platform/nm-platform.h12
-rw-r--r--src/vpn-manager/nm-vpn-connection.c9
6 files changed, 600 insertions, 188 deletions
diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c
index 864a7d57bd..2a3ef8da69 100644
--- a/src/devices/nm-device.c
+++ b/src/devices/nm-device.c
@@ -238,11 +238,14 @@ typedef struct {
IpState ip4_state;
NMIP4Config * dev_ip4_config; /* Config from DHCP, PPP, LLv4, etc */
NMIP4Config * ext_ip4_config; /* Stuff added outside NM */
+ gboolean ext_ip4_config_had_any_addresses;
NMIP4Config * wwan_ip4_config; /* WWAN configuration */
struct {
gboolean v4_has;
+ gboolean v4_is_assumed;
NMPlatformIP4Route v4;
gboolean v6_has;
+ gboolean v6_is_assumed;
NMPlatformIP6Route v6;
} default_route;
@@ -273,6 +276,7 @@ typedef struct {
NMIP6Config * vpn6_config; /* routes added by a VPN which uses this device */
NMIP6Config * wwan_ip6_config;
NMIP6Config * ext_ip6_config; /* Stuff added outside NM */
+ gboolean ext_ip6_config_had_any_addresses;
gboolean nm_ipv6ll; /* TRUE if NM handles the device's IPv6LL address */
NMRDisc * rdisc;
@@ -724,7 +728,7 @@ nm_device_get_ip6_route_metric (NMDevice *self)
}
const NMPlatformIP4Route *
-nm_device_get_ip4_default_route (NMDevice *self)
+nm_device_get_ip4_default_route (NMDevice *self, gboolean *out_is_assumed)
{
NMDevicePrivate *priv;
@@ -732,11 +736,14 @@ nm_device_get_ip4_default_route (NMDevice *self)
priv = NM_DEVICE_GET_PRIVATE (self);
+ if (out_is_assumed)
+ *out_is_assumed = priv->default_route.v4_is_assumed;
+
return priv->default_route.v4_has ? &priv->default_route.v4 : NULL;
}
const NMPlatformIP6Route *
-nm_device_get_ip6_default_route (NMDevice *self)
+nm_device_get_ip6_default_route (NMDevice *self, gboolean *out_is_assumed)
{
NMDevicePrivate *priv;
@@ -744,6 +751,9 @@ nm_device_get_ip6_default_route (NMDevice *self)
priv = NM_DEVICE_GET_PRIVATE (self);
+ if (out_is_assumed)
+ *out_is_assumed = priv->default_route.v6_is_assumed;
+
return priv->default_route.v6_has ? &priv->default_route.v6 : NULL;
}
@@ -2782,8 +2792,10 @@ ip4_config_merge_and_apply (NMDevice *self,
*/
connection = nm_device_get_connection (self);
priv->default_route.v4_has = FALSE;
+ priv->default_route.v4_is_assumed = TRUE;
if (connection) {
gboolean assumed = nm_device_uses_assumed_connection (self);
+ NMPlatformIP4Route *route = &priv->default_route.v4;
if (!nm_settings_connection_get_nm_generated_assumed (NM_SETTINGS_CONNECTION (connection))) {
nm_ip4_config_merge_setting (composite,
@@ -2802,13 +2814,17 @@ ip4_config_merge_and_apply (NMDevice *self,
* NMDefaultRouteManager eventually configures (because the it might
* tweak the effective metric).
*/
- if (nm_default_route_manager_ip4_connection_has_default_route (nm_default_route_manager_get (), connection)) {
+ if ( !assumed
+ && nm_default_route_manager_ip4_connection_has_default_route (nm_default_route_manager_get (), connection)) {
guint32 gateway = 0;
- NMPlatformIP4Route *route = &priv->default_route.v4;
- if (assumed)
- priv->default_route.v4_has = _device_get_default_route_from_platform (self, AF_INET, (NMPlatformIPRoute *) route);
- else {
+ priv->default_route.v4_is_assumed = FALSE;
+ if ( (!commit && priv->ext_ip4_config_had_any_addresses)
+ || ( commit && nm_ip4_config_get_num_addresses (composite))) {
+ /* For managed interfaces, we can only configure a gateway, if either the external config indicates
+ * that we already have addresses, or if we are about to commit any addresses.
+ * Otherwise adding a default route will fail, because NMDefaultRouteManager does not add any
+ * addresses for the route. */
gateway = nm_ip4_config_get_gateway (composite);
if ( gateway
|| nm_device_get_device_type (self) == NM_DEVICE_TYPE_MODEM) {
@@ -2832,6 +2848,10 @@ ip4_config_merge_and_apply (NMDevice *self,
}
}
}
+ } else {
+ /* For interfaces that are assumed and that have no default-route by configuration, we assume
+ * the default connection and pick up whatever is configured. */
+ priv->default_route.v4_has = _device_get_default_route_from_platform (self, AF_INET, (NMPlatformIPRoute *) route);
}
}
@@ -3331,8 +3351,10 @@ ip6_config_merge_and_apply (NMDevice *self,
*/
connection = nm_device_get_connection (self);
priv->default_route.v6_has = FALSE;
+ priv->default_route.v6_is_assumed = TRUE;
if (connection) {
gboolean assumed = nm_device_uses_assumed_connection (self);
+ NMPlatformIP6Route *route = &priv->default_route.v6;
if (!nm_settings_connection_get_nm_generated_assumed (NM_SETTINGS_CONNECTION (connection))) {
nm_ip6_config_merge_setting (composite,
@@ -3351,13 +3373,17 @@ ip6_config_merge_and_apply (NMDevice *self,
* NMDefaultRouteManager eventually configures (because the it might
* tweak the effective metric).
*/
- if (nm_default_route_manager_ip6_connection_has_default_route (nm_default_route_manager_get (), connection)) {
+ if ( !assumed
+ && nm_default_route_manager_ip6_connection_has_default_route (nm_default_route_manager_get (), connection)) {
const struct in6_addr *gateway = NULL;
- NMPlatformIP6Route *route = &priv->default_route.v6;
- if (assumed)
- priv->default_route.v6_has = _device_get_default_route_from_platform (self, AF_INET6, (NMPlatformIPRoute *) route);
- else {
+ priv->default_route.v6_is_assumed = FALSE;
+ if ( (!commit && priv->ext_ip6_config_had_any_addresses)
+ || ( commit && nm_ip6_config_get_num_addresses (composite))) {
+ /* For managed interfaces, we can only configure a gateway, if either the external config indicates
+ * that we already have addresses, or if we are about to commit any addresses.
+ * Otherwise adding a default route will fail, because NMDefaultRouteManager does not add any
+ * addresses for the route. */
gateway = nm_ip6_config_get_gateway (composite);
if (gateway) {
memset (route, 0, sizeof (*route));
@@ -3380,6 +3406,10 @@ ip6_config_merge_and_apply (NMDevice *self,
}
}
}
+ } else {
+ /* For interfaces that are assumed and that have no default-route by configuration, we assume
+ * the default connection and pick up whatever is configured. */
+ priv->default_route.v6_has = _device_get_default_route_from_platform (self, AF_INET6, (NMPlatformIPRoute *) route);
}
}
@@ -6336,7 +6366,8 @@ update_ip_config (NMDevice *self, gboolean initial)
/* IPv4 */
g_clear_object (&priv->ext_ip4_config);
priv->ext_ip4_config = nm_ip4_config_capture (ifindex, capture_resolv_conf);
-
+ priv->ext_ip4_config_had_any_addresses = ( priv->ext_ip4_config
+ && nm_ip4_config_get_num_addresses (priv->ext_ip4_config) > 0);
if (priv->ext_ip4_config) {
if (initial) {
g_clear_object (&priv->dev_ip4_config);
@@ -6355,6 +6386,8 @@ update_ip_config (NMDevice *self, gboolean initial)
/* IPv6 */
g_clear_object (&priv->ext_ip6_config);
priv->ext_ip6_config = nm_ip6_config_capture (ifindex, capture_resolv_conf, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+ priv->ext_ip6_config_had_any_addresses = ( priv->ext_ip6_config
+ && nm_ip6_config_get_num_addresses (priv->ext_ip6_config) > 0);
if (priv->ext_ip6_config) {
/* Check this before modifying ext_ip6_config */
@@ -6933,10 +6966,12 @@ _cleanup_generic_post (NMDevice *self, gboolean deconfigure)
NMDeviceStateReason ignored = NM_DEVICE_STATE_REASON_NONE;
priv->default_route.v4_has = FALSE;
+ priv->default_route.v4_is_assumed = TRUE;
priv->default_route.v6_has = FALSE;
+ priv->default_route.v6_is_assumed = TRUE;
- nm_default_route_manager_ip4_remove_default_route (nm_default_route_manager_get (), self);
- nm_default_route_manager_ip6_remove_default_route (nm_default_route_manager_get (), self);
+ nm_default_route_manager_ip4_update_default_route (nm_default_route_manager_get (), self);
+ nm_default_route_manager_ip6_update_default_route (nm_default_route_manager_get (), self);
/* Clean up IP configs; this does not actually deconfigure the
* interface; the caller must flush routes and addresses explicitly.
@@ -6954,6 +6989,9 @@ _cleanup_generic_post (NMDevice *self, gboolean deconfigure)
g_clear_object (&priv->wwan_ip6_config);
g_clear_object (&priv->ip6_config);
+ priv->ext_ip4_config_had_any_addresses = FALSE;
+ priv->ext_ip6_config_had_any_addresses = FALSE;
+
clear_act_request (self);
/* Clear legacy IPv4 address property */
@@ -7730,6 +7768,9 @@ nm_device_init (NMDevice *self)
priv->unmanaged_flags = NM_UNMANAGED_INTERNAL;
priv->available_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
priv->ip6_saved_properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+
+ priv->default_route.v4_is_assumed = TRUE;
+ priv->default_route.v6_is_assumed = TRUE;
}
/*
diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h
index 840a19c08b..64687f7cbe 100644
--- a/src/devices/nm-device.h
+++ b/src/devices/nm-device.h
@@ -362,8 +362,8 @@ gboolean nm_device_owns_iface (NMDevice *device, const char *iface);
NMConnection *nm_device_new_default_connection (NMDevice *self);
-const NMPlatformIP4Route *nm_device_get_ip4_default_route (NMDevice *self);
-const NMPlatformIP6Route *nm_device_get_ip6_default_route (NMDevice *self);
+const NMPlatformIP4Route *nm_device_get_ip4_default_route (NMDevice *self, gboolean *out_is_assumed);
+const NMPlatformIP6Route *nm_device_get_ip6_default_route (NMDevice *self, gboolean *out_is_assumed);
void nm_device_spawn_iface_helper (NMDevice *self);
diff --git a/src/nm-default-route-manager.c b/src/nm-default-route-manager.c
index 8afdbb487a..4dec5853dd 100644
--- a/src/nm-default-route-manager.c
+++ b/src/nm-default-route-manager.c
@@ -37,6 +37,13 @@
typedef struct {
GPtrArray *entries_ip4;
GPtrArray *entries_ip6;
+ struct {
+ guint guard;
+ guint backoff_wait_time_ms;
+ guint idle_handle;
+ gboolean has_v4_changes;
+ gboolean has_v6_changes;
+ } resync;
} NMDefaultRouteManagerPrivate;
#define NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEFAULT_ROUTE_MANAGER, NMDefaultRouteManagerPrivate))
@@ -51,7 +58,7 @@ static NMDefaultRouteManager *_instance;
guint64 __domain = __addr_family == AF_INET ? LOGD_IP4 : LOGD_IP6; \
\
if (nm_logging_enabled ((level), (__domain))) { \
- char __ch = __addr_family == AF_INET ? '4' : '6'; \
+ char __ch = __addr_family == AF_INET ? '4' : (__addr_family == AF_INET6 ? '6' : '-'); \
char __prefix[30] = "default-route"; \
\
if ((self) != _instance) \
@@ -69,12 +76,18 @@ static NMDefaultRouteManager *_instance;
#define _LOGW(addr_family, ...) _LOG (LOGL_WARN , addr_family, __VA_ARGS__)
#define _LOGE(addr_family, ...) _LOG (LOGL_ERR , addr_family, __VA_ARGS__)
-#define LOG_ENTRY_FMT "entry[%u/%s:%p:%s]"
+#define LOG_ENTRY_FMT "entry[%u/%s:%p:%s:%c%c]"
#define LOG_ENTRY_ARGS(entry_idx, entry) \
- 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_vpn_connection_get_connection_id (entry->source.vpn)
+ (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_vpn_connection_get_connection_id ((entry)->source.vpn), \
+ ((entry)->never_default ? 'N' : 'n'), \
+ ((entry)->synced ? 'S' : 's')
+
+/***********************************************************************************/
+
+static void _resync_idle_cancel (NMDefaultRouteManager *self);
/***********************************************************************************/
@@ -85,16 +98,33 @@ typedef struct {
NMDevice *device;
NMVpnConnection *vpn;
} source;
- union {
- NMPlatformIPRoute route;
- NMPlatformIP4Route route4;
- NMPlatformIP6Route route6;
- };
- gboolean synced; /* if true, we synced the entry to platform. We don't sync assumed devices */
-
- /* it makes sense to order sources based on their priority, without
- * actually adding a default route. This is useful to decide which
- * DNS server to prefer. never_default entries are not synced to platform. */
+ 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) entires 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;
@@ -104,6 +134,7 @@ typedef struct {
int addr_family;
GPtrArray *(*get_entries) (NMDefaultRouteManagerPrivate *priv);
const char *(*platform_route_to_string) (const NMPlatformIPRoute *route);
+ GArray *(*platform_route_get_all) (int ifindex, NMPlatformGetRouteMode mode);
gboolean (*platform_route_delete_default) (int ifindex, guint32 metric);
guint32 (*route_metric_normalize) (guint32 metric);
} VTableIP;
@@ -112,6 +143,43 @@ static const VTableIP vtable_ip4, vtable_ip6;
#define VTABLE_IS_IP4 (vtable->addr_family == AF_INET)
+static NMPlatformIPRoute *
+_vt_route_index (const VTableIP *vtable, GArray *routes, guint index)
+{
+ if (VTABLE_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_IS_IP4) {
+ for (i = 0; i < routes->len; i++) {
+ NMPlatformIP4Route *r = &g_array_index (routes, NMPlatformIP4Route, i);
+
+ route.rx.source = r->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.source = r->source;
+ if (nm_platform_ip6_route_cmp (r, &route.r6) == 0)
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
static void
_entry_free (Entry *entry)
{
@@ -141,13 +209,13 @@ _entry_find_by_source (GPtrArray *entries, gpointer source, guint *out_idx)
return NULL;
}
-static void
+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);
guint i;
- gboolean has_unsynced_entry = FALSE;
+ Entry *entry_unsynced = NULL;
Entry *entry = NULL;
gboolean success;
@@ -165,75 +233,76 @@ _platform_route_sync_add (const VTableIP *vtable, NMDefaultRouteManager *self, g
if (e->synced) {
g_assert (!entry || metric == G_MAXUINT32);
- entry = e;
+ if (!entry)
+ entry = e;
} else
- has_unsynced_entry = TRUE;
+ entry_unsynced = e;
}
- /* For synced entries, we expect that the metric is chosen uniquely. */
- g_assert (!entry || !has_unsynced_entry || metric == G_MAXUINT32);
+ /* 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;
+ return FALSE;
if (VTABLE_IS_IP4) {
- success = nm_platform_ip4_route_add (entry->route.ifindex,
- entry->route.source,
+ success = nm_platform_ip4_route_add (entry->route.rx.ifindex,
+ entry->route.rx.source,
0,
0,
- entry->route4.gateway,
+ entry->route.r4.gateway,
entry->effective_metric,
- entry->route.mss);
+ entry->route.rx.mss);
} else {
- success = nm_platform_ip6_route_add (entry->route.ifindex,
- entry->route.source,
+ success = nm_platform_ip6_route_add (entry->route.rx.ifindex,
+ entry->route.rx.source,
in6addr_any,
0,
- entry->route6.gateway,
+ entry->route.r6.gateway,
entry->effective_metric,
- entry->route.mss);
+ entry->route.rx.mss);
+ }
+ if (!success) {
+ _LOGW (vtable->addr_family, "failed to add default route %s with effective metric %u",
+ vtable->platform_route_to_string (&entry->route.rx), (guint) entry->effective_metric);
}
- if (!success)
- _LOGW (vtable->addr_family, "failed to add default route %s with effective metric %u", vtable->platform_route_to_string (&entry->route), (guint) entry->effective_metric);
+ return TRUE;
}
-static void
-_platform_route_sync_flush (const VTableIP *vtable, NMDefaultRouteManager *self)
+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. */
- if (VTABLE_IS_IP4)
- routes = nm_platform_ip4_route_get_all (0, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT);
- else
- routes = nm_platform_ip6_route_get_all (0, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT);
+ routes = vtable->platform_route_get_all (0, NM_PLATFORM_GET_ROUTE_MODE_ONLY_DEFAULT);
for (i = 0; i < routes->len; i++) {
const NMPlatformIPRoute *route;
gboolean has_ifindex_synced = FALSE;
Entry *entry = NULL;
- if (VTABLE_IS_IP4)
- route = (const NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP4Route, i);
- else
- route = (const NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP6Route, i);
+ route = _vt_route_index (vtable, routes, i);
/* look at all entires 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->never_default)
- continue;
-
- if ( e->route.ifindex == route->ifindex
+ if ( e->route.rx.ifindex == route->ifindex
&& e->synced) {
has_ifindex_synced = TRUE;
- if (e->effective_metric == route->metric)
+ if ( !e->never_default
+ && e->effective_metric == route->metric)
entry = e;
}
}
@@ -245,10 +314,14 @@ _platform_route_sync_flush (const VTableIP *vtable, NMDefaultRouteManager *self)
* Otherwise, don't delete the route because it's configured
* externally (and will be assumed -- or already is assumed).
*/
- if (has_ifindex_synced && !entry)
+ if ( !entry
+ && (has_ifindex_synced || ifindex_to_flush == route->ifindex)) {
vtable->platform_route_delete_default (route->ifindex, route->metric);
+ changed = TRUE;
+ }
}
g_array_free (routes, TRUE);
+ return changed;
}
static int
@@ -259,8 +332,8 @@ _sort_entries_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
const Entry *e_b = *((const Entry **) b);
/* when comparing routes, we consider the (original) metric. */
- m_a = e_a->route.metric;
- m_b = e_b->route.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 */
@@ -293,24 +366,93 @@ _sort_entries_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
return 0;
}
-static void
-_resync_all (const VTableIP *vtable, NMDefaultRouteManager *self, const Entry *changed_entry, const Entry *old_entry, gboolean do_sync)
+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->route_metric_normalize (route->metric)));
+ }
+
+ return result;
+}
+
+static int
+_sort_metrics_ascending_fcn (gconstpointer a, gconstpointer b)
+{
+ guint32 m_a = *((guint32 *) a);
+ guint32 m_b = *((guint32 *) b);
+
+ if (m_a < m_b)
+ return -1;
+ return m_a == m_b ? 0 : 1;
+}
+
+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;
+ guint i, j;
gint64 last_metric = -1;
guint32 expected_metric;
GPtrArray *entries;
- GHashTableIter iter;
- gpointer ptr;
- GHashTable *changed_metrics = g_hash_table_new (NULL, NULL);
+ 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_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);
- if (old_entry && old_entry->synced) {
+ routes = vtable->platform_route_get_all (0, NM_PLATFORM_GET_ROUTE_MODE_ONLY_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_hash_table_add (changed_metrics, GUINT_TO_POINTER (old_entry->effective_metric));
+ g_array_append_val (changed_metrics, old_entry->effective_metric);
}
/* first iterate over all entries and adjust the effective metrics. */
@@ -323,44 +465,122 @@ _resync_all (const VTableIP *vtable, NMDefaultRouteManager *self, const Entry *c
continue;
if (!entry->synced) {
- last_metric = MAX (last_metric, (gint64) entry->effective_metric);
+ 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.metric;
+ 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_hash_table_add (changed_metrics, GUINT_TO_POINTER (expected_metric));
- if (old_entry)
- _LOGD (vtable->addr_family, LOG_ENTRY_FMT": update %s (%u -> %u)", LOG_ENTRY_ARGS (i, entry), vtable->platform_route_to_string (&entry->route), (guint) old_entry->effective_metric, (guint) expected_metric);
- else
- _LOGD (vtable->addr_family, LOG_ENTRY_FMT": add %s (%u)", LOG_ENTRY_ARGS (i, entry), vtable->platform_route_to_string (&entry->route), (guint) expected_metric);
+ g_array_append_val (changed_metrics, expected_metric);
+ if (old_entry) {
+ _LOGD (vtable->addr_family, LOG_ENTRY_FMT": update %s (%u -> %u)", LOG_ENTRY_ARGS (i, entry),
+ vtable->platform_route_to_string (&entry->route.rx), (guint) old_entry->effective_metric,
+ (guint) expected_metric);
+ } else {
+ _LOGD (vtable->addr_family, LOG_ENTRY_FMT": add %s (%u)", LOG_ENTRY_ARGS (i, entry),
+ vtable->platform_route_to_string (&entry->route.rx), (guint) expected_metric);
+ }
} else if (entry->effective_metric != expected_metric) {
- g_hash_table_add (changed_metrics, GUINT_TO_POINTER (entry->effective_metric));
- g_hash_table_add (changed_metrics, GUINT_TO_POINTER (expected_metric));
- _LOGD (vtable->addr_family, LOG_ENTRY_FMT": resync metric %s (%u -> %u)", LOG_ENTRY_ARGS (i, entry), vtable->platform_route_to_string (&entry->route), (guint) entry->effective_metric, (guint) expected_metric);
+ g_array_append_val (changed_metrics, entry->effective_metric);
+ g_array_append_val (changed_metrics, expected_metric);
+ _LOGD (vtable->addr_family, LOG_ENTRY_FMT": resync metric %s (%u -> %u)", LOG_ENTRY_ARGS (i, entry),
+ vtable->platform_route_to_string (&entry->route.rx), (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);
+ _LOGD (vtable->addr_family, LOG_ENTRY_FMT": readd route %s (%u -> %u)", LOG_ENTRY_ARGS (i, entry),
+ vtable->platform_route_to_string (&entry->route.rx), (guint) entry->effective_metric,
+ (guint) entry->effective_metric);
+ }
}
- entry->effective_metric = expected_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 (changed_metrics, _sort_metrics_ascending_fcn);
+ 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 (do_sync) {
- g_hash_table_iter_init (&iter, changed_metrics);
- while (g_hash_table_iter_next (&iter, &ptr, NULL))
- _platform_route_sync_add (vtable, self, GPOINTER_TO_UINT (ptr));
- _platform_route_sync_flush (vtable, self);
+ 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;
}
- g_hash_table_unref (changed_metrics);
+ 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 void
-_entry_at_idx_update (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx, const Entry *old_entry, gboolean do_sync)
+_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;
@@ -372,24 +592,23 @@ _entry_at_idx_update (const VTableIP *vtable, NMDefaultRouteManager *self, guint
entry = g_ptr_array_index (entries, entry_idx);
g_assert ( !old_entry
- || (entry->source.pointer == old_entry->source.pointer && entry->route.ifindex == old_entry->route.ifindex));
+ || (entry->source.pointer == old_entry->source.pointer && entry->route.rx.ifindex == old_entry->route.rx.ifindex));
- if (!entry->synced) {
- entry->effective_metric = entry->route.metric;
- _LOGD (vtable->addr_family, LOG_ENTRY_FMT": %s %s%s",
- LOG_ENTRY_ARGS (entry_idx, entry),
- old_entry ? "update" : "add",
- vtable->platform_route_to_string (&entry->route),
- entry->never_default ? " (never-default)" : (entry->synced ? "" : " (not synced)"));
- }
+ if (!entry->synced && !entry->never_default)
+ entry->effective_metric = entry->route.rx.metric;
+
+ _LOGD (vtable->addr_family, LOG_ENTRY_FMT": %s %s",
+ LOG_ENTRY_ARGS (entry_idx, entry),
+ old_entry ? "update" : "add",
+ vtable->platform_route_to_string (&entry->route.rx));
g_ptr_array_sort_with_data (entries, _sort_entries_cmp, NULL);
- _resync_all (vtable, self, entry, old_entry, do_sync);
+ _resync_all (vtable, self, entry, old_entry, FALSE);
}
static void
-_entry_at_idx_remove (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx, gboolean do_sync)
+_entry_at_idx_remove (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx)
{
NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
Entry *entry;
@@ -401,13 +620,14 @@ _entry_at_idx_remove (const VTableIP *vtable, NMDefaultRouteManager *self, guint
entry = g_ptr_array_index (entries, entry_idx);
- _LOGD (vtable->addr_family, LOG_ENTRY_FMT": remove %s (%u%s)", LOG_ENTRY_ARGS (entry_idx, entry), vtable->platform_route_to_string (&entry->route), (guint) entry->effective_metric, entry->synced ? "" : ", not synced");
+ _LOGD (vtable->addr_family, LOG_ENTRY_FMT": remove %s (%u)", LOG_ENTRY_ARGS (entry_idx, entry),
+ vtable->platform_route_to_string (&entry->route.rx), (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);
- _resync_all (vtable, self, NULL, entry, do_sync);
+ _resync_all (vtable, self, NULL, entry, FALSE);
_entry_free (entry);
}
@@ -421,17 +641,13 @@ _ipx_update_default_route (const VTableIP *vtable, NMDefaultRouteManager *self,
Entry *entry;
guint entry_idx;
const NMPlatformIPRoute *default_route = NULL;
- union {
- NMPlatformIPRoute vx;
- NMPlatformIP4Route v4;
- NMPlatformIP6Route v6;
- } rt;
+ NMPlatformIPXRoute rt;
int ip_ifindex;
GPtrArray *entries;
NMDevice *device = NULL;
NMVpnConnection *vpn = NULL;
gboolean never_default = FALSE;
- gboolean synced;
+ gboolean synced = FALSE;
g_return_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self));
if (NM_IS_DEVICE (source))
@@ -460,14 +676,14 @@ _ipx_update_default_route (const VTableIP *vtable, NMDefaultRouteManager *self,
entry = _entry_find_by_source (entries, source, &entry_idx);
if ( entry
- && entry->route.ifindex != ip_ifindex) {
+ && entry->route.rx.ifindex != ip_ifindex) {
/* Strange... the ifindex changed... Remove the device and start again. */
_LOGD (vtable->addr_family, "ifindex of "LOG_ENTRY_FMT" changed: %d -> %d",
LOG_ENTRY_ARGS (entry_idx, entry),
- entry->route.ifindex, ip_ifindex);
+ entry->route.rx.ifindex, ip_ifindex);
g_object_freeze_notify (G_OBJECT (self));
- _entry_at_idx_remove (vtable, self, entry_idx, FALSE);
+ _entry_at_idx_remove (vtable, self, entry_idx);
g_assert (!_entry_find_by_source (entries, source, NULL));
_ipx_update_default_route (vtable, self, source);
g_object_thaw_notify (G_OBJECT (self));
@@ -477,29 +693,48 @@ _ipx_update_default_route (const VTableIP *vtable, NMDefaultRouteManager *self,
/* get the @default_route from the device. */
if (ip_ifindex > 0) {
if (device) {
+ gboolean is_assumed;
+
if (VTABLE_IS_IP4)
- default_route = (const NMPlatformIPRoute *) nm_device_get_ip4_default_route (device);
+ 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);
+ 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.source = NM_IP_CONFIG_SOURCE_UNKNOWN;
+ rt.rx.metric = G_MAXUINT32;
+ default_route = &rt.rx;
+
+ never_default = TRUE;
+ synced = TRUE;
+ } else
+ synced = default_route && !is_assumed;
} else {
NMConnection *connection = nm_active_connection_get_connection ((NMActiveConnection *) vpn);
if ( connection
&& nm_vpn_connection_get_vpn_state (vpn) == NM_VPN_CONNECTION_STATE_ACTIVATED) {
+ memset (&rt, 0, sizeof (rt));
if (VTABLE_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);
- memset (&rt.v4, 0, sizeof (rt.v4));
- rt.v4.ifindex = ip_ifindex;
- rt.v4.source = NM_IP_CONFIG_SOURCE_VPN;
- rt.v4.gateway = nm_vpn_connection_get_ip4_internal_gateway (vpn);
- rt.v4.metric = nm_vpn_connection_get_ip4_route_metric (vpn);
- rt.v4.mss = nm_ip4_config_get_mss (vpn_config);
- default_route = &rt.vx;
+ rt.r4.ifindex = ip_ifindex;
+ rt.r4.source = NM_IP_CONFIG_SOURCE_VPN;
+ rt.r4.gateway = nm_vpn_connection_get_ip4_internal_gateway (vpn);
+ 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;
@@ -509,24 +744,20 @@ _ipx_update_default_route (const VTableIP *vtable, NMDefaultRouteManager *self,
const struct in6_addr *int_gw = nm_vpn_connection_get_ip6_internal_gateway (vpn);
never_default = nm_ip6_config_get_never_default (vpn_config);
- memset (&rt.v6, 0, sizeof (rt.v6));
- rt.v6.ifindex = ip_ifindex;
- rt.v6.source = NM_IP_CONFIG_SOURCE_VPN;
- rt.v6.gateway = int_gw ? *int_gw : in6addr_any;
- rt.v6.metric = nm_vpn_connection_get_ip6_route_metric (vpn);
- rt.v6.mss = nm_ip6_config_get_mss (vpn_config);
- default_route = &rt.vx;
+ rt.r6.ifindex = ip_ifindex;
+ rt.r6.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;
}
}
}
+ synced = TRUE;
}
}
g_assert (!default_route || default_route->plen == 0);
- /* if the source is never_default or the device uses an assumed connection,
- * we don't sync the route. */
- synced = !never_default && (!device || !nm_device_uses_assumed_connection (device));
-
if (!entry && !default_route)
/* nothing to do */;
else if (!entry) {
@@ -535,31 +766,31 @@ _ipx_update_default_route (const VTableIP *vtable, NMDefaultRouteManager *self,
entry->source.object = g_object_ref (source);
if (VTABLE_IS_IP4)
- entry->route4 = *((const NMPlatformIP4Route *) default_route);
+ entry->route.r4 = *((const NMPlatformIP4Route *) default_route);
else
- entry->route6 = *((const NMPlatformIP6Route *) default_route);
+ entry->route.r6 = *((const NMPlatformIP6Route *) default_route);
/* only use normalized metrics */
- entry->route.metric = vtable->route_metric_normalize (entry->route.metric);
- entry->route.ifindex = ip_ifindex;
+ entry->route.rx.metric = vtable->route_metric_normalize (entry->route.rx.metric);
+ entry->route.rx.ifindex = ip_ifindex;
entry->never_default = never_default;
- entry->effective_metric = entry->route.metric;
+ entry->effective_metric = entry->route.rx.metric;
entry->synced = synced;
g_ptr_array_add (entries, entry);
- _entry_at_idx_update (vtable, self, entries->len - 1, NULL, TRUE);
+ _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_IS_IP4)
- new_entry.route4 = *((const NMPlatformIP4Route *) default_route);
+ new_entry.route.r4 = *((const NMPlatformIP4Route *) default_route);
else
- new_entry.route6 = *((const NMPlatformIP6Route *) default_route);
+ new_entry.route.r6 = *((const NMPlatformIP6Route *) default_route);
/* only use normalized metrics */
- new_entry.route.metric = vtable->route_metric_normalize (new_entry.route.metric);
- new_entry.route.ifindex = ip_ifindex;
+ new_entry.route.rx.metric = vtable->route_metric_normalize (new_entry.route.rx.metric);
+ new_entry.route.rx.ifindex = ip_ifindex;
new_entry.never_default = never_default;
new_entry.synced = synced;
@@ -568,10 +799,10 @@ _ipx_update_default_route (const VTableIP *vtable, NMDefaultRouteManager *self,
old_entry = *entry;
*entry = new_entry;
- _entry_at_idx_update (vtable, self, entry_idx, &old_entry, TRUE);
+ _entry_at_idx_update (vtable, self, entry_idx, &old_entry);
} else {
/* delete */
- _entry_at_idx_remove (vtable, self, entry_idx, TRUE);
+ _entry_at_idx_remove (vtable, self, entry_idx);
}
}
@@ -589,35 +820,6 @@ nm_default_route_manager_ip6_update_default_route (NMDefaultRouteManager *self,
/***********************************************************************************/
-static void
-_ipx_remove_default_route (const VTableIP *vtable, NMDefaultRouteManager *self, gpointer source)
-{
- NMDefaultRouteManagerPrivate *priv;
- guint entry_idx;
-
- g_return_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self));
- g_return_if_fail (NM_IS_DEVICE (source) || NM_IS_VPN_CONNECTION (source));
-
- priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
-
- if (_entry_find_by_source (vtable->get_entries (priv), source, &entry_idx))
- _entry_at_idx_remove (vtable, self, entry_idx, TRUE);
-}
-
-void
-nm_default_route_manager_ip4_remove_default_route (NMDefaultRouteManager *self, gpointer source)
-{
- _ipx_remove_default_route (&vtable_ip4, self, source);
-}
-
-void
-nm_default_route_manager_ip6_remove_default_route (NMDefaultRouteManager *self, gpointer source)
-{
- _ipx_remove_default_route (&vtable_ip6, self, source);
-}
-
-/***********************************************************************************/
-
static gboolean
_ipx_connection_has_default_route (const VTableIP *vtable, NMDefaultRouteManager *self, NMConnection *connection)
{
@@ -686,7 +888,8 @@ _ipx_get_best_device (const VTableIP *vtable, NMDefaultRouteManager *self, const
if (!NM_IS_DEVICE (entry->source.pointer))
continue;
- g_assert (!entry->never_default);
+ if (entry->never_default)
+ continue;
if (g_slist_find ((GSList *) devices, entry->source.device))
return entry->source.pointer;
@@ -840,6 +1043,9 @@ _ipx_get_best_config (const VTableIP *vtable,
NMDevice *device = entry->source.device;
NMActRequest *req;
+ if (entry->never_default)
+ continue;
+
if (VTABLE_IS_IP4)
config_result = nm_device_get_ip4_config (device);
else
@@ -931,6 +1137,7 @@ static const VTableIP vtable_ip4 = {
.addr_family = AF_INET,
.get_entries = _v4_get_entries,
.platform_route_to_string = (const char *(*)(const NMPlatformIPRoute *)) nm_platform_ip4_route_to_string,
+ .platform_route_get_all = nm_platform_ip4_route_get_all,
.platform_route_delete_default = _v4_platform_route_delete_default,
.route_metric_normalize = _v4_route_metric_normalize,
};
@@ -939,6 +1146,7 @@ static const VTableIP vtable_ip6 = {
.addr_family = AF_INET6,
.get_entries = _v6_get_entries,
.platform_route_to_string = (const char *(*)(const NMPlatformIPRoute *)) nm_platform_ip6_route_to_string,
+ .platform_route_get_all = nm_platform_ip6_route_get_all,
.platform_route_delete_default = _v6_platform_route_delete_default,
.route_metric_normalize = nm_utils_ip6_route_metric_normalize,
};
@@ -957,13 +1165,168 @@ nm_default_route_manager_get ()
/***********************************************************************************/
+static gboolean
+_resync_idle_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;
+ priv->resync.idle_handle = 0;
+ 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 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 readd/remove default routes for
+ * the interfaces we manage, there could be the erronous 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");
+ priv->resync.idle_handle = g_idle_add ((GSourceFunc) _resync_idle_now, self);
+ } 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_ipx_route_changed_cb (const VTableIP *vtable,
+ NMDefaultRouteManager *self,
+ const NMPlatformIPRoute *route)
+{
+ NMDefaultRouteManagerPrivate *priv;
+
+ if (route && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
+ /* we only care about address changes or changes of default route. */
+ return;
+ }
+
+ priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
+
+ if (priv->resync.guard) {
+ /* callbacks while executing _resync_all() are ignored. */
+ return;
+ }
+
+ if (VTABLE_IS_IP4)
+ priv->resync.has_v4_changes = TRUE;
+ else
+ priv->resync.has_v6_changes = TRUE;
+
+ _resync_idle_reschedule (self);
+}
+
+static void
+_platform_ip4_address_changed_cb (NMPlatform *platform,
+ int ifindex,
+ gpointer platform_object,
+ NMPlatformSignalChangeType change_type,
+ NMPlatformReason reason,
+ NMDefaultRouteManager *self)
+{
+ _platform_ipx_route_changed_cb (&vtable_ip4, self, NULL);
+}
+
+static void
+_platform_ip6_address_changed_cb (NMPlatform *platform,
+ int ifindex,
+ gpointer platform_object,
+ NMPlatformSignalChangeType change_type,
+ NMPlatformReason reason,
+ NMDefaultRouteManager *self)
+{
+ _platform_ipx_route_changed_cb (&vtable_ip6, self, NULL);
+}
+
+static void
+_platform_ip4_route_changed_cb (NMPlatform *platform,
+ int ifindex,
+ gpointer platform_object,
+ NMPlatformSignalChangeType change_type,
+ NMPlatformReason reason,
+ NMDefaultRouteManager *self)
+{
+ _platform_ipx_route_changed_cb (&vtable_ip4, self, platform_object);
+}
+
+static void
+_platform_ip6_route_changed_cb (NMPlatform *platform,
+ int ifindex,
+ gpointer platform_object,
+ NMPlatformSignalChangeType change_type,
+ NMPlatformReason reason,
+ NMDefaultRouteManager *self)
+{
+ _platform_ipx_route_changed_cb (&vtable_ip6, self, platform_object);
+}
+
+/***********************************************************************************/
+
static void
nm_default_route_manager_init (NMDefaultRouteManager *self)
{
NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
+ NMPlatform *platform;
priv->entries_ip4 = g_ptr_array_new_full (0, (GDestroyNotify) _entry_free);
priv->entries_ip6 = g_ptr_array_new_full (0, (GDestroyNotify) _entry_free);
+
+ platform = nm_platform_get ();
+ g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_platform_ip4_address_changed_cb), self);
+ g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_platform_ip6_address_changed_cb), self);
+ g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_platform_ip4_route_changed_cb), self);
+ g_signal_connect (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_platform_ip6_route_changed_cb), self);
}
static void
@@ -981,6 +1344,10 @@ dispose (GObject *object)
priv->entries_ip6 = NULL;
}
+ _resync_idle_cancel (self);
+
+ g_signal_handlers_disconnect_by_data (nm_platform_get (), self);
+
G_OBJECT_CLASS (nm_default_route_manager_parent_class)->dispose (object);
}
diff --git a/src/nm-default-route-manager.h b/src/nm-default-route-manager.h
index 88fb59f0c9..d8e422735e 100644
--- a/src/nm-default-route-manager.h
+++ b/src/nm-default-route-manager.h
@@ -51,9 +51,6 @@ NMDefaultRouteManager *nm_default_route_manager_get (void);
void nm_default_route_manager_ip4_update_default_route (NMDefaultRouteManager *manager, gpointer source);
void nm_default_route_manager_ip6_update_default_route (NMDefaultRouteManager *manager, gpointer source);
-void nm_default_route_manager_ip4_remove_default_route (NMDefaultRouteManager *manager, gpointer source);
-void nm_default_route_manager_ip6_remove_default_route (NMDefaultRouteManager *manager, gpointer source);
-
gboolean nm_default_route_manager_ip4_connection_has_default_route (NMDefaultRouteManager *manager, NMConnection *connection);
gboolean nm_default_route_manager_ip6_connection_has_default_route (NMDefaultRouteManager *manager, NMConnection *connection);
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index 3514dd084d..34ed384af7 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -175,6 +175,12 @@ struct _NMPlatformIP6Address {
};
G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPAddress, address_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Address, address));
+typedef union {
+ NMPlatformIPAddress ax;
+ NMPlatformIP4Address a4;
+ NMPlatformIP6Address a6;
+} NMPlatformIPXAddress;
+
#undef __NMPlatformIPAddress_COMMON
@@ -215,6 +221,12 @@ struct _NMPlatformIP6Route {
};
G_STATIC_ASSERT (G_STRUCT_OFFSET (NMPlatformIPRoute, network_ptr) == G_STRUCT_OFFSET (NMPlatformIP6Route, network));
+typedef union {
+ NMPlatformIPRoute rx;
+ NMPlatformIP4Route r4;
+ NMPlatformIP6Route r6;
+} NMPlatformIPXRoute;
+
#undef __NMPlatformIPRoute_COMMON
diff --git a/src/vpn-manager/nm-vpn-connection.c b/src/vpn-manager/nm-vpn-connection.c
index 682e1dc5b7..be1ce60f93 100644
--- a/src/vpn-manager/nm-vpn-connection.c
+++ b/src/vpn-manager/nm-vpn-connection.c
@@ -237,9 +237,6 @@ vpn_cleanup (NMVpnConnection *connection, NMDevice *parent_dev)
nm_platform_address_flush (priv->ip_ifindex);
}
- nm_default_route_manager_ip4_remove_default_route (nm_default_route_manager_get (), connection);
- nm_default_route_manager_ip6_remove_default_route (nm_default_route_manager_get (), connection);
-
nm_device_set_vpn4_config (parent_dev, NULL);
nm_device_set_vpn6_config (parent_dev, NULL);
@@ -327,10 +324,8 @@ _set_vpn_state (NMVpnConnection *connection,
dispatcher_cleanup (connection);
- if (vpn_state >= STATE_DISCONNECTED && vpn_state <= STATE_FAILED) {
- nm_default_route_manager_ip4_remove_default_route (nm_default_route_manager_get (), connection);
- nm_default_route_manager_ip6_remove_default_route (nm_default_route_manager_get (), connection);
- }
+ nm_default_route_manager_ip4_update_default_route (nm_default_route_manager_get (), connection);
+ nm_default_route_manager_ip6_update_default_route (nm_default_route_manager_get (), connection);
/* The connection gets destroyed by the VPN manager when it enters the
* disconnected/failed state, but we need to keep it around for a bit