device: renew dhcp leases on awake for software devices
[NetworkManager.git] / src / nm-default-route-manager.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager -- Network link manager
3  *
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.
8  *
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.
13  *
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.
17  *
18  * Copyright (C) 2014 Red Hat, Inc.
19  */
20
21
22 #include "nm-default.h"
23
24 #include <string.h>
25
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"
34
35 typedef struct {
36         GPtrArray *entries_ip4;
37         GPtrArray *entries_ip6;
38         struct {
39                 guint guard;
40                 guint backoff_wait_time_ms;
41                 guint idle_handle;
42                 gboolean has_v4_changes;
43                 gboolean has_v6_changes;
44         } resync;
45
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
49          * pointers.
50          * Guard every publicly accessible function to return early if the instance
51          * is already disposing. */
52         gboolean disposed;
53
54         NMPlatform *platform;
55 } NMDefaultRouteManagerPrivate;
56
57 #define NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DEFAULT_ROUTE_MANAGER, NMDefaultRouteManagerPrivate))
58
59 G_DEFINE_TYPE (NMDefaultRouteManager, nm_default_route_manager, G_TYPE_OBJECT)
60
61 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
62         PROP_PLATFORM,
63 );
64
65 NM_DEFINE_SINGLETON_GETTER (NMDefaultRouteManager, nm_default_route_manager_get, NM_TYPE_DEFAULT_ROUTE_MANAGER);
66
67 #define _NMLOG_PREFIX_NAME   "default-route"
68 #undef  _NMLOG_ENABLED
69 #define _NMLOG_ENABLED(level, addr_family) \
70     ({ \
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); \
74         \
75         nm_logging_enabled (__level, __domain); \
76     })
77 #define _NMLOG(level, addr_family, ...) \
78     G_STMT_START { \
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); \
82         \
83         if (nm_logging_enabled (__level, __domain)) { \
84             char __prefix_buf[100]; \
85             \
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' : '-'), \
92                                           self) \
93                         : _NMLOG2_PREFIX_NAME \
94                      _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
95         } \
96     } G_STMT_END
97
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, ...) \
102     G_STMT_START { \
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); \
106         \
107         if (nm_logging_enabled (__level, __domain)) { \
108             char __prefix_buf[100]; \
109             guint __entry_idx = (entry_idx); \
110             const Entry *const __entry = (entry); \
111             \
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' : '-'), \
118                                           self) \
119                         : _NMLOG2_PREFIX_NAME, \
120                      __entry_idx, \
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__)); \
127         } \
128     } G_STMT_END
129
130 /***********************************************************************************/
131
132 static void _resync_idle_cancel (NMDefaultRouteManager *self);
133
134 /***********************************************************************************/
135
136 typedef struct {
137         union {
138                 void *pointer;
139                 GObject *object;
140                 NMDevice *device;
141                 NMVpnConnection *vpn;
142         } source;
143         NMPlatformIPXRoute route;
144
145         /* Whether the route is synced to platform and has a default route.
146          *
147          * ( synced && !never_default): the interface gets a default route that
148          *     is enforced and managed by NMDefaultRouteManager.
149          *
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.
156          *
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.
166          *
167          * (!synced &&  never_default): this combination makes no sense.
168          */
169         gboolean synced;
170         gboolean never_default;
171
172         guint32 effective_metric;
173 } Entry;
174
175 typedef struct {
176         const NMPlatformVTableRoute *vt;
177         GPtrArray *(*get_entries) (NMDefaultRouteManagerPrivate *priv);
178 } VTableIP;
179
180 static const VTableIP vtable_ip4, vtable_ip6;
181
182 static NMPlatformIPRoute *
183 _vt_route_index (const VTableIP *vtable, GArray *routes, guint index)
184 {
185         if (vtable->vt->is_ip4)
186                 return (NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP4Route, index);
187         else
188                 return (NMPlatformIPRoute *) &g_array_index (routes, NMPlatformIP6Route, index);
189 }
190
191 static gboolean
192 _vt_routes_has_entry (const VTableIP *vtable, GArray *routes, const Entry *entry)
193 {
194         guint i;
195         NMPlatformIPXRoute route = entry->route;
196
197         route.rx.metric = entry->effective_metric;
198
199         if (vtable->vt->is_ip4) {
200                 for (i = 0; i < routes->len; i++) {
201                         NMPlatformIP4Route *r = &g_array_index (routes, NMPlatformIP4Route, i);
202
203                         route.rx.source = r->source;
204                         if (nm_platform_ip4_route_cmp (r, &route.r4) == 0)
205                                 return TRUE;
206                 }
207         } else {
208                 for (i = 0; i < routes->len; i++) {
209                         NMPlatformIP6Route *r = &g_array_index (routes, NMPlatformIP6Route, i);
210
211                         route.rx.source = r->source;
212                         if (nm_platform_ip6_route_cmp (r, &route.r6) == 0)
213                                 return TRUE;
214                 }
215         }
216         return FALSE;
217 }
218
219 static void
220 _entry_free (Entry *entry)
221 {
222         if (entry) {
223                 g_object_unref (entry->source.object);
224                 g_slice_free (Entry, entry);
225         }
226 }
227
228 static Entry *
229 _entry_find_by_source (GPtrArray *entries, gpointer source, guint *out_idx)
230 {
231         guint i;
232
233         for (i = 0; i < entries->len; i++) {
234                 Entry *e = g_ptr_array_index (entries, i);
235
236                 if (e->source.pointer == source) {
237                         if (out_idx)
238                                 *out_idx = i;
239                         return e;
240                 }
241         }
242
243         if (out_idx)
244                 *out_idx = G_MAXUINT;
245         return NULL;
246 }
247
248 static gboolean
249 _platform_route_sync_add (const VTableIP *vtable, NMDefaultRouteManager *self, guint32 metric)
250 {
251         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
252         GPtrArray *entries = vtable->get_entries (priv);
253         guint i;
254         Entry *entry_unsynced = NULL;
255         Entry *entry = NULL;
256         gboolean success;
257
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);
263
264                 if (e->never_default)
265                         continue;
266
267                 if (e->effective_metric != metric)
268                         continue;
269
270                 if (e->synced) {
271                         g_assert (!entry || metric == G_MAXUINT32);
272                         if (!entry)
273                                 entry = e;
274                 } else
275                         entry_unsynced = e;
276         }
277
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);
284
285         /* we only add the route, if we have an (to be synced) entry for it. */
286         if (!entry)
287                 return FALSE;
288
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,
293                                                      0,
294                                                      0,
295                                                      entry->route.r4.gateway,
296                                                      0,
297                                                      entry->effective_metric,
298                                                      entry->route.rx.mss);
299         } else {
300                 success = nm_platform_ip6_route_add (priv->platform,
301                                                      entry->route.rx.ifindex,
302                                                      entry->route.rx.source,
303                                                      in6addr_any,
304                                                      0,
305                                                      entry->route.r6.gateway,
306                                                      entry->effective_metric,
307                                                      entry->route.rx.mss);
308         }
309         if (!success) {
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);
312         }
313         return TRUE;
314 }
315
316 static gboolean
317 _platform_route_sync_flush (const VTableIP *vtable, NMDefaultRouteManager *self, int ifindex_to_flush)
318 {
319         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
320         GPtrArray *entries = vtable->get_entries (priv);
321         GArray *routes;
322         guint i, j;
323         gboolean changed = FALSE;
324
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);
327
328         for (i = 0; i < routes->len; i++) {
329                 const NMPlatformIPRoute *route;
330                 gboolean has_ifindex_synced = FALSE;
331                 Entry *entry = NULL;
332
333                 route = _vt_route_index (vtable, routes, i);
334
335                 /* look at all entries and see if the route for this ifindex pair is
336                  * a known entry. */
337                 for (j = 0; j < entries->len; j++) {
338                         Entry *e = g_ptr_array_index (entries, j);
339
340                         if (   e->route.rx.ifindex == route->ifindex
341                             && e->synced) {
342                                 has_ifindex_synced = TRUE;
343                                 if (   !e->never_default
344                                     && e->effective_metric == route->metric)
345                                         entry = e;
346                         }
347                 }
348
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).
352                  *
353                  * Otherwise, don't delete the route because it's configured
354                  * externally (and will be assumed -- or already is assumed).
355                  */
356                 if (   !entry
357                     && (has_ifindex_synced || ifindex_to_flush == route->ifindex)) {
358                         vtable->vt->route_delete_default (priv->platform, route->ifindex, route->metric);
359                         changed = TRUE;
360                 }
361         }
362         g_array_free (routes, TRUE);
363         return changed;
364 }
365
366 static int
367 _sort_entries_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
368 {
369         guint32 m_a, m_b;
370         const Entry *e_a = *((const Entry **) a);
371         const Entry *e_b = *((const Entry **) b);
372
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;
376
377         /* we normalize route.metric already in _ipx_update_default_route().
378          * so we can just compare the metrics numerically */
379
380         if (m_a != m_b)
381                 return (m_a < m_b) ? -1 : 1;
382
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;
386
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
392          * synced.
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)
397          * prefered.
398          * If both devices are assumed, we also have non-determinism, but also
399          * we don't reorder either.
400          */
401         if (!!e_a->synced != !!e_b->synced)
402                 return e_a->synced ? 1 : -1;
403
404         /* otherwise, do not reorder */
405         return 0;
406 }
407
408 static GHashTable *
409 _get_assumed_interface_metrics (const VTableIP *vtable, NMDefaultRouteManager *self, GArray *routes)
410 {
411         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
412         GPtrArray *entries;
413         guint i, j;
414         GHashTable *result;
415
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. */
420
421         entries = vtable->get_entries (priv);
422
423         result = g_hash_table_new (NULL, NULL);
424
425         for (i = 0; i < routes->len; i++) {
426                 gboolean ifindex_has_synced_entry = FALSE;
427                 const NMPlatformIPRoute *route;
428
429                 route = _vt_route_index (vtable, routes, i);
430
431                 for (j = 0; j < entries->len; j++) {
432                         Entry *e = g_ptr_array_index (entries, j);
433
434                         if (   e->synced
435                             && e->route.rx.ifindex == route->ifindex) {
436                                 ifindex_has_synced_entry = TRUE;
437                                 break;
438                         }
439                 }
440
441                 if (!ifindex_has_synced_entry)
442                         g_hash_table_add (result, GUINT_TO_POINTER (vtable->vt->metric_normalize (route->metric)));
443         }
444
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);
451
452                 if (e_i->synced)
453                         continue;
454
455                 for (j = 0; j < entries->len; j++) {
456                         Entry *e_j = g_ptr_array_index (entries, j);
457
458                         if (   j != i
459                             && (e_j->synced && e_j->route.rx.ifindex == e_i->route.rx.ifindex)) {
460                                 ifindex_has_synced_entry = TRUE;
461                                 break;
462                         }
463                 }
464
465                 if (!ifindex_has_synced_entry)
466                         g_hash_table_add (result, GUINT_TO_POINTER (vtable->vt->metric_normalize (e_i->route.rx.metric)));
467         }
468
469         return result;
470 }
471
472 static int
473 _sort_metrics_ascending_fcn (gconstpointer a, gconstpointer b)
474 {
475         guint32 m_a = *((guint32 *) a);
476         guint32 m_b = *((guint32 *) b);
477
478         if (m_a < m_b)
479                 return -1;
480         return m_a == m_b ? 0 : 1;
481 }
482
483 static gboolean
484 _resync_all (const VTableIP *vtable, NMDefaultRouteManager *self, const Entry *changed_entry, const Entry *old_entry, gboolean external_change)
485 {
486         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
487         Entry *entry;
488         guint i, j;
489         gint64 last_metric = -1;
490         guint32 expected_metric;
491         GPtrArray *entries;
492         GArray *changed_metrics = g_array_new (FALSE, FALSE, sizeof (guint32));
493         GHashTable *assumed_metrics;
494         GArray *routes;
495         gboolean changed = FALSE;
496         int ifindex_to_flush = 0;
497
498         g_assert (priv->resync.guard == 0);
499         priv->resync.guard++;
500
501         if (!external_change) {
502                 if (vtable->vt->is_ip4)
503                         priv->resync.has_v4_changes = FALSE;
504                 else
505                         priv->resync.has_v6_changes = FALSE;
506                 if (!priv->resync.has_v4_changes && !priv->resync.has_v6_changes)
507                         _resync_idle_cancel (self);
508         }
509
510         entries = vtable->get_entries (priv);
511
512         routes = vtable->vt->route_get_all (priv->platform, 0, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT);
513
514         assumed_metrics = _get_assumed_interface_metrics (vtable, self, routes);
515
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);
519         }
520
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);
524
525                 g_assert (entry != old_entry);
526
527                 if (entry->never_default)
528                         continue;
529
530                 if (!entry->synced) {
531                         gboolean has_synced_entry = FALSE;
532
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);
539
540                                 if (   e->synced
541                                         && e->route.rx.ifindex == entry->route.rx.ifindex) {
542                                         has_synced_entry = TRUE;
543                                         break;
544                                 }
545                         }
546                         if (!has_synced_entry)
547                                 last_metric = MAX (last_metric, (gint64) entry->effective_metric);
548                         continue;
549                 }
550
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;
554
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;
558
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. */
561
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);
565
566                                 if (   r->metric == expected_metric
567                                     && r->ifindex == entry->route.rx.ifindex) {
568                                         has_metric_for_ifindex = TRUE;
569                                         break;
570                                 }
571                         }
572                         if (has_metric_for_ifindex)
573                                 break;
574                         expected_metric++;
575                 }
576
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);
581                         if (old_entry) {
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);
585                         } else {
586                                 _LOG2D (vtable, i, entry, "sync:add    %s (%u)",
587                                         vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) expected_metric);
588                         }
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);
595                 } else {
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);
601                         }
602                 }
603
604                 if (entry->effective_metric != expected_metric) {
605                         entry->effective_metric = expected_metric;
606                         changed = TRUE;
607                 }
608                 last_metric = expected_metric;
609         }
610
611         g_array_free (routes, TRUE);
612
613         g_array_sort (changed_metrics, _sort_metrics_ascending_fcn);
614         last_metric = -1;
615         for (j = 0; j < changed_metrics->len; j++) {
616                 expected_metric = g_array_index (changed_metrics, guint32, j);
617
618                 if (last_metric == (gint64) expected_metric) {
619                         /* skip duplicates. */
620                         continue;
621                 }
622                 changed |= _platform_route_sync_add (vtable, self, expected_metric);
623                 last_metric = expected_metric;
624         }
625
626         if (   old_entry
627             && !changed_entry
628             && old_entry->synced
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;
634         }
635
636         changed |= _platform_route_sync_flush (vtable, self, ifindex_to_flush);
637
638         g_array_free (changed_metrics, TRUE);
639         g_hash_table_unref (assumed_metrics);
640
641         priv->resync.guard--;
642         return changed;
643 }
644
645 static void
646 _entry_at_idx_update (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx, const Entry *old_entry)
647 {
648         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
649         Entry *entry;
650         GPtrArray *entries;
651
652         entries = vtable->get_entries (priv);
653         g_assert (entry_idx < entries->len);
654
655         entry = g_ptr_array_index (entries, entry_idx);
656
657         g_assert (   !old_entry
658                   || (entry->source.pointer == old_entry->source.pointer && entry->route.rx.ifindex == old_entry->route.rx.ifindex));
659
660         if (!entry->synced && !entry->never_default)
661                 entry->effective_metric = entry->route.rx.metric;
662
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);
667
668         g_ptr_array_sort_with_data (entries, _sort_entries_cmp, NULL);
669
670         _resync_all (vtable, self, entry, old_entry, FALSE);
671 }
672
673 static void
674 _entry_at_idx_remove (const VTableIP *vtable, NMDefaultRouteManager *self, guint entry_idx)
675 {
676         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
677         Entry *entry;
678         GPtrArray *entries;
679
680         entries = vtable->get_entries (priv);
681
682         g_assert (entry_idx < entries->len);
683
684         entry = g_ptr_array_index (entries, entry_idx);
685
686         _LOG2D (vtable, entry_idx, entry, "record:remove %s (%u)",
687                vtable->vt->route_to_string (&entry->route, NULL, 0), (guint) entry->effective_metric);
688
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);
692
693         _resync_all (vtable, self, NULL, entry, FALSE);
694
695         _entry_free (entry);
696 }
697
698 /***********************************************************************************/
699
700 static void
701 _ipx_update_default_route (const VTableIP *vtable, NMDefaultRouteManager *self, gpointer source)
702 {
703         NMDefaultRouteManagerPrivate *priv;
704         Entry *entry;
705         guint entry_idx;
706         const NMPlatformIPRoute *default_route = NULL;
707         NMPlatformIPXRoute rt;
708         int ip_ifindex;
709         GPtrArray *entries;
710         NMDevice *device = NULL;
711         NMVpnConnection *vpn = NULL;
712         gboolean never_default = FALSE;
713         gboolean synced = FALSE;
714
715         g_return_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self));
716
717         priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
718         if (priv->disposed)
719                 return;
720
721         if (NM_IS_DEVICE (source))
722                 device = source;
723         else if (NM_IS_VPN_CONNECTION (source))
724                 vpn = source;
725         else
726                 g_return_if_reached ();
727
728         if (device)
729                 ip_ifindex = nm_device_get_ip_ifindex (device);
730         else {
731                 ip_ifindex = nm_vpn_connection_get_ip_ifindex (vpn);
732
733                 if (ip_ifindex <= 0) {
734                         NMDevice *parent = nm_active_connection_get_device (NM_ACTIVE_CONNECTION (vpn));
735
736                         if (parent)
737                                 ip_ifindex = nm_device_get_ip_ifindex (parent);
738                 }
739         }
740
741         entries = vtable->get_entries (priv);
742         entry = _entry_find_by_source (entries, source, &entry_idx);
743
744         if (   entry
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);
749
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));
755                 return;
756         }
757
758         /* get the @default_route from the device. */
759         if (ip_ifindex > 0) {
760                 if (device) {
761                         gboolean is_assumed;
762
763                         if (vtable->vt->is_ip4)
764                                 default_route = (const NMPlatformIPRoute *) nm_device_get_ip4_default_route (device, &is_assumed);
765                         else
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.
770                                  *
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
773                                  * the device. */
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;
779
780                                 never_default = TRUE;
781                                 synced = TRUE;
782                         } else
783                                 synced = default_route && !is_assumed;
784                 } else {
785                         NMConnection *connection = nm_active_connection_get_applied_connection ((NMActiveConnection *) vpn);
786
787                         if (   connection
788                             && nm_vpn_connection_get_vpn_state (vpn) == NM_VPN_CONNECTION_STATE_ACTIVATED) {
789
790                                 memset (&rt, 0, sizeof (rt));
791                                 if (vtable->vt->is_ip4) {
792                                         NMIP4Config *vpn_config;
793
794                                         vpn_config = nm_vpn_connection_get_ip4_config (vpn);
795                                         if (vpn_config) {
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;
803                                         }
804                                 } else {
805                                         NMIP6Config *vpn_config;
806
807                                         vpn_config = nm_vpn_connection_get_ip6_config (vpn);
808                                         if (vpn_config) {
809                                                 const struct in6_addr *int_gw = nm_ip6_config_get_gateway (vpn_config);
810
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;
818                                         }
819                                 }
820                         }
821                         if (nm_vpn_connection_get_ip_ifindex (vpn) > 0)
822                                 synced = TRUE;
823                         else {
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;
827                         }
828                 }
829         }
830
831         g_assert (!default_route || default_route->plen == 0);
832
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;
837         }
838
839         if (!entry && !default_route)
840                 /* nothing to do */;
841         else if (!entry) {
842                 /* add */
843                 entry = g_slice_new0 (Entry);
844                 entry->source.object = g_object_ref (source);
845
846                 if (vtable->vt->is_ip4)
847                         entry->route.r4 = *((const NMPlatformIP4Route *) default_route);
848                 else
849                         entry->route.r6 = *((const NMPlatformIP6Route *) default_route);
850
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;
857
858                 g_ptr_array_add (entries, entry);
859                 _entry_at_idx_update (vtable, self, entries->len - 1, NULL);
860         } else if (default_route) {
861                 /* update */
862                 Entry old_entry, new_entry;
863
864                 new_entry = *entry;
865                 if (vtable->vt->is_ip4)
866                         new_entry.route.r4 = *((const NMPlatformIP4Route *) default_route);
867                 else
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;
874
875                 if (memcmp (entry, &new_entry, sizeof (new_entry)) == 0)
876                         return;
877
878                 old_entry = *entry;
879                 *entry = new_entry;
880                 _entry_at_idx_update (vtable, self, entry_idx, &old_entry);
881         } else {
882                 /* delete */
883                 _entry_at_idx_remove (vtable, self, entry_idx);
884         }
885 }
886
887 void
888 nm_default_route_manager_ip4_update_default_route (NMDefaultRouteManager *self, gpointer source)
889 {
890         _ipx_update_default_route (&vtable_ip4, self, source);
891 }
892
893 void
894 nm_default_route_manager_ip6_update_default_route (NMDefaultRouteManager *self, gpointer source)
895 {
896         _ipx_update_default_route (&vtable_ip6, self, source);
897 }
898
899 /***********************************************************************************/
900
901 static gboolean
902 _ipx_connection_has_default_route (const VTableIP *vtable, NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
903 {
904         const char *method;
905         NMSettingIPConfig *s_ip;
906         gboolean is_never_default = FALSE;
907         gboolean has_default_route = FALSE;
908
909         g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), FALSE);
910
911         if (!connection)
912                 goto out;
913
914         if (vtable->vt->is_ip4)
915                 s_ip = nm_connection_get_setting_ip4_config (connection);
916         else
917                 s_ip = nm_connection_get_setting_ip6_config (connection);
918         if (!s_ip)
919                 goto out;
920         if (nm_setting_ip_config_get_never_default (s_ip)) {
921                 is_never_default = TRUE;
922                 goto out;
923         }
924
925         if (vtable->vt->is_ip4) {
926                 method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
927                 if (   !method
928                     || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)
929                     || !strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
930                         goto out;
931         } else {
932                 method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP6_CONFIG);
933                 if (   !method
934                     || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)
935                     || !strcmp (method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
936                         goto out;
937         }
938
939         has_default_route = TRUE;
940 out:
941         if (out_is_never_default)
942                 *out_is_never_default = is_never_default;
943         return has_default_route;
944 }
945
946 gboolean
947 nm_default_route_manager_ip4_connection_has_default_route (NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
948 {
949         return _ipx_connection_has_default_route (&vtable_ip4, self, connection, out_is_never_default);
950 }
951
952 gboolean
953 nm_default_route_manager_ip6_connection_has_default_route (NMDefaultRouteManager *self, NMConnection *connection, gboolean *out_is_never_default)
954 {
955         return _ipx_connection_has_default_route (&vtable_ip6, self, connection, out_is_never_default);
956 }
957
958 /***********************************************************************************/
959
960 static NMDevice *
961 _ipx_get_best_device (const VTableIP *vtable, NMDefaultRouteManager *self, const GSList *devices)
962 {
963         NMDefaultRouteManagerPrivate *priv;
964         GPtrArray *entries;
965         guint i;
966
967         g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
968
969         if (!devices)
970                 return NULL;
971
972         priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
973         if (priv->disposed)
974                 return NULL;
975         entries = vtable->get_entries (priv);
976
977         for (i = 0; i < entries->len; i++) {
978                 Entry *entry = g_ptr_array_index (entries, i);
979                 NMDeviceState state;
980
981                 if (!NM_IS_DEVICE (entry->source.pointer))
982                         continue;
983
984                 if (entry->never_default)
985                         continue;
986
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.
992                          *
993                          * Later we also want to properly assume connections for unmanaged devices.
994                          *
995                          * Also, we don't want to have DEACTIVATING devices returned as best_device(). */
996                         continue;
997                 }
998
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;
1002                 }
1003         }
1004         return NULL;
1005 }
1006
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.
1016  **/
1017 static NMDevice *
1018 _ipx_get_best_activating_device (const VTableIP *vtable, NMDefaultRouteManager *self, const GSList *devices, NMDevice *preferred_device)
1019 {
1020         NMDefaultRouteManagerPrivate *priv;
1021         const GSList *iter;
1022         NMDevice *best_device = NULL;
1023         guint32 best_prio = G_MAXUINT32;
1024         NMDevice *best_activated_device;
1025
1026         g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
1027
1028         priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1029         if (priv->disposed)
1030                 return NULL;
1031
1032         best_activated_device = _ipx_get_best_device (vtable, self, devices);
1033
1034         for (iter = devices; iter; iter = g_slist_next (iter)) {
1035                 NMDevice *device = NM_DEVICE (iter->data);
1036                 guint32 prio;
1037                 Entry *entry;
1038
1039                 entry = _entry_find_by_source (vtable->get_entries (priv), device, NULL);
1040
1041                 if (entry) {
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)
1045                                 continue;
1046                         prio = entry->effective_metric;
1047                 } else {
1048                         NMDeviceState state = nm_device_get_state (device);
1049
1050                         if (   state <= NM_DEVICE_STATE_DISCONNECTED
1051                             || state >= NM_DEVICE_STATE_DEACTIVATING)
1052                                 continue;
1053
1054                         if (!_ipx_connection_has_default_route (vtable, self, nm_device_get_applied_connection (device), NULL))
1055                                 continue;
1056
1057                         prio = nm_device_get_ip4_route_metric (device);
1058                 }
1059                 prio = vtable->vt->metric_normalize (prio);
1060
1061                 if (   !best_device
1062                     || prio < best_prio
1063                         || (prio == best_prio && preferred_device == device)) {
1064                         best_device = device;
1065                         best_prio = prio;
1066                 }
1067         }
1068
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.
1072          */
1073         if (best_device && nm_device_get_state (best_device) >= NM_DEVICE_STATE_SECONDARIES)
1074                 return NULL;
1075         return best_device;
1076 }
1077
1078 NMDevice *
1079 nm_default_route_manager_ip4_get_best_device (NMDefaultRouteManager *self, const GSList *devices, gboolean fully_activated, NMDevice *preferred_device)
1080 {
1081         if (fully_activated)
1082                 return _ipx_get_best_device (&vtable_ip4, self, devices);
1083         else
1084                 return _ipx_get_best_activating_device (&vtable_ip4, self, devices, preferred_device);
1085 }
1086
1087 NMDevice *
1088 nm_default_route_manager_ip6_get_best_device (NMDefaultRouteManager *self, const GSList *devices, gboolean fully_activated, NMDevice *preferred_device)
1089 {
1090         if (fully_activated)
1091                 return _ipx_get_best_device (&vtable_ip6, self, devices);
1092         else
1093                 return _ipx_get_best_activating_device (&vtable_ip6, self, devices, preferred_device);
1094 }
1095
1096 /***********************************************************************************/
1097
1098 static gpointer
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)
1106 {
1107         NMDefaultRouteManagerPrivate *priv;
1108         GPtrArray *entries;
1109         guint i;
1110         gpointer config_result = NULL;
1111
1112         g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
1113
1114         if (out_ip_iface)
1115                 *out_ip_iface = NULL;
1116         if (out_ac)
1117                 *out_ac = NULL;
1118         if (out_device)
1119                 *out_device = NULL;
1120         if (out_vpn)
1121                 *out_vpn = NULL;
1122
1123         priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1124         if (priv->disposed)
1125                 return NULL;
1126
1127         g_return_val_if_fail (NM_IS_DEFAULT_ROUTE_MANAGER (self), NULL);
1128
1129         priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1130         entries = vtable->get_entries (priv);
1131
1132         for (i = 0; i < entries->len; i++) {
1133                 Entry *entry = g_ptr_array_index (entries, i);
1134
1135                 if (!NM_IS_DEVICE (entry->source.pointer)) {
1136                         NMVpnConnection *vpn = NM_VPN_CONNECTION (entry->source.vpn);
1137
1138                         if (entry->never_default && !ignore_never_default)
1139                                 continue;
1140
1141                         if (vtable->vt->is_ip4)
1142                                 config_result = nm_vpn_connection_get_ip4_config (vpn);
1143                         else
1144                                 config_result = nm_vpn_connection_get_ip6_config (vpn);
1145                         g_assert (config_result);
1146
1147                         if (out_vpn)
1148                                 *out_vpn = vpn;
1149                         if (out_ac)
1150                                 *out_ac = NM_ACTIVE_CONNECTION (vpn);
1151                         if (out_ip_iface)
1152                                 *out_ip_iface = nm_vpn_connection_get_ip_iface (vpn);
1153                 } else {
1154                         NMDevice *device = entry->source.device;
1155                         NMActRequest *req;
1156                         NMDeviceState state;
1157
1158                         if (entry->never_default)
1159                                 continue;
1160
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.
1166                                  *
1167                                  * In the future, we want unmanaged devices also assume a connection
1168                                  * if they are activated externally.
1169                                  *
1170                                  * Also, we don't want to have DEACTIVATING devices returned as best_config(). */
1171                                 continue;
1172                         }
1173
1174                         if (vtable->vt->is_ip4)
1175                                 config_result = nm_device_get_ip4_config (device);
1176                         else
1177                                 config_result = nm_device_get_ip6_config (device);
1178                         g_assert (config_result);
1179                         req = nm_device_get_act_request (device);
1180                         g_assert (req);
1181
1182                         if (out_device)
1183                                 *out_device = device;
1184                         if (out_ac)
1185                                 *out_ac = NM_ACTIVE_CONNECTION (req);
1186                         if (out_ip_iface)
1187                                 *out_ip_iface = nm_device_get_ip_iface (device);
1188                 }
1189                 break;
1190         }
1191
1192         return config_result;
1193 }
1194
1195 NMIP4Config *
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)
1202 {
1203         return _ipx_get_best_config (&vtable_ip4,
1204                                      self,
1205                                      ignore_never_default,
1206                                      out_ip_iface,
1207                                      out_ac,
1208                                      out_device,
1209                                      out_vpn);
1210 }
1211
1212 NMIP6Config *
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)
1219 {
1220         return _ipx_get_best_config (&vtable_ip6,
1221                                      self,
1222                                      ignore_never_default,
1223                                      out_ip_iface,
1224                                      out_ac,
1225                                      out_device,
1226                                      out_vpn);
1227 }
1228
1229 /***********************************************************************************/
1230
1231 static GPtrArray *
1232 _v4_get_entries (NMDefaultRouteManagerPrivate *priv)
1233 {
1234         return priv->entries_ip4;
1235 }
1236
1237 static GPtrArray *
1238 _v6_get_entries (NMDefaultRouteManagerPrivate *priv)
1239 {
1240         return priv->entries_ip6;
1241 }
1242
1243 static const VTableIP vtable_ip4 = {
1244         .vt                             = &nm_platform_vtable_route_v4,
1245         .get_entries                    = _v4_get_entries,
1246 };
1247
1248 static const VTableIP vtable_ip6 = {
1249         .vt                             = &nm_platform_vtable_route_v6,
1250         .get_entries                    = _v6_get_entries,
1251 };
1252
1253 /***********************************************************************************/
1254
1255 static gboolean
1256 _resync_idle_now (NMDefaultRouteManager *self)
1257 {
1258         gboolean has_v4_changes, has_v6_changes;
1259         gboolean changed = FALSE;
1260
1261         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1262
1263         has_v4_changes = priv->resync.has_v4_changes;
1264         has_v6_changes = priv->resync.has_v6_changes;
1265
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");
1268
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
1274             ? 100
1275             : priv->resync.backoff_wait_time_ms * 2;
1276
1277         if (has_v4_changes)
1278                 changed |= _resync_all (&vtable_ip4, self, NULL, NULL, TRUE);
1279
1280         if (has_v6_changes)
1281                 changed |= _resync_all (&vtable_ip6, self, NULL, NULL, TRUE);
1282
1283         if (!changed) {
1284                 /* Nothing changed: reset the backoff wait time */
1285                 _resync_idle_cancel (self);
1286         }
1287
1288         return G_SOURCE_REMOVE;
1289 }
1290
1291 static void
1292 _resync_idle_cancel (NMDefaultRouteManager *self)
1293 {
1294         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1295
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;
1300         }
1301         priv->resync.backoff_wait_time_ms = 0;
1302         priv->resync.has_v4_changes = FALSE;
1303         priv->resync.has_v6_changes = FALSE;
1304 }
1305
1306 static void
1307 _resync_idle_reschedule (NMDefaultRouteManager *self)
1308 {
1309         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1310
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). */
1315
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);
1320                 else
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);
1329         }
1330 }
1331
1332 static void
1333 _platform_ipx_route_changed_cb (const VTableIP *vtable,
1334                                 NMDefaultRouteManager *self,
1335                                 const NMPlatformIPRoute *route)
1336 {
1337         NMDefaultRouteManagerPrivate *priv;
1338
1339         if (route && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) {
1340                 /* we only care about address changes or changes of default route. */
1341                 return;
1342         }
1343
1344         priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1345
1346         if (priv->resync.guard) {
1347                 /* callbacks while executing _resync_all() are ignored. */
1348                 return;
1349         }
1350
1351         if (vtable->vt->is_ip4)
1352                 priv->resync.has_v4_changes = TRUE;
1353         else
1354                 priv->resync.has_v6_changes = TRUE;
1355
1356         _resync_idle_reschedule (self);
1357 }
1358
1359 static void
1360 _platform_changed_cb (NMPlatform *platform,
1361                       NMPObjectType obj_type,
1362                       int ifindex,
1363                       gpointer platform_object,
1364                       NMPlatformSignalChangeType change_type,
1365                       NMDefaultRouteManager *self)
1366 {
1367         switch (obj_type) {
1368         case NMP_OBJECT_TYPE_IP4_ADDRESS:
1369                 _platform_ipx_route_changed_cb (&vtable_ip4, self, NULL);
1370                 break;
1371         case NMP_OBJECT_TYPE_IP6_ADDRESS:
1372                 _platform_ipx_route_changed_cb (&vtable_ip6, self, NULL);
1373                 break;
1374         case NMP_OBJECT_TYPE_IP4_ROUTE:
1375                 _platform_ipx_route_changed_cb (&vtable_ip4, self, (const NMPlatformIPRoute *) platform_object);
1376                 break;
1377         case NMP_OBJECT_TYPE_IP6_ROUTE:
1378                 _platform_ipx_route_changed_cb (&vtable_ip6, self, (const NMPlatformIPRoute *) platform_object);
1379                 break;
1380         default:
1381                 g_return_if_reached ();
1382         }
1383 }
1384
1385 /***********************************************************************************/
1386
1387 static void
1388 set_property (GObject *object, guint prop_id,
1389               const GValue *value, GParamSpec *pspec)
1390 {
1391         NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
1392         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1393
1394         switch (prop_id) {
1395         case PROP_PLATFORM:
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);
1401                 break;
1402         default:
1403                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1404                 break;
1405         }
1406 }
1407
1408 static void
1409 nm_default_route_manager_init (NMDefaultRouteManager *self)
1410 {
1411 }
1412
1413 static void
1414 constructed (GObject *object)
1415 {
1416         NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
1417         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1418
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);
1421
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);
1426 }
1427
1428 NMDefaultRouteManager *
1429 nm_default_route_manager_new (NMPlatform *platform)
1430 {
1431         return g_object_new (NM_TYPE_DEFAULT_ROUTE_MANAGER,
1432                              NM_DEFAULT_ROUTE_MANAGER_PLATFORM, platform,
1433                              NULL);
1434 }
1435
1436 static void
1437 dispose (GObject *object)
1438 {
1439         NMDefaultRouteManager *self = NM_DEFAULT_ROUTE_MANAGER (object);
1440         NMDefaultRouteManagerPrivate *priv = NM_DEFAULT_ROUTE_MANAGER_GET_PRIVATE (self);
1441
1442         priv->disposed = TRUE;
1443
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);
1447         }
1448
1449         _resync_idle_cancel (self);
1450
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
1456          * one-by-one.
1457          */
1458         if (priv->entries_ip4) {
1459                 g_ptr_array_free (priv->entries_ip4, TRUE);
1460                 priv->entries_ip4 = NULL;
1461         }
1462         if (priv->entries_ip6) {
1463                 g_ptr_array_free (priv->entries_ip6, TRUE);
1464                 priv->entries_ip6 = NULL;
1465         }
1466
1467         G_OBJECT_CLASS (nm_default_route_manager_parent_class)->dispose (object);
1468 }
1469
1470 static void
1471 nm_default_route_manager_class_init (NMDefaultRouteManagerClass *klass)
1472 {
1473         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1474
1475         g_type_class_add_private (klass, sizeof (NMDefaultRouteManagerPrivate));
1476
1477         /* virtual methods */
1478         object_class->constructed = constructed;
1479         object_class->dispose = dispose;
1480         object_class->set_property = set_property;
1481
1482         obj_properties[PROP_PLATFORM] =
1483             g_param_spec_object (NM_DEFAULT_ROUTE_MANAGER_PLATFORM, "", "",
1484                                  NM_TYPE_PLATFORM,
1485                                  G_PARAM_WRITABLE |
1486                                  G_PARAM_CONSTRUCT_ONLY |
1487                                  G_PARAM_STATIC_STRINGS);
1488         g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
1489
1490 }
1491