mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-25 22:20:08 +01:00
Arguably, we currently only have one instance of NMPlatform, NMRouteManager, NMDefaultRouteManager -- the one owned by the NMNetns singleton. Hence, all these instances we create with "log-with-ptr" set explicitly to false. In the future we want to support namespaces, and it will be be common to have multiple instances. For that we have "log-with-ptr" so we are able to disambiguiate the logging. Change the default to TRUE because it makes more sense. It has currently no effect as the default is never used.
1565 lines
50 KiB
C
1565 lines
50 KiB
C
/* -*- 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 "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);
|
|
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;
|
|
|
|
rt.network = 0;
|
|
rt.plen = 0;
|
|
rt.metric = entry->effective_metric;
|
|
|
|
success = nm_platform_ip4_route_add (priv->platform, &rt);
|
|
} else {
|
|
NMPlatformIP6Route rt = entry->route.r6;
|
|
|
|
rt.network = in6addr_any;
|
|
rt.plen = 0;
|
|
rt.metric = entry->effective_metric;
|
|
|
|
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);
|
|
}
|