diff options
author | Thomas Haller <thaller@redhat.com> | 2014-11-19 23:01:05 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2014-11-19 23:01:51 +0100 |
commit | 5338178d8161a2f37d756896be43859b3b86ff3e (patch) | |
tree | deca8cdcc8eb37c2fe28fc8d179841d08447a33f | |
parent | 9f5cff0bb3429078cdbb9ada955a7dbe17d3c7b0 (diff) | |
parent | a0f81f266bac2a65385ef0b252c461ac152e85b9 (diff) | |
download | NetworkManager-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.c | 71 | ||||
-rw-r--r-- | src/devices/nm-device.h | 4 | ||||
-rw-r--r-- | src/nm-default-route-manager.c | 689 | ||||
-rw-r--r-- | src/nm-default-route-manager.h | 3 | ||||
-rw-r--r-- | src/platform/nm-platform.h | 12 | ||||
-rw-r--r-- | src/vpn-manager/nm-vpn-connection.c | 9 |
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 |