mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-01-01 08:20:11 +01:00
Keep the include paths clean and separate. We use directories to group source files together. That makes sense (I guess), but then we should use this grouping also when including files. Thus require to #include files with their path relative to "src/". Also, we build various artifacts from the "src/" tree. Instead of having individual CFLAGS for each artifact in Makefile.am, the CFLAGS should be unified. Previously, the CFLAGS for each artifact differ and are inconsistent in which paths they add to the search path. Fix the inconsistency by just don't add the paths at all.
1497 lines
49 KiB
C
1497 lines
49 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_PLATFORM,
|
|
);
|
|
|
|
typedef struct {
|
|
GPtrArray *entries_ip4;
|
|
GPtrArray *entries_ip6;
|
|
struct {
|
|
guint guard;
|
|
guint backoff_wait_time_ms;
|
|
guint idle_handle;
|
|
gboolean has_v4_changes;
|
|
gboolean has_v6_changes;
|
|
} resync;
|
|
|
|
/* 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. */
|
|
gboolean disposed;
|
|
|
|
NMPlatform *platform;
|
|
} 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)
|
|
|
|
NM_DEFINE_SINGLETON_GETTER (NMDefaultRouteManager, nm_default_route_manager_get, NM_TYPE_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, \
|
|
"%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
|
|
self != singleton_instance \
|
|
? 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, \
|
|
"%s: entry[%u/%s:%p:%s:%c:%csync]: "_NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
|
|
self != singleton_instance \
|
|
? 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 ? '0' : '1'), \
|
|
(__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) {
|
|
success = nm_platform_ip4_route_add (priv->platform,
|
|
entry->route.rx.ifindex,
|
|
entry->route.rx.rt_source,
|
|
0,
|
|
0,
|
|
entry->route.r4.gateway,
|
|
0,
|
|
entry->effective_metric,
|
|
entry->route.rx.mss);
|
|
} else {
|
|
success = nm_platform_ip6_route_add (priv->platform,
|
|
entry->route.rx.ifindex,
|
|
entry->route.rx.rt_source,
|
|
in6addr_any,
|
|
0,
|
|
entry->route.r6.gateway,
|
|
entry->effective_metric,
|
|
entry->route.rx.mss);
|
|
}
|
|
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 int
|
|
_sort_metrics_ascending_fcn (gconstpointer a, gconstpointer b)
|
|
{
|
|
guint32 m_a = *((guint32 *) a);
|
|
guint32 m_b = *((guint32 *) b);
|
|
|
|
if (m_a < m_b)
|
|
return -1;
|
|
return m_a == m_b ? 0 : 1;
|
|
}
|
|
|
|
static gboolean
|
|
_resync_all (const VTableIP *vtable, NMDefaultRouteManager *self, const Entry *changed_entry, const Entry *old_entry, gboolean external_change)
|
|
{
|
|
NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
|
|
Entry *entry;
|
|
guint i, 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);
|
|
|
|
g_assert (entry != old_entry);
|
|
|
|
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: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:add %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 (changed_metrics, _sort_metrics_ascending_fcn);
|
|
last_metric = -1;
|
|
for (j = 0; j < changed_metrics->len; j++) {
|
|
expected_metric = g_array_index (changed_metrics, guint32, j);
|
|
|
|
if (last_metric == (gint64) expected_metric) {
|
|
/* skip duplicates. */
|
|
continue;
|
|
}
|
|
changed |= _platform_route_sync_add (vtable, self, expected_metric);
|
|
last_metric = expected_metric;
|
|
}
|
|
|
|
if ( 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 void
|
|
_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 ? "record:update" : "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);
|
|
|
|
_resync_all (vtable, self, entry, old_entry, FALSE);
|
|
}
|
|
|
|
static void
|
|
_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;
|
|
|
|
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);
|
|
|
|
_resync_all (vtable, self, NULL, entry, FALSE);
|
|
|
|
_entry_free (entry);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
_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;
|
|
|
|
g_return_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self));
|
|
|
|
priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
|
|
if (priv->disposed)
|
|
return;
|
|
|
|
if (NM_IS_DEVICE (source))
|
|
device = source;
|
|
else if (NM_IS_VPN_CONNECTION (source))
|
|
vpn = source;
|
|
else
|
|
g_return_if_reached ();
|
|
|
|
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));
|
|
_ipx_update_default_route (vtable, self, source);
|
|
g_object_thaw_notify (G_OBJECT (self));
|
|
return;
|
|
}
|
|
|
|
/* get the @default_route from the device. */
|
|
if (ip_ifindex > 0) {
|
|
if (device) {
|
|
gboolean is_assumed;
|
|
|
|
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 = TRUE;
|
|
} else
|
|
synced = default_route && !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 */;
|
|
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);
|
|
_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)
|
|
return;
|
|
|
|
old_entry = *entry;
|
|
*entry = new_entry;
|
|
_entry_at_idx_update (vtable, self, entry_idx, &old_entry);
|
|
} else {
|
|
/* delete */
|
|
_entry_at_idx_remove (vtable, self, entry_idx);
|
|
}
|
|
}
|
|
|
|
void
|
|
nm_default_route_manager_ip4_update_default_route (NMDefaultRouteManager *self, gpointer source)
|
|
{
|
|
_ipx_update_default_route (&vtable_ip4, self, source);
|
|
}
|
|
|
|
void
|
|
nm_default_route_manager_ip6_update_default_route (NMDefaultRouteManager *self, gpointer source)
|
|
{
|
|
_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_idle_now (NMDefaultRouteManager *self)
|
|
{
|
|
gboolean has_v4_changes, has_v6_changes;
|
|
gboolean changed = FALSE;
|
|
|
|
NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
|
|
|
|
has_v4_changes = priv->resync.has_v4_changes;
|
|
has_v6_changes = priv->resync.has_v6_changes;
|
|
|
|
_LOGD (0, "resync: sync now (%u) (IPv4 changes: %s, IPv6 changes: %s)", priv->resync.idle_handle,
|
|
has_v4_changes ? "yes" : "no", has_v6_changes ? "yes" : "no");
|
|
|
|
priv->resync.has_v4_changes = FALSE;
|
|
priv->resync.has_v6_changes = FALSE;
|
|
priv->resync.idle_handle = 0;
|
|
priv->resync.backoff_wait_time_ms =
|
|
priv->resync.backoff_wait_time_ms == 0
|
|
? 100
|
|
: priv->resync.backoff_wait_time_ms * 2;
|
|
|
|
if (has_v4_changes)
|
|
changed |= _resync_all (&vtable_ip4, self, NULL, NULL, TRUE);
|
|
|
|
if (has_v6_changes)
|
|
changed |= _resync_all (&vtable_ip6, self, NULL, NULL, TRUE);
|
|
|
|
if (!changed) {
|
|
/* Nothing changed: reset the backoff wait time */
|
|
_resync_idle_cancel (self);
|
|
}
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
_resync_idle_cancel (NMDefaultRouteManager *self)
|
|
{
|
|
NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
|
|
|
|
if (priv->resync.idle_handle) {
|
|
_LOGD (0, "resync: cancelled (%u)", priv->resync.idle_handle);
|
|
g_source_remove (priv->resync.idle_handle);
|
|
priv->resync.idle_handle = 0;
|
|
}
|
|
priv->resync.backoff_wait_time_ms = 0;
|
|
priv->resync.has_v4_changes = FALSE;
|
|
priv->resync.has_v6_changes = FALSE;
|
|
}
|
|
|
|
static void
|
|
_resync_idle_reschedule (NMDefaultRouteManager *self)
|
|
{
|
|
NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
|
|
|
|
/* since we react on external changes and 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_ipx_route_changed_cb (const VTableIP *vtable,
|
|
NMDefaultRouteManager *self,
|
|
const NMPlatformIPRoute *route)
|
|
{
|
|
NMDefaultRouteManagerPrivate *priv;
|
|
|
|
if (route && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
|
|
/* we only care about address changes or changes of default route. */
|
|
return;
|
|
}
|
|
|
|
priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
|
|
|
|
if (priv->resync.guard) {
|
|
/* callbacks while executing _resync_all() are ignored. */
|
|
return;
|
|
}
|
|
|
|
if (vtable->vt->is_ip4)
|
|
priv->resync.has_v4_changes = TRUE;
|
|
else
|
|
priv->resync.has_v6_changes = TRUE;
|
|
|
|
_resync_idle_reschedule (self);
|
|
}
|
|
|
|
static void
|
|
_platform_changed_cb (NMPlatform *platform,
|
|
int obj_type_i,
|
|
int ifindex,
|
|
gpointer platform_object,
|
|
int change_type_i,
|
|
NMDefaultRouteManager *self)
|
|
{
|
|
const NMPObjectType obj_type = obj_type_i;
|
|
|
|
switch (obj_type) {
|
|
case NMP_OBJECT_TYPE_IP4_ADDRESS:
|
|
_platform_ipx_route_changed_cb (&vtable_ip4, self, NULL);
|
|
break;
|
|
case NMP_OBJECT_TYPE_IP6_ADDRESS:
|
|
_platform_ipx_route_changed_cb (&vtable_ip6, self, NULL);
|
|
break;
|
|
case NMP_OBJECT_TYPE_IP4_ROUTE:
|
|
_platform_ipx_route_changed_cb (&vtable_ip4, self, (const NMPlatformIPRoute *) platform_object);
|
|
break;
|
|
case NMP_OBJECT_TYPE_IP6_ROUTE:
|
|
_platform_ipx_route_changed_cb (&vtable_ip6, self, (const NMPlatformIPRoute *) platform_object);
|
|
break;
|
|
default:
|
|
g_return_if_reached ();
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
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_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 (NMPlatform *platform)
|
|
{
|
|
return g_object_new (NM_TYPE_DEFAULT_ROUTE_MANAGER,
|
|
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_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);
|
|
}
|