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