1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager -- Network link manager
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Copyright (C) 2014 Red Hat, Inc.
22 #include "nm-default.h"
26 #include "nm-default-route-manager.h"
27 #include "nm-device.h"
28 #include "nm-vpn-connection.h"
29 #include "nm-platform.h"
30 #include "nm-manager.h"
31 #include "nm-ip4-config.h"
32 #include "nm-ip6-config.h"
33 #include "nm-activation-request.h"
36 GPtrArray *entries_ip4;
37 GPtrArray *entries_ip6;
40 guint backoff_wait_time_ms;
42 gboolean has_v4_changes;
43 gboolean has_v6_changes;
46 /* During disposing, we unref the sources of all entries. This happens usually
47 * during shutdown, which might call the final deletion of the object. That
48 * again might cause calls back into NMDefaultRouteManager, which finds dangling
50 * Guard every publicly accessible function to return early if the instance
51 * is already disposing. */
55 } NMDefaultRouteManagerPrivate;
57 #define NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEFAULT_ROUTE_MANAGER, NMDefaultRouteManagerPrivate))
59 G_DEFINE_TYPE (NMDefaultRouteManager, nm_default_route_manager, G_TYPE_OBJECT)
61 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
65 NM_DEFINE_SINGLETON_GETTER (NMDefaultRouteManager, nm_default_route_manager_get, NM_TYPE_DEFAULT_ROUTE_MANAGER);
67 #define _NMLOG_PREFIX_NAME "default-route"
69 #define _NMLOG_ENABLED(level, addr_family) \
71 const int __addr_family = (addr_family); \
72 const NMLogLevel __level = (level); \
73 const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
75 nm_logging_enabled (__level, __domain); \
77 #define _NMLOG(level, addr_family, ...) \
79 const int __addr_family = (addr_family); \
80 const NMLogLevel __level = (level); \
81 const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
83 if (nm_logging_enabled (__level, __domain)) { \
84 char __prefix_buf[100]; \
86 _nm_log (__level, __domain, 0, \
87 "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
88 self != singleton_instance \
89 ? nm_sprintf_buf (__prefix_buf, "%s%c[%p]", \
90 _NMLOG2_PREFIX_NAME, \
91 __addr_family == AF_INET ? '4' : (__addr_family == AF_INET6 ? '6' : '-'), \
93 : _NMLOG2_PREFIX_NAME \
94 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
98 #define _NMLOG2_PREFIX_NAME _NMLOG_PREFIX_NAME
99 #undef _NMLOG2_ENABLED
100 #define _NMLOG2_ENABLED _NMLOG_ENABLED
101 #define _NMLOG2(level, vtable, entry_idx, entry, ...) \
103 const int __addr_family = (vtable)->vt->addr_family; \
104 const NMLogLevel __level = (level); \
105 const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
107 if (nm_logging_enabled (__level, __domain)) { \
108 char __prefix_buf[100]; \
109 guint __entry_idx = (entry_idx); \
110 const Entry *const __entry = (entry); \
112 _nm_log (__level, __domain, 0, \
113 "%s: entry[%u/%s:%p:%s:%c:%csync]: "_NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
114 self != singleton_instance \
115 ? nm_sprintf_buf (__prefix_buf, "%s%c[%p]", \
116 _NMLOG2_PREFIX_NAME, \
117 __addr_family == AF_INET ? '4' : (__addr_family == AF_INET6 ? '6' : '-'), \
119 : _NMLOG2_PREFIX_NAME, \
121 NM_IS_DEVICE (__entry->source.pointer) ? "dev" : "vpn", \
122 __entry->source.pointer, \
123 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)), \
124 (__entry->never_default ? '0' : '1'), \
125 (__entry->synced ? '+' : '-') \
126 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
130 /***********************************************************************************/
132 static void _resync_idle_cancel (NMDefaultRouteManager *self);
134 /***********************************************************************************/
141 NMVpnConnection *vpn;
143 NMPlatformIPXRoute route;
145 /* Whether the route is synced to platform and has a default route.
147 * ( synced && !never_default): the interface gets a default route that
148 * is enforced and managed by NMDefaultRouteManager.
150 * (!synced && !never_default): the interface has this route, but it is assumed.
151 * Assumed interfaces are those that have no tracked entry or that only have
152 * (!synced && !never_default) entries. NMDefaultRouteManager will not touch
153 * default routes on these interfaces.
154 * This combination makes only sense for device sources.
155 * They are tracked so that assumed devices can also be the best device.
157 * ( synced && never_default): entries of this kind are a placeholder
158 * to indicate that the ifindex is managed but has no default-route.
159 * Missing entries also indicate that a certain ifindex has no default-route.
160 * The difference is that missing entries are considered assumed while on
161 * (synced && never_default) entries the absence of the default route
162 * is enforced. NMDefaultRouteManager will actively remove any default
163 * route on such ifindexes.
164 * Also, for VPN sources in addition we track them so that a never-default
165 * VPN connection can be choosen by get_best_config() to receive the DNS configuration.
167 * (!synced && never_default): this combination makes no sense.
170 gboolean never_default;
172 guint32 effective_metric;
176 const NMPlatformVTableRoute *vt;
177 GPtrArray *(*get_entries) (NMDefaultRouteManagerPrivate *priv);
180 static const VTableIP vtable_ip4, vtable_ip6;
182 static NMPlatformIPRoute *
183 _vt_route_index (const VTableIP *vtable, GArray *routes, guint index)
185 if (vtable->vt->is_ip4)
186 return (NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP4Route, index);
188 return (NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP6Route, index);
192 _vt_routes_has_entry (const VTableIP *vtable, GArray *routes, const Entry *entry)
195 NMPlatformIPXRoute route = entry->route;
197 route.rx.metric = entry->effective_metric;
199 if (vtable->vt->is_ip4) {
200 for (i = 0; i < routes->len; i++) {
201 NMPlatformIP4Route *r = &g_array_index (routes, NMPlatformIP4Route, i);
203 route.rx.source = r->source;
204 if (nm_platform_ip4_route_cmp (r, &route.r4) == 0)
208 for (i = 0; i < routes->len; i++) {
209 NMPlatformIP6Route *r = &g_array_index (routes, NMPlatformIP6Route, i);
211 route.rx.source = r->source;
212 if (nm_platform_ip6_route_cmp (r, &route.r6) == 0)
220 _entry_free (Entry *entry)
223 g_object_unref (entry->source.object);
224 g_slice_free (Entry, entry);
229 _entry_find_by_source (GPtrArray *entries, gpointer source, guint *out_idx)
233 for (i = 0; i < entries->len; i++) {
234 Entry *e = g_ptr_array_index (entries, i);
236 if (e->source.pointer == source) {
244 *out_idx = G_MAXUINT;
249 _platform_route_sync_add (const VTableIP *vtable, NMDefaultRouteManager *self, guint32 metric)
251 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
252 GPtrArray *entries = vtable->get_entries (priv);
254 Entry *entry_unsynced = NULL;
258 /* Find the entries for the given metric.
259 * The effective metric for synced entries is choosen in a way that it
260 * is unique (except for G_MAXUINT32, where a clash is not solvable). */
261 for (i = 0; i < entries->len; i++) {
262 Entry *e = g_ptr_array_index (entries, i);
264 if (e->never_default)
267 if (e->effective_metric != metric)
271 g_assert (!entry || metric == G_MAXUINT32);
278 /* We don't expect to have an unsynced *and* a synced entry for the same metric.
279 * Unless, (a) their metric is G_MAXUINT32, in which case we could not find an unused effective metric,
280 * or (b) if we have an unsynced and a synced entry for the same ifindex.
281 * The latter case happens for example when activating an openvpn connection (synced) and
282 * assuming the corresponding tun0 interface (unsynced). */
283 g_assert (!entry || !entry_unsynced || (entry->route.rx.ifindex == entry_unsynced->route.rx.ifindex) || metric == G_MAXUINT32);
285 /* we only add the route, if we have an (to be synced) entry for it. */
289 if (vtable->vt->is_ip4) {
290 success = nm_platform_ip4_route_add (priv->platform,
291 entry->route.rx.ifindex,
292 entry->route.rx.source,
295 entry->route.r4.gateway,
297 entry->effective_metric,
298 entry->route.rx.mss);
300 success = nm_platform_ip6_route_add (priv->platform,
301 entry->route.rx.ifindex,
302 entry->route.rx.source,
305 entry->route.r6.gateway,
306 entry->effective_metric,
307 entry->route.rx.mss);
310 _LOGW (vtable->vt->addr_family, "failed to add default route %s with effective metric %u",
311 vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric);
317 _platform_route_sync_flush (const VTableIP *vtable, NMDefaultRouteManager *self, int ifindex_to_flush)
319 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
320 GPtrArray *entries = vtable->get_entries (priv);
323 gboolean changed = FALSE;
325 /* prune all other default routes from this device. */
326 routes = vtable->vt->route_get_all (priv->platform, 0, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
328 for (i = 0; i < routes->len; i++) {
329 const NMPlatformIPRoute *route;
330 gboolean has_ifindex_synced = FALSE;
333 route = _vt_route_index (vtable, routes, i);
335 /* look at all entries and see if the route for this ifindex pair is
337 for (j = 0; j < entries->len; j++) {
338 Entry *e = g_ptr_array_index (entries, j);
340 if ( e->route.rx.ifindex == route->ifindex
342 has_ifindex_synced = TRUE;
343 if ( !e->never_default
344 && e->effective_metric == route->metric)
349 /* we only delete the route if we don't have a matching entry,
350 * and there is at least one entry that references this ifindex
351 * (indicating that the ifindex is managed by us -- not assumed).
353 * Otherwise, don't delete the route because it's configured
354 * externally (and will be assumed -- or already is assumed).
357 && (has_ifindex_synced || ifindex_to_flush == route->ifindex)) {
358 vtable->vt->route_delete_default (priv->platform, route->ifindex, route->metric);
362 g_array_free (routes, TRUE);
367 _sort_entries_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
370 const Entry *e_a = *((const Entry **) a);
371 const Entry *e_b = *((const Entry **) b);
373 /* when comparing routes, we consider the (original) metric. */
374 m_a = e_a->route.rx.metric;
375 m_b = e_b->route.rx.metric;
377 /* we normalize route.metric already in _ipx_update_default_route().
378 * so we can just compare the metrics numerically */
381 return (m_a < m_b) ? -1 : 1;
383 /* If the metrics are equal, we prefer the one that is !never_default */
384 if (!!e_a->never_default != !!e_b->never_default)
385 return e_a->never_default ? 1 : -1;
387 /* If the metrics are equal, we prefer the one that is assumed (!synced).
388 * Entries that we sync, can be modified so that only the best
389 * entry has a (deterministically) lowest metric.
390 * With assumed devices we cannot increase/change the metric.
391 * For example: two devices, both metric 0. One is assumed the other is
393 * If we would choose the synced entry as best, we cannot
394 * increase the metric of the assumed one and we would have non-determinism.
395 * If we instead prefer the assumed device, we can increase the metric
396 * of the synced device and the assumed device is (deterministically)
398 * If both devices are assumed, we also have non-determinism, but also
399 * we don't reorder either.
401 if (!!e_a->synced != !!e_b->synced)
402 return e_a->synced ? 1 : -1;
404 /* otherwise, do not reorder */
409 _get_assumed_interface_metrics (const VTableIP *vtable, NMDefaultRouteManager *self, GArray *routes)
411 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
416 /* create a list of all metrics that are currently assigned on an interface
417 * that is *not* already covered by one of our synced entries.
418 * IOW, returns the metrics that are in use by assumed interfaces
419 * that we want to preserve. */
421 entries = vtable->get_entries (priv);
423 result = g_hash_table_new (NULL, NULL);
425 for (i = 0; i < routes->len; i++) {
426 gboolean ifindex_has_synced_entry = FALSE;
427 const NMPlatformIPRoute *route;
429 route = _vt_route_index (vtable, routes, i);
431 for (j = 0; j < entries->len; j++) {
432 Entry *e = g_ptr_array_index (entries, j);
435 && e->route.rx.ifindex == route->ifindex) {
436 ifindex_has_synced_entry = TRUE;
441 if (!ifindex_has_synced_entry)
442 g_hash_table_add (result, GUINT_TO_POINTER (vtable->vt->metric_normalize (route->metric)));
445 /* also add all non-synced metrics from our entries list. We might have there some metrics that
446 * we track as non-synced but that are no longer part of platform routes. Anyway, for now
447 * we still want to treat them as assumed. */
448 for (i = 0; i < entries->len; i++) {
449 gboolean ifindex_has_synced_entry = FALSE;
450 Entry *e_i = g_ptr_array_index (entries, i);
455 for (j = 0; j < entries->len; j++) {
456 Entry *e_j = g_ptr_array_index (entries, j);
459 && (e_j->synced && e_j->route.rx.ifindex == e_i->route.rx.ifindex)) {
460 ifindex_has_synced_entry = TRUE;
465 if (!ifindex_has_synced_entry)
466 g_hash_table_add (result, GUINT_TO_POINTER (vtable->vt->metric_normalize (e_i->route.rx.metric)));
473 _sort_metrics_ascending_fcn (gconstpointer a, gconstpointer b)
475 guint32 m_a = *((guint32 *) a);
476 guint32 m_b = *((guint32 *) b);
480 return m_a == m_b ? 0 : 1;
484 _resync_all (const VTableIP *vtable, NMDefaultRouteManager *self, const Entry *changed_entry, const Entry *old_entry, gboolean external_change)
486 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
489 gint64 last_metric = -1;
490 guint32 expected_metric;
492 GArray *changed_metrics = g_array_new (FALSE, FALSE, sizeof (guint32));
493 GHashTable *assumed_metrics;
495 gboolean changed = FALSE;
496 int ifindex_to_flush = 0;
498 g_assert (priv->resync.guard == 0);
499 priv->resync.guard++;
501 if (!external_change) {
502 if (vtable->vt->is_ip4)
503 priv->resync.has_v4_changes = FALSE;
505 priv->resync.has_v6_changes = FALSE;
506 if (!priv->resync.has_v4_changes && !priv->resync.has_v6_changes)
507 _resync_idle_cancel (self);
510 entries = vtable->get_entries (priv);
512 routes = vtable->vt->route_get_all (priv->platform, 0, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
514 assumed_metrics = _get_assumed_interface_metrics (vtable, self, routes);
516 if (old_entry && old_entry->synced && !old_entry->never_default) {
517 /* The old version obviously changed. */
518 g_array_append_val (changed_metrics, old_entry->effective_metric);
521 /* first iterate over all entries and adjust the effective metrics. */
522 for (i = 0; i < entries->len; i++) {
523 entry = g_ptr_array_index (entries, i);
525 g_assert (entry != old_entry);
527 if (entry->never_default)
530 if (!entry->synced) {
531 gboolean has_synced_entry = FALSE;
533 /* A non synced entry is completely ignored, if we have
534 * a synced entry for the same if index.
535 * Otherwise the metric of the entry is still remembered as
536 * last_metric to avoid reusing it. */
537 for (j = 0; j < entries->len; j++) {
538 const Entry *e = g_ptr_array_index (entries, j);
541 && e->route.rx.ifindex == entry->route.rx.ifindex) {
542 has_synced_entry = TRUE;
546 if (!has_synced_entry)
547 last_metric = MAX (last_metric, (gint64) entry->effective_metric);
551 expected_metric = entry->route.rx.metric;
552 if ((gint64) expected_metric <= last_metric)
553 expected_metric = last_metric == G_MAXUINT32 ? G_MAXUINT32 : last_metric + 1;
555 while ( expected_metric < G_MAXUINT32
556 && g_hash_table_contains (assumed_metrics, GUINT_TO_POINTER (expected_metric))) {
557 gboolean has_metric_for_ifindex = FALSE;
559 /* Check if there are assumed devices that have default routes with this metric.
560 * If there are any, we have to pick another effective_metric. */
562 /* However, if there is a matching route (ifindex+metric) for our current entry, we are done. */
563 for (j = 0; j < routes->len; j++) {
564 const NMPlatformIPRoute *r = _vt_route_index (vtable, routes, i);
566 if ( r->metric == expected_metric
567 && r->ifindex == entry->route.rx.ifindex) {
568 has_metric_for_ifindex = TRUE;
572 if (has_metric_for_ifindex)
577 if (changed_entry == entry) {
578 /* for the changed entry, the previous metric was either old_entry->effective_metric,
579 * or none. Hence, we only have to remember what is going to change. */
580 g_array_append_val (changed_metrics, expected_metric);
582 _LOG2D (vtable, i, entry, "sync:update %s (%u -> %u)",
583 vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) old_entry->effective_metric,
584 (guint) expected_metric);
586 _LOG2D (vtable, i, entry, "sync:add %s (%u)",
587 vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) expected_metric);
589 } else if (entry->effective_metric != expected_metric) {
590 g_array_append_val (changed_metrics, entry->effective_metric);
591 g_array_append_val (changed_metrics, expected_metric);
592 _LOG2D (vtable, i, entry, "sync:metric %s (%u -> %u)",
593 vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric,
594 (guint) expected_metric);
596 if (!_vt_routes_has_entry (vtable, routes, entry)) {
597 g_array_append_val (changed_metrics, entry->effective_metric);
598 _LOG2D (vtable, i, entry, "sync:re-add %s (%u -> %u)",
599 vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric,
600 (guint) entry->effective_metric);
604 if (entry->effective_metric != expected_metric) {
605 entry->effective_metric = expected_metric;
608 last_metric = expected_metric;
611 g_array_free (routes, TRUE);
613 g_array_sort (changed_metrics, _sort_metrics_ascending_fcn);
615 for (j = 0; j < changed_metrics->len; j++) {
616 expected_metric = g_array_index (changed_metrics, guint32, j);
618 if (last_metric == (gint64) expected_metric) {
619 /* skip duplicates. */
622 changed |= _platform_route_sync_add (vtable, self, expected_metric);
623 last_metric = expected_metric;
629 && !old_entry->never_default) {
630 /* If we entriely remove an entry that was synced before, we must make
631 * sure to flush routes for this ifindex too. Otherwise they linger
632 * around as "assumed" routes */
633 ifindex_to_flush = old_entry->route.rx.ifindex;
636 changed |= _platform_route_sync_flush (vtable, self, ifindex_to_flush);
638 g_array_free (changed_metrics, TRUE);
639 g_hash_table_unref (assumed_metrics);
641 priv->resync.guard--;
646 _entry_at_idx_update (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx, const Entry *old_entry)
648 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
652 entries = vtable->get_entries (priv);
653 g_assert (entry_idx < entries->len);
655 entry = g_ptr_array_index (entries, entry_idx);
657 g_assert ( !old_entry
658 || (entry->source.pointer == old_entry->source.pointer && entry->route.rx.ifindex == old_entry->route.rx.ifindex));
660 if (!entry->synced && !entry->never_default)
661 entry->effective_metric = entry->route.rx.metric;
663 _LOG2D (vtable, entry_idx, entry, "%s %s (%"G_GUINT32_FORMAT")",
664 old_entry ? "record:update" : "record:add ",
665 vtable->vt->route_to_string (&entry->route, NULL, 0),
666 entry->effective_metric);
668 g_ptr_array_sort_with_data (entries, _sort_entries_cmp, NULL);
670 _resync_all (vtable, self, entry, old_entry, FALSE);
674 _entry_at_idx_remove (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx)
676 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
680 entries = vtable->get_entries (priv);
682 g_assert (entry_idx < entries->len);
684 entry = g_ptr_array_index (entries, entry_idx);
686 _LOG2D (vtable, entry_idx, entry, "record:remove %s (%u)",
687 vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric);
689 /* Remove the entry from the list (but don't free it yet) */
690 g_ptr_array_index (entries, entry_idx) = NULL;
691 g_ptr_array_remove_index (entries, entry_idx);
693 _resync_all (vtable, self, NULL, entry, FALSE);
698 /***********************************************************************************/
701 _ipx_update_default_route (const VTableIP *vtable, NMDefaultRouteManager *self, gpointer source)
703 NMDefaultRouteManagerPrivate *priv;
706 const NMPlatformIPRoute *default_route = NULL;
707 NMPlatformIPXRoute rt;
710 NMDevice *device = NULL;
711 NMVpnConnection *vpn = NULL;
712 gboolean never_default = FALSE;
713 gboolean synced = FALSE;
715 g_return_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self));
717 priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
721 if (NM_IS_DEVICE (source))
723 else if (NM_IS_VPN_CONNECTION (source))
726 g_return_if_reached ();
729 ip_ifindex = nm_device_get_ip_ifindex (device);
731 ip_ifindex = nm_vpn_connection_get_ip_ifindex (vpn);
733 if (ip_ifindex <= 0) {
734 NMDevice *parent = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
737 ip_ifindex = nm_device_get_ip_ifindex (parent);
741 entries = vtable->get_entries (priv);
742 entry = _entry_find_by_source (entries, source, &entry_idx);
745 && entry->route.rx.ifindex != ip_ifindex) {
746 /* Strange... the ifindex changed... Remove the device and start again. */
747 _LOG2D (vtable, entry_idx, entry, "ifindex changed: %d -> %d",
748 entry->route.rx.ifindex, ip_ifindex);
750 g_object_freeze_notify (G_OBJECT (self));
751 _entry_at_idx_remove (vtable, self, entry_idx);
752 g_assert (!_entry_find_by_source (entries, source, NULL));
753 _ipx_update_default_route (vtable, self, source);
754 g_object_thaw_notify (G_OBJECT (self));
758 /* get the @default_route from the device. */
759 if (ip_ifindex > 0) {
763 if (vtable->vt->is_ip4)
764 default_route = (const NMPlatformIPRoute *) nm_device_get_ip4_default_route (device, &is_assumed);
766 default_route = (const NMPlatformIPRoute *) nm_device_get_ip6_default_route (device, &is_assumed);
767 if (!default_route && !is_assumed) {
768 /* the device has no default route, but it is not assumed. That means, NMDefaultRouteManager
769 * enforces that the device has no default route.
771 * Hence we have to keep track of this entry, otherwise a missing entry tells us
772 * that the interface is assumed and NM would not remove the default routes on
774 memset (&rt, 0, sizeof (rt));
775 rt.rx.ifindex = ip_ifindex;
776 rt.rx.source = NM_IP_CONFIG_SOURCE_UNKNOWN;
777 rt.rx.metric = G_MAXUINT32;
778 default_route = &rt.rx;
780 never_default = TRUE;
783 synced = default_route && !is_assumed;
785 NMConnection *connection = nm_active_connection_get_applied_connection ((NMActiveConnection *) vpn);
788 && nm_vpn_connection_get_vpn_state (vpn) == NM_VPN_CONNECTION_STATE_ACTIVATED) {
790 memset (&rt, 0, sizeof (rt));
791 if (vtable->vt->is_ip4) {
792 NMIP4Config *vpn_config;
794 vpn_config = nm_vpn_connection_get_ip4_config (vpn);
796 never_default = nm_ip4_config_get_never_default (vpn_config);
797 rt.r4.ifindex = ip_ifindex;
798 rt.r4.source = NM_IP_CONFIG_SOURCE_VPN;
799 rt.r4.gateway = nm_ip4_config_get_gateway (vpn_config);
800 rt.r4.metric = nm_vpn_connection_get_ip4_route_metric (vpn);
801 rt.r4.mss = nm_ip4_config_get_mss (vpn_config);
802 default_route = &rt.rx;
805 NMIP6Config *vpn_config;
807 vpn_config = nm_vpn_connection_get_ip6_config (vpn);
809 const struct in6_addr *int_gw = nm_ip6_config_get_gateway (vpn_config);
811 never_default = nm_ip6_config_get_never_default (vpn_config);
812 rt.r6.ifindex = ip_ifindex;
813 rt.r6.source = NM_IP_CONFIG_SOURCE_VPN;
814 rt.r6.gateway = int_gw ? *int_gw : in6addr_any;
815 rt.r6.metric = nm_vpn_connection_get_ip6_route_metric (vpn);
816 rt.r6.mss = nm_ip6_config_get_mss (vpn_config);
817 default_route = &rt.rx;
821 if (nm_vpn_connection_get_ip_ifindex (vpn) > 0)
824 /* a VPN connection without tunnel device cannot have a non-synced, missing default route.
825 * Either it has a default route (which is synced), or it has no entry. */
826 synced = default_route && !never_default;
831 g_assert (!default_route || default_route->plen == 0);
833 if (!synced && never_default) {
834 /* having a non-synced, never-default entry is non-sensical. Unset
835 * @default_route so that we don't add such an entry below. */
836 default_route = NULL;
839 if (!entry && !default_route)
843 entry = g_slice_new0 (Entry);
844 entry->source.object = g_object_ref (source);
846 if (vtable->vt->is_ip4)
847 entry->route.r4 = *((const NMPlatformIP4Route *) default_route);
849 entry->route.r6 = *((const NMPlatformIP6Route *) default_route);
851 /* only use normalized metrics */
852 entry->route.rx.metric = vtable->vt->metric_normalize (entry->route.rx.metric);
853 entry->route.rx.ifindex = ip_ifindex;
854 entry->never_default = never_default;
855 entry->effective_metric = entry->route.rx.metric;
856 entry->synced = synced;
858 g_ptr_array_add (entries, entry);
859 _entry_at_idx_update (vtable, self, entries->len - 1, NULL);
860 } else if (default_route) {
862 Entry old_entry, new_entry;
865 if (vtable->vt->is_ip4)
866 new_entry.route.r4 = *((const NMPlatformIP4Route *) default_route);
868 new_entry.route.r6 = *((const NMPlatformIP6Route *) default_route);
869 /* only use normalized metrics */
870 new_entry.route.rx.metric = vtable->vt->metric_normalize (new_entry.route.rx.metric);
871 new_entry.route.rx.ifindex = ip_ifindex;
872 new_entry.never_default = never_default;
873 new_entry.synced = synced;
875 if (memcmp (entry, &new_entry, sizeof (new_entry)) == 0)
880 _entry_at_idx_update (vtable, self, entry_idx, &old_entry);
883 _entry_at_idx_remove (vtable, self, entry_idx);
888 nm_default_route_manager_ip4_update_default_route (NMDefaultRouteManager *self, gpointer source)
890 _ipx_update_default_route (&vtable_ip4, self, source);
894 nm_default_route_manager_ip6_update_default_route (NMDefaultRouteManager *self, gpointer source)
896 _ipx_update_default_route (&vtable_ip6, self, source);
899 /***********************************************************************************/
902 _ipx_connection_has_default_route (const VTableIP *vtable, NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
905 NMSettingIPConfig *s_ip;
906 gboolean is_never_default = FALSE;
907 gboolean has_default_route = FALSE;
909 g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), FALSE);
914 if (vtable->vt->is_ip4)
915 s_ip = nm_connection_get_setting_ip4_config (connection);
917 s_ip = nm_connection_get_setting_ip6_config (connection);
920 if (nm_setting_ip_config_get_never_default (s_ip)) {
921 is_never_default = TRUE;
925 if (vtable->vt->is_ip4) {
926 method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
928 || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)
929 || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
932 method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
934 || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)
935 || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
939 has_default_route = TRUE;
941 if (out_is_never_default)
942 *out_is_never_default = is_never_default;
943 return has_default_route;
947 nm_default_route_manager_ip4_connection_has_default_route (NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
949 return _ipx_connection_has_default_route (&vtable_ip4, self, connection, out_is_never_default);
953 nm_default_route_manager_ip6_connection_has_default_route (NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
955 return _ipx_connection_has_default_route (&vtable_ip6, self, connection, out_is_never_default);
958 /***********************************************************************************/
961 _ipx_get_best_device (const VTableIP *vtable, NMDefaultRouteManager *self, const GSList *devices)
963 NMDefaultRouteManagerPrivate *priv;
967 g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
972 priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
975 entries = vtable->get_entries (priv);
977 for (i = 0; i < entries->len; i++) {
978 Entry *entry = g_ptr_array_index (entries, i);
981 if (!NM_IS_DEVICE (entry->source.pointer))
984 if (entry->never_default)
987 state = nm_device_get_state (entry->source.device);
988 if ( state <= NM_DEVICE_STATE_DISCONNECTED
989 || state >= NM_DEVICE_STATE_DEACTIVATING) {
990 /* FIXME: we also track unmanaged devices with assumed default routes.
991 * Skip them, they are (currently) no candidates for best-device.
993 * Later we also want to properly assume connections for unmanaged devices.
995 * Also, we don't want to have DEACTIVATING devices returned as best_device(). */
999 if (g_slist_find ((GSList *) devices, entry->source.device)) {
1000 g_return_val_if_fail (nm_device_get_act_request (entry->source.pointer), entry->source.pointer);
1001 return entry->source.pointer;
1007 /** _ipx_get_best_activating_device:
1008 * @vtable: the virtual table
1009 * @self: #NMDefaultRouteManager
1010 * @devices: list of devices to be searched. Only devices from this list will be considered
1011 * @fully_activated: if #TRUE, only search for devices that are fully activated. Otherwise,
1012 * search if there is a best device going to be activated. In the latter case, this will
1013 * return NULL if the best device is already activated.
1014 * @preferred_device: if not-NULL, this device is preferred if there are more devices with
1015 * the same priority.
1018 _ipx_get_best_activating_device (const VTableIP *vtable, NMDefaultRouteManager *self, const GSList *devices, NMDevice *preferred_device)
1020 NMDefaultRouteManagerPrivate *priv;
1022 NMDevice *best_device = NULL;
1023 guint32 best_prio = G_MAXUINT32;
1024 NMDevice *best_activated_device;
1026 g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
1028 priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1032 best_activated_device = _ipx_get_best_device (vtable, self, devices);
1034 for (iter = devices; iter; iter = g_slist_next (iter)) {
1035 NMDevice *device = NM_DEVICE (iter->data);
1039 entry = _entry_find_by_source (vtable->get_entries (priv), device, NULL);
1042 /* of all the device that have an entry, we already know that best_activated_device
1043 * is the best. entry cannot be better. */
1044 if (entry->source.device != best_activated_device)
1046 prio = entry->effective_metric;
1048 NMDeviceState state = nm_device_get_state (device);
1050 if ( state <= NM_DEVICE_STATE_DISCONNECTED
1051 || state >= NM_DEVICE_STATE_DEACTIVATING)
1054 if (!_ipx_connection_has_default_route (vtable, self, nm_device_get_applied_connection (device), NULL))
1057 prio = nm_device_get_ip4_route_metric (device);
1059 prio = vtable->vt->metric_normalize (prio);
1063 || (prio == best_prio && preferred_device == device)) {
1064 best_device = device;
1069 /* There's only a best activating device if the best device
1070 * among all activating and already-activated devices is a
1071 * still-activating one.
1073 if (best_device && nm_device_get_state (best_device) >= NM_DEVICE_STATE_SECONDARIES)
1079 nm_default_route_manager_ip4_get_best_device (NMDefaultRouteManager *self, const GSList *devices, gboolean fully_activated, NMDevice *preferred_device)
1081 if (fully_activated)
1082 return _ipx_get_best_device (&vtable_ip4, self, devices);
1084 return _ipx_get_best_activating_device (&vtable_ip4, self, devices, preferred_device);
1088 nm_default_route_manager_ip6_get_best_device (NMDefaultRouteManager *self, const GSList *devices, gboolean fully_activated, NMDevice *preferred_device)
1090 if (fully_activated)
1091 return _ipx_get_best_device (&vtable_ip6, self, devices);
1093 return _ipx_get_best_activating_device (&vtable_ip6, self, devices, preferred_device);
1096 /***********************************************************************************/
1099 _ipx_get_best_config (const VTableIP *vtable,
1100 NMDefaultRouteManager *self,
1101 gboolean ignore_never_default,
1102 const char **out_ip_iface,
1103 NMActiveConnection **out_ac,
1104 NMDevice **out_device,
1105 NMVpnConnection **out_vpn)
1107 NMDefaultRouteManagerPrivate *priv;
1110 gpointer config_result = NULL;
1112 g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
1115 *out_ip_iface = NULL;
1123 priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1127 g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
1129 priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1130 entries = vtable->get_entries (priv);
1132 for (i = 0; i < entries->len; i++) {
1133 Entry *entry = g_ptr_array_index (entries, i);
1135 if (!NM_IS_DEVICE (entry->source.pointer)) {
1136 NMVpnConnection *vpn = NM_VPN_CONNECTION (entry->source.vpn);
1138 if (entry->never_default && !ignore_never_default)
1141 if (vtable->vt->is_ip4)
1142 config_result = nm_vpn_connection_get_ip4_config (vpn);
1144 config_result = nm_vpn_connection_get_ip6_config (vpn);
1145 g_assert (config_result);
1150 *out_ac = NM_ACTIVE_CONNECTION (vpn);
1152 *out_ip_iface = nm_vpn_connection_get_ip_iface (vpn);
1154 NMDevice *device = entry->source.device;
1156 NMDeviceState state;
1158 if (entry->never_default)
1161 state = nm_device_get_state (device);
1162 if ( state <= NM_DEVICE_STATE_DISCONNECTED
1163 || state >= NM_DEVICE_STATE_DEACTIVATING) {
1164 /* FIXME: the device has a default route, but we ignore it due to
1165 * unexpected state. That happens for example for unmanaged devices.
1167 * In the future, we want unmanaged devices also assume a connection
1168 * if they are activated externally.
1170 * Also, we don't want to have DEACTIVATING devices returned as best_config(). */
1174 if (vtable->vt->is_ip4)
1175 config_result = nm_device_get_ip4_config (device);
1177 config_result = nm_device_get_ip6_config (device);
1178 g_assert (config_result);
1179 req = nm_device_get_act_request (device);
1183 *out_device = device;
1185 *out_ac = NM_ACTIVE_CONNECTION (req);
1187 *out_ip_iface = nm_device_get_ip_iface (device);
1192 return config_result;
1196 nm_default_route_manager_ip4_get_best_config (NMDefaultRouteManager *self,
1197 gboolean ignore_never_default,
1198 const char **out_ip_iface,
1199 NMActiveConnection **out_ac,
1200 NMDevice **out_device,
1201 NMVpnConnection **out_vpn)
1203 return _ipx_get_best_config (&vtable_ip4,
1205 ignore_never_default,
1213 nm_default_route_manager_ip6_get_best_config (NMDefaultRouteManager *self,
1214 gboolean ignore_never_default,
1215 const char **out_ip_iface,
1216 NMActiveConnection **out_ac,
1217 NMDevice **out_device,
1218 NMVpnConnection **out_vpn)
1220 return _ipx_get_best_config (&vtable_ip6,
1222 ignore_never_default,
1229 /***********************************************************************************/
1232 _v4_get_entries (NMDefaultRouteManagerPrivate *priv)
1234 return priv->entries_ip4;
1238 _v6_get_entries (NMDefaultRouteManagerPrivate *priv)
1240 return priv->entries_ip6;
1243 static const VTableIP vtable_ip4 = {
1244 .vt = &nm_platform_vtable_route_v4,
1245 .get_entries = _v4_get_entries,
1248 static const VTableIP vtable_ip6 = {
1249 .vt = &nm_platform_vtable_route_v6,
1250 .get_entries = _v6_get_entries,
1253 /***********************************************************************************/
1256 _resync_idle_now (NMDefaultRouteManager *self)
1258 gboolean has_v4_changes, has_v6_changes;
1259 gboolean changed = FALSE;
1261 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1263 has_v4_changes = priv->resync.has_v4_changes;
1264 has_v6_changes = priv->resync.has_v6_changes;
1266 _LOGD (0, "resync: sync now (%u) (IPv4 changes: %s, IPv6 changes: %s)", priv->resync.idle_handle,
1267 has_v4_changes ? "yes" : "no", has_v6_changes ? "yes" : "no");
1269 priv->resync.has_v4_changes = FALSE;
1270 priv->resync.has_v6_changes = FALSE;
1271 priv->resync.idle_handle = 0;
1272 priv->resync.backoff_wait_time_ms =
1273 priv->resync.backoff_wait_time_ms == 0
1275 : priv->resync.backoff_wait_time_ms * 2;
1278 changed |= _resync_all (&vtable_ip4, self, NULL, NULL, TRUE);
1281 changed |= _resync_all (&vtable_ip6, self, NULL, NULL, TRUE);
1284 /* Nothing changed: reset the backoff wait time */
1285 _resync_idle_cancel (self);
1288 return G_SOURCE_REMOVE;
1292 _resync_idle_cancel (NMDefaultRouteManager *self)
1294 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1296 if (priv->resync.idle_handle) {
1297 _LOGD (0, "resync: cancelled (%u)", priv->resync.idle_handle);
1298 g_source_remove (priv->resync.idle_handle);
1299 priv->resync.idle_handle = 0;
1301 priv->resync.backoff_wait_time_ms = 0;
1302 priv->resync.has_v4_changes = FALSE;
1303 priv->resync.has_v6_changes = FALSE;
1307 _resync_idle_reschedule (NMDefaultRouteManager *self)
1309 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1311 /* since we react on external changes and re-add/remove default routes for
1312 * the interfaces we manage, there could be the erroneous situation where two applications
1313 * fight over a certain default route.
1314 * Avoid this, by increasingly wait longer to touch the system (backoff wait time). */
1316 if (priv->resync.backoff_wait_time_ms == 0) {
1317 /* for scheduling idle, always reschedule (to process all other events first) */
1318 if (priv->resync.idle_handle)
1319 g_source_remove (priv->resync.idle_handle);
1321 _LOGD (0, "resync: schedule on idle");
1322 /* Schedule this at low priority so that on an external change to platform
1323 * a NMDevice has a chance to picks up the changes first. */
1324 priv->resync.idle_handle = g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) _resync_idle_now, self, NULL);
1325 } else if (!priv->resync.idle_handle) {
1326 priv->resync.idle_handle = g_timeout_add (priv->resync.backoff_wait_time_ms, (GSourceFunc) _resync_idle_now, self);
1327 _LOGD (0, "resync: schedule in %u.%03u seconds (%u)", priv->resync.backoff_wait_time_ms/1000,
1328 priv->resync.backoff_wait_time_ms%1000, priv->resync.idle_handle);
1333 _platform_ipx_route_changed_cb (const VTableIP *vtable,
1334 NMDefaultRouteManager *self,
1335 const NMPlatformIPRoute *route)
1337 NMDefaultRouteManagerPrivate *priv;
1339 if (route && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
1340 /* we only care about address changes or changes of default route. */
1344 priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1346 if (priv->resync.guard) {
1347 /* callbacks while executing _resync_all() are ignored. */
1351 if (vtable->vt->is_ip4)
1352 priv->resync.has_v4_changes = TRUE;
1354 priv->resync.has_v6_changes = TRUE;
1356 _resync_idle_reschedule (self);
1360 _platform_changed_cb (NMPlatform *platform,
1361 NMPObjectType obj_type,
1363 gpointer platform_object,
1364 NMPlatformSignalChangeType change_type,
1365 NMDefaultRouteManager *self)
1368 case NMP_OBJECT_TYPE_IP4_ADDRESS:
1369 _platform_ipx_route_changed_cb (&vtable_ip4, self, NULL);
1371 case NMP_OBJECT_TYPE_IP6_ADDRESS:
1372 _platform_ipx_route_changed_cb (&vtable_ip6, self, NULL);
1374 case NMP_OBJECT_TYPE_IP4_ROUTE:
1375 _platform_ipx_route_changed_cb (&vtable_ip4, self, (const NMPlatformIPRoute *) platform_object);
1377 case NMP_OBJECT_TYPE_IP6_ROUTE:
1378 _platform_ipx_route_changed_cb (&vtable_ip6, self, (const NMPlatformIPRoute *) platform_object);
1381 g_return_if_reached ();
1385 /***********************************************************************************/
1388 set_property (GObject *object, guint prop_id,
1389 const GValue *value, GParamSpec *pspec)
1391 NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
1392 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1396 /* construct-only */
1397 priv->platform = g_value_get_object (value) ? : NM_PLATFORM_GET;
1398 if (!priv->platform)
1399 g_return_if_reached ();
1400 g_object_ref (priv->platform);
1403 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1409 nm_default_route_manager_init (NMDefaultRouteManager *self)
1414 constructed (GObject *object)
1416 NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
1417 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1419 priv->entries_ip4 = g_ptr_array_new_full (0, (GDestroyNotify) _entry_free);
1420 priv->entries_ip6 = g_ptr_array_new_full (0, (GDestroyNotify) _entry_free);
1422 g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, G_CALLBACK (_platform_changed_cb), self);
1423 g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, G_CALLBACK (_platform_changed_cb), self);
1424 g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_platform_changed_cb), self);
1425 g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, G_CALLBACK (_platform_changed_cb), self);
1428 NMDefaultRouteManager *
1429 nm_default_route_manager_new (NMPlatform *platform)
1431 return g_object_new (NM_TYPE_DEFAULT_ROUTE_MANAGER,
1432 NM_DEFAULT_ROUTE_MANAGER_PLATFORM, platform,
1437 dispose (GObject *object)
1439 NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
1440 NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1442 priv->disposed = TRUE;
1444 if (priv->platform) {
1445 g_signal_handlers_disconnect_by_func (priv->platform, G_CALLBACK (_platform_changed_cb), self);
1446 g_clear_object (&priv->platform);
1449 _resync_idle_cancel (self);
1451 /* g_ptr_array_free() invokes the free function for all entries without actually
1452 * removing them and having dangling pointers in the process. _entry_free()
1453 * will unref the source, which might cause the destruction of the object, which
1454 * might trigger calling into @self again. This is guarded by priv->dispose.
1455 * If you remove priv->dispose, you must refactor the lines below to remove enties
1458 if (priv->entries_ip4) {
1459 g_ptr_array_free (priv->entries_ip4, TRUE);
1460 priv->entries_ip4 = NULL;
1462 if (priv->entries_ip6) {
1463 g_ptr_array_free (priv->entries_ip6, TRUE);
1464 priv->entries_ip6 = NULL;
1467 G_OBJECT_CLASS (nm_default_route_manager_parent_class)->dispose (object);
1471 nm_default_route_manager_class_init (NMDefaultRouteManagerClass *klass)
1473 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1475 g_type_class_add_private (klass, sizeof (NMDefaultRouteManagerPrivate));
1477 /* virtual methods */
1478 object_class->constructed = constructed;
1479 object_class->dispose = dispose;
1480 object_class->set_property = set_property;
1482 obj_properties[PROP_PLATFORM] =
1483 g_param_spec_object (NM_DEFAULT_ROUTE_MANAGER_PLATFORM, "", "",
1486 G_PARAM_CONSTRUCT_ONLY |
1487 G_PARAM_STATIC_STRINGS);
1488 g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);