NetworkManager/src/nm-default-route-manager.c
Thomas Haller 41148caba8 core: enable "log-with-ptr" by default for platform and route-manager
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.
2017-04-18 15:49:14 +02:00

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);
}