device: renew dhcp leases on awake for software devices
[NetworkManager.git] / src / nm-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) 2015 Red Hat, Inc.
19  */
20
21 #include "nm-default.h"
22
23 #include <string.h>
24
25 #include "nm-route-manager.h"
26 #include "nm-platform.h"
27 #include "nmp-object.h"
28 #include "nm-core-internal.h"
29 #include "NetworkManagerUtils.h"
30
31 /* if within half a second after adding an IP address a matching device-route shows
32  * up, we delete it. */
33 #define IP4_DEVICE_ROUTES_WAIT_TIME_NS                 (NM_UTILS_NS_PER_SECOND / 2)
34
35 #define IP4_DEVICE_ROUTES_GC_INTERVAL_SEC              (IP4_DEVICE_ROUTES_WAIT_TIME_NS * 2)
36
37 typedef struct {
38         guint len;
39         NMPlatformIPXRoute *entries[1];
40 } RouteIndex;
41
42 typedef struct {
43         GArray *entries;
44         RouteIndex *index;
45
46         /* list of effective metrics. The indexes of the array correspond to @index, not @entries. */
47         GArray *effective_metrics;
48
49         /* this array contains the effective metrics but using the reversed index that corresponds
50          * to @entries, instead of @index. */
51         GArray *effective_metrics_reverse;
52 } RouteEntries;
53
54 typedef struct {
55         NMRouteManager *self;
56         gint64 scheduled_at_ns;
57         guint idle_id;
58         NMPObject *obj;
59 } IP4DeviceRoutePurgeEntry;
60
61 typedef struct {
62         NMPlatform *platform;
63
64         RouteEntries ip4_routes;
65         RouteEntries ip6_routes;
66         struct {
67                 GHashTable *entries;
68                 guint gc_id;
69         } ip4_device_routes;
70 } NMRouteManagerPrivate;
71
72 #define NM_ROUTE_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_ROUTE_MANAGER, NMRouteManagerPrivate))
73
74 G_DEFINE_TYPE (NMRouteManager, nm_route_manager, G_TYPE_OBJECT);
75
76 NM_GOBJECT_PROPERTIES_DEFINE_BASE (
77         PROP_PLATFORM,
78 );
79
80 NM_DEFINE_SINGLETON_GETTER (NMRouteManager, nm_route_manager_get, NM_TYPE_ROUTE_MANAGER);
81
82 /*********************************************************************************************/
83
84 typedef struct {
85         const NMPlatformVTableRoute *vt;
86
87         /* a compare function for two routes that considers only the destination fields network/plen.
88          * It is a looser comparisong then @route_id_cmp(), that means that if @route_dest_cmp()
89          * returns non-zero, also @route_id_cmp() returns the same value. It also means, that
90          * sorting by @route_id_cmp() implicitly sorts by @route_dest_cmp() as well. */
91         int (*route_dest_cmp) (const NMPlatformIPXRoute *r1, const NMPlatformIPXRoute *r2);
92
93         /* a compare function for two routes that considers only the fields network/plen,metric. */
94         int (*route_id_cmp) (const NMPlatformIPXRoute *r1, const NMPlatformIPXRoute *r2);
95 } VTableIP;
96
97 static const VTableIP vtable_v4, vtable_v6;
98
99 #define VTABLE_ROUTE_INDEX(vtable, garray, idx) ((NMPlatformIPXRoute *) &((garray)->data[(idx) * (vtable)->vt->sizeof_route]))
100
101 #define VTABLE_IS_DEVICE_ROUTE(vtable, route) ((vtable)->vt->is_ip4 \
102                                                 ? ((route)->r4.gateway == 0) \
103                                                 : IN6_IS_ADDR_UNSPECIFIED (&(route)->r6.gateway) )
104
105 #define CMP_AND_RETURN_INT(a, b) \
106         G_STMT_START { \
107                 typeof(a) _a = (a), _b = (b); \
108                 \
109                 if (_a < _b) \
110                         return -1; \
111                 if (_a > _b) \
112                         return 1; \
113         } G_STMT_END
114
115 /*********************************************************************************************/
116
117 #define _NMLOG_PREFIX_NAME   "route-mgr"
118 #undef  _NMLOG_ENABLED
119 #define _NMLOG_ENABLED(level, addr_family) \
120     ({ \
121         const int __addr_family = (addr_family); \
122         const NMLogLevel __level = (level); \
123         const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
124         \
125         nm_logging_enabled (__level, __domain); \
126     })
127 #define _NMLOG(level, addr_family, ...) \
128     G_STMT_START { \
129         const int __addr_family = (addr_family); \
130         const NMLogLevel __level = (level); \
131         const NMLogDomain __domain = __addr_family == AF_INET ? LOGD_IP4 : (__addr_family == AF_INET6 ? LOGD_IP6 : LOGD_IP); \
132         \
133         if (nm_logging_enabled (__level, __domain)) { \
134             char __ch = __addr_family == AF_INET ? '4' : (__addr_family == AF_INET6 ? '6' : '-'); \
135             char __prefix[30] = _NMLOG_PREFIX_NAME; \
136             \
137             if ((self) != singleton_instance) \
138                 g_snprintf (__prefix, sizeof (__prefix), "%s%c[%p]", _NMLOG_PREFIX_NAME, __ch, (self)); \
139             else \
140                 __prefix[NM_STRLEN (_NMLOG_PREFIX_NAME)] = __ch; \
141             _nm_log ((level), (__domain), 0, \
142                      "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
143                      __prefix _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
144         } \
145     } G_STMT_END
146
147 /*********************************************************************************************/
148
149 static gboolean _ip4_device_routes_cancel (NMRouteManager *self);
150
151 /*********************************************************************************************/
152
153 #if NM_MORE_ASSERTS && !defined (G_DISABLE_ASSERT)
154 inline static void
155 ASSERT_route_index_valid (const VTableIP *vtable, const GArray *entries, const RouteIndex *index, gboolean unique_ifindexes)
156 {
157         guint i, j;
158         int c;
159         const NMPlatformIPXRoute *r1, *r2;
160         gs_unref_hashtable GHashTable *ptrs = g_hash_table_new (NULL, NULL);
161         const NMPlatformIPXRoute *r_first = NULL, *r_last = NULL;
162
163         g_assert (index);
164
165         if (entries)
166                 g_assert_cmpint (entries->len, ==, index->len);
167         else
168                 g_assert (index->len == 0);
169
170         if (index->len > 0) {
171                 r_first = VTABLE_ROUTE_INDEX (vtable, entries, 0);
172                 r_last = VTABLE_ROUTE_INDEX (vtable, entries, index->len - 1);
173         }
174
175         /* assert that the @index is valid for the @entries. */
176
177         g_assert (!index->entries[index->len]);
178         for (i = 0; i < index->len; i++) {
179                 r1 = index->entries[i];
180
181                 g_assert (r1);
182                 g_assert (r1 >= r_first);
183                 g_assert (r1 <= r_last);
184                 g_assert_cmpint ((((char *) r1) - ((char *) entries->data)) % vtable->vt->sizeof_route, ==, 0);
185
186                 g_assert (!g_hash_table_contains (ptrs, (gpointer) r1));
187                 g_hash_table_add (ptrs, (gpointer) r1);
188
189                 for (j = i; j > 0; ) {
190                         r2 = index->entries[--j];
191
192                         c = vtable->route_id_cmp (r1, r2);
193                         g_assert (c >= 0);
194                         if (c != 0)
195                                 break;
196                         if (unique_ifindexes)
197                                 g_assert_cmpint (r1->rx.ifindex, !=, r2->rx.ifindex);
198                 }
199         }
200 }
201 #else
202 #define ASSERT_route_index_valid(vtable, entries, index, unique_ifindexes) G_STMT_START { (void) 0; } G_STMT_END
203 #endif
204
205 /*********************************************************************************************/
206
207 static int
208 _v4_route_dest_cmp (const NMPlatformIP4Route *r1, const NMPlatformIP4Route *r2)
209 {
210         CMP_AND_RETURN_INT (r1->plen, r2->plen);
211         CMP_AND_RETURN_INT (nm_utils_ip4_address_clear_host_address (r1->network, r1->plen),
212                             nm_utils_ip4_address_clear_host_address (r2->network, r2->plen));
213         return 0;
214 }
215
216 static int
217 _v6_route_dest_cmp (const NMPlatformIP6Route *r1, const NMPlatformIP6Route *r2)
218 {
219         struct in6_addr n1, n2;
220
221         CMP_AND_RETURN_INT (r1->plen, r2->plen);
222
223         nm_utils_ip6_address_clear_host_address (&n1, &r1->network, r1->plen);
224         nm_utils_ip6_address_clear_host_address (&n2, &r2->network, r2->plen);
225         return memcmp (&n1, &n2, sizeof (n1));
226 }
227
228 static int
229 _v4_route_id_cmp (const NMPlatformIP4Route *r1, const NMPlatformIP4Route *r2)
230 {
231         CMP_AND_RETURN_INT (r1->plen, r2->plen);
232         CMP_AND_RETURN_INT (nm_utils_ip4_address_clear_host_address (r1->network, r1->plen),
233                             nm_utils_ip4_address_clear_host_address (r2->network, r2->plen));
234         CMP_AND_RETURN_INT (r1->metric, r2->metric);
235         return 0;
236 }
237
238 static int
239 _v6_route_id_cmp (const NMPlatformIP6Route *r1, const NMPlatformIP6Route *r2)
240 {
241         struct in6_addr n1, n2;
242         int c;
243
244         CMP_AND_RETURN_INT (r1->plen, r2->plen);
245
246         nm_utils_ip6_address_clear_host_address (&n1, &r1->network, r1->plen);
247         nm_utils_ip6_address_clear_host_address (&n2, &r2->network, r2->plen);
248         c = memcmp (&n1, &n2, sizeof (n1));
249         if (c != 0)
250                 return c;
251
252         CMP_AND_RETURN_INT (nm_utils_ip6_route_metric_normalize (r1->metric),
253                             nm_utils_ip6_route_metric_normalize (r2->metric));
254         return 0;
255 }
256
257 /*********************************************************************************************/
258
259 static int
260 _route_index_create_sort (const NMPlatformIPXRoute **p1, const NMPlatformIPXRoute ** p2, const VTableIP *vtable)
261 {
262         return vtable->route_id_cmp (*p1, *p2);
263 }
264
265 static RouteIndex *
266 _route_index_create (const VTableIP *vtable, const GArray *routes)
267 {
268         RouteIndex *index;
269         guint i;
270         guint len = routes ? routes->len : 0;
271
272         index = g_malloc (sizeof (RouteIndex) + len * sizeof (NMPlatformIPXRoute *));
273
274         index->len = len;
275         for (i = 0; i < len; i++)
276                 index->entries[i] = VTABLE_ROUTE_INDEX (vtable, routes, i);
277         index->entries[i] = NULL;
278
279         /* this is a stable sort, which is very important at this point. */
280         g_qsort_with_data (index->entries,
281                            len,
282                            sizeof (NMPlatformIPXRoute *),
283                            (GCompareDataFunc) _route_index_create_sort,
284                            (gpointer) vtable);
285         return index;
286 }
287
288 static int
289 _vx_route_id_cmp_full (const NMPlatformIPXRoute *r1, const NMPlatformIPXRoute *r2, const VTableIP *vtable)
290 {
291         return vtable->route_id_cmp (r1, r2);
292 }
293
294 static gssize
295 _route_index_find (const VTableIP *vtable, const RouteIndex *index, const NMPlatformIPXRoute *needle)
296 {
297         gssize idx, idx2;
298
299         idx = _nm_utils_ptrarray_find_binary_search ((gpointer *) index->entries, index->len, (gpointer) needle, (GCompareDataFunc) _vx_route_id_cmp_full, (gpointer) vtable);
300         if (idx < 0)
301                 return idx;
302
303         /* we only know that the route at index @idx has matching destination. Also find the one with the right
304          * ifindex by searching the neighbours */
305
306         idx2 = idx;
307         do {
308                 if (index->entries[idx2]->rx.ifindex == needle->rx.ifindex)
309                         return idx2;
310         } while (   idx2 > 0
311                  && vtable->route_id_cmp (index->entries[--idx2], needle) != 0);
312
313         for (idx++; idx < index->len; idx++ ){
314                 if (vtable->route_id_cmp (index->entries[idx], needle) != 0)
315                         break;
316                 if (index->entries[idx]->rx.ifindex == needle->rx.ifindex)
317                         return idx;
318         }
319
320         return ~idx;
321 }
322
323 static guint
324 _route_index_reverse_idx (const VTableIP *vtable, const RouteIndex *index, guint idx_idx, const GArray *routes)
325 {
326         const NMPlatformIPXRoute *r, *r0;
327         gssize offset;
328
329         /* reverse the @idx_idx that points into @index, to the corresponding index into the unsorted @routes array. */
330
331         r = index->entries[idx_idx];
332         r0 = VTABLE_ROUTE_INDEX (vtable, routes, 0);
333
334         if (vtable->vt->is_ip4)
335                 offset = &r->r4 - &r0->r4;
336         else
337                 offset = &r->r6 - &r0->r6;
338         g_assert (offset >= 0 && offset < index->len);
339         g_assert (VTABLE_ROUTE_INDEX (vtable, routes, offset) == r);
340         return offset;
341 }
342
343 /*********************************************************************************************/
344
345 static gboolean
346 _route_equals_ignoring_ifindex (const VTableIP *vtable, const NMPlatformIPXRoute *r1, const NMPlatformIPXRoute *r2, gint64 r2_metric)
347 {
348         NMPlatformIPXRoute r2_backup;
349
350         if (   r1->rx.ifindex != r2->rx.ifindex
351             || (r2_metric >= 0 && ((guint32) r2_metric) != r2->rx.metric)) {
352                 memcpy (&r2_backup, r2, vtable->vt->sizeof_route);
353                 r2_backup.rx.ifindex = r1->rx.ifindex;
354                 if (r2_metric >= 0)
355                         r2_backup.rx.metric = (guint32) r2_metric;
356                 r2 = &r2_backup;
357         }
358         return vtable->vt->route_cmp (r1, r2) == 0;
359 }
360
361 static NMPlatformIPXRoute *
362 _get_next_ipx_route (const RouteIndex *index, gboolean start_at_zero, guint *cur_idx, int ifindex)
363 {
364         guint i;
365
366         if (start_at_zero)
367                 i = 0;
368         else
369                 i = *cur_idx + 1;
370         /* Find the next route with matching @ifindex. */
371         for (; i < index->len; i++) {
372                 if (index->entries[i]->rx.ifindex == ifindex) {
373                         *cur_idx = i;
374                         return index->entries[i];
375                 }
376         }
377         *cur_idx = index->len;
378         return NULL;
379 }
380
381 static const NMPlatformIPXRoute *
382 _get_next_known_route (const VTableIP *vtable, const RouteIndex *index, gboolean start_at_zero, guint *cur_idx)
383 {
384         guint i = 0;
385         const NMPlatformIPXRoute *cur = NULL;
386
387         if (!start_at_zero) {
388                 i = *cur_idx;
389                 cur = index->entries[i];
390                 i++;
391         }
392         /* For @known_routes we expect that all routes have the same @ifindex. This is not enforced however,
393          * the ifindex value of these routes is ignored. */
394         for (; i < index->len; i++) {
395                 const NMPlatformIPXRoute *r = index->entries[i];
396
397                 /* skip over default routes. */
398                 if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (r))
399                         continue;
400
401                 /* @known_routes should not, but could contain duplicate routes. Skip over them. */
402                 if (cur && vtable->route_id_cmp (cur, r) == 0)
403                         continue;
404
405                 *cur_idx = i;
406                 return r;
407         }
408         *cur_idx = index->len;
409         return NULL;
410 }
411
412 static const NMPlatformIPXRoute *
413 _get_next_plat_route (const RouteIndex *index, gboolean start_at_zero, guint *cur_idx)
414 {
415         if (start_at_zero)
416                 *cur_idx = 0;
417         else
418                 ++*cur_idx;
419
420         /* get next route from the platform index. */
421         if (*cur_idx < index->len)
422                 return index->entries[*cur_idx];
423         *cur_idx = index->len;
424         return NULL;
425 }
426
427 static int
428 _sort_indexes_cmp (guint *a, guint *b)
429 {
430         CMP_AND_RETURN_INT (*a, *b);
431         g_return_val_if_reached (0);
432 }
433
434 /*********************************************************************************************/
435
436 static gboolean
437 _vx_route_sync (const VTableIP *vtable, NMRouteManager *self, int ifindex, const GArray *known_routes, gboolean ignore_kernel_routes, gboolean full_sync)
438 {
439         NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
440         GArray *plat_routes;
441         RouteEntries *ipx_routes;
442         RouteIndex *plat_routes_idx, *known_routes_idx;
443         gboolean success = TRUE;
444         guint i, i_type;
445         GArray *to_delete_indexes = NULL;
446         GPtrArray *to_add_routes = NULL;
447         guint i_known_routes, i_plat_routes, i_ipx_routes;
448         const NMPlatformIPXRoute *cur_known_route, *cur_plat_route;
449         NMPlatformIPXRoute *cur_ipx_route;
450         gint64 *p_effective_metric = NULL;
451         gboolean ipx_routes_changed = FALSE;
452         gint64 *effective_metrics = NULL;
453
454         nm_platform_process_events (priv->platform);
455
456         ipx_routes = vtable->vt->is_ip4 ? &priv->ip4_routes : &priv->ip6_routes;
457         plat_routes = vtable->vt->route_get_all (priv->platform, ifindex,
458                                                  ignore_kernel_routes
459                                                      ? NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT
460                                                      : NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_RTPROT_KERNEL);
461         plat_routes_idx = _route_index_create (vtable, plat_routes);
462         known_routes_idx = _route_index_create (vtable, known_routes);
463
464         effective_metrics = &g_array_index (ipx_routes->effective_metrics, gint64, 0);
465
466         ASSERT_route_index_valid (vtable, plat_routes, plat_routes_idx, TRUE);
467         ASSERT_route_index_valid (vtable, known_routes, known_routes_idx, FALSE);
468
469         _LOGD (vtable->vt->addr_family, "%3d: sync %u IPv%c routes", ifindex, known_routes_idx->len, vtable->vt->is_ip4 ? '4' : '6');
470         if (_LOGt_ENABLED (vtable->vt->addr_family)) {
471                 for (i = 0; i < known_routes_idx->len; i++) {
472                         _LOGt (vtable->vt->addr_family, "%3d: sync new route #%u: %s",
473                                ifindex, i, vtable->vt->route_to_string (VTABLE_ROUTE_INDEX (vtable, known_routes, i), NULL, 0));
474                 }
475                 for (i = 0; i < ipx_routes->index->len; i++)
476                         _LOGt (vtable->vt->addr_family, "%3d: STATE: has     #%u - %s (%lld)",
477                                ifindex, i,
478                                vtable->vt->route_to_string (ipx_routes->index->entries[i], NULL, 0),
479                                (long long) g_array_index (ipx_routes->effective_metrics, gint64, i));
480         }
481
482         /***************************************************************************
483          * Check which routes are in @known_routes, and update @ipx_routes.
484          *
485          * This first part only updates @ipx_routes to find out what routes must
486          * be added/deleted.
487          **************************************************************************/
488
489         /* iterate over @ipx_routes and @known_routes */
490         cur_ipx_route = _get_next_ipx_route (ipx_routes->index, TRUE, &i_ipx_routes, ifindex);
491         cur_known_route = _get_next_known_route (vtable, known_routes_idx, TRUE, &i_known_routes);
492         while (cur_ipx_route || cur_known_route) {
493                 int route_id_cmp_result = -1;
494
495                 while (   cur_ipx_route
496                        && (   !cur_known_route
497                            || ((route_id_cmp_result = vtable->route_id_cmp (cur_ipx_route, cur_known_route)) < 0))) {
498                         /* we have @cur_ipx_route, which is less then @cur_known_route. Hence,
499                          * the route does no longer exist in @known_routes */
500                         if (!to_delete_indexes)
501                                 to_delete_indexes = g_array_new (FALSE, FALSE, sizeof (guint));
502                         g_array_append_val (to_delete_indexes, i_ipx_routes);
503
504                         /* find the next @cur_ipx_route with matching ifindex. */
505                         cur_ipx_route = _get_next_ipx_route (ipx_routes->index, FALSE, &i_ipx_routes, ifindex);
506                 }
507                 if (   cur_ipx_route
508                     && cur_known_route
509                     && route_id_cmp_result == 0) {
510                         if (!_route_equals_ignoring_ifindex (vtable, cur_ipx_route, cur_known_route, -1)) {
511                                 /* The routes match. Update the entry in place. As this is an exact match of primary
512                                  * fields, this only updates possibly modified fields such as @gateway or @mss.
513                                  * Modifiying @cur_ipx_route this way does not invalidate @ipx_routes->index. */
514                                 memcpy (cur_ipx_route, cur_known_route, vtable->vt->sizeof_route);
515                                 cur_ipx_route->rx.ifindex = ifindex;
516                                 cur_ipx_route->rx.metric = vtable->vt->metric_normalize (cur_ipx_route->rx.metric);
517                                 ipx_routes_changed = TRUE;
518                                 _LOGt (vtable->vt->addr_family, "%3d: STATE: update  #%u - %s", ifindex, i_ipx_routes,
519                                        vtable->vt->route_to_string (cur_ipx_route, NULL, 0));
520                         }
521                 } else if (cur_known_route) {
522                         g_assert (!cur_ipx_route || route_id_cmp_result > 0);
523                         /* @cur_known_route is new. We cannot immediately add @cur_known_route to @ipx_routes, because
524                          * it would invalidate @ipx_routes->index. Instead remember to add it later. */
525                         if (!to_add_routes)
526                                 to_add_routes = g_ptr_array_new ();
527                         g_ptr_array_add (to_add_routes, (gpointer) cur_known_route);
528                 }
529
530                 if (cur_ipx_route && (!cur_known_route || route_id_cmp_result == 0))
531                         cur_ipx_route = _get_next_ipx_route (ipx_routes->index, FALSE, &i_ipx_routes, ifindex);
532                 if (cur_known_route)
533                         cur_known_route = _get_next_known_route (vtable, known_routes_idx, FALSE, &i_known_routes);
534         }
535
536         if (!full_sync && to_delete_indexes) {
537                 /***************************************************************************
538                  * Delete routes in platform, that we are about to remove from @ipx_routes
539                  *
540                  * When doing a non-full_sync, we delete routes from platform that were previously
541                  * known by route-manager, and are now deleted.
542                  ***************************************************************************/
543
544                 /* iterate over @to_delete_indexes and @plat_routes.
545                  * @to_delete_indexes contains the indexes (relative to ipx_routes->index) of items
546                  * we are about to delete. */
547                 cur_plat_route = _get_next_plat_route (plat_routes_idx, TRUE, &i_plat_routes);
548                 for (i = 0; i < to_delete_indexes->len; i++) {
549                         int route_dest_cmp_result = 0;
550                         i_ipx_routes = g_array_index (to_delete_indexes, guint, i);
551                         cur_ipx_route = ipx_routes->index->entries[i_ipx_routes];
552                         p_effective_metric = &effective_metrics[i_ipx_routes];
553
554                         nm_assert (cur_ipx_route->rx.ifindex == ifindex);
555
556                         if (*p_effective_metric == -1)
557                                 continue;
558
559                         /* skip over @plat_routes that are ordered before our @cur_ipx_route. */
560                         while (   cur_plat_route
561                                && (route_dest_cmp_result = vtable->route_dest_cmp (cur_plat_route, cur_ipx_route)) <= 0) {
562                                 if (   route_dest_cmp_result == 0
563                                     && cur_plat_route->rx.metric >= *p_effective_metric)
564                                         break;
565                                 cur_plat_route = _get_next_plat_route (plat_routes_idx, FALSE, &i_plat_routes);
566                         }
567
568                         if (!cur_plat_route) {
569                                 /* no more platform routes. Break the loop. */
570                                 break;
571                         }
572
573                         if (   route_dest_cmp_result == 0
574                             && cur_plat_route->rx.metric == *p_effective_metric) {
575                                 /* we are about to delete cur_ipx_route and we have a matching route
576                                  * in platform. Delete it. */
577                                 _LOGt (vtable->vt->addr_family, "%3d: platform rt-rm #%u - %s", ifindex, i_plat_routes,
578                                        vtable->vt->route_to_string (cur_plat_route, NULL, 0));
579                                 vtable->vt->route_delete (priv->platform, ifindex, cur_plat_route);
580                         }
581                 }
582         }
583
584         /* Update @ipx_routes with the just learned changes. */
585         if (to_delete_indexes || to_add_routes) {
586                 if (to_delete_indexes) {
587                         for (i = 0; i < to_delete_indexes->len; i++) {
588                                 guint idx = g_array_index (to_delete_indexes, guint, i);
589
590                                 _LOGt (vtable->vt->addr_family, "%3d: STATE: delete  #%u - %s", ifindex, idx,
591                                        vtable->vt->route_to_string (ipx_routes->index->entries[idx], NULL, 0));
592                                 g_array_index (to_delete_indexes, guint, i) = _route_index_reverse_idx (vtable, ipx_routes->index, idx, ipx_routes->entries);
593                         }
594                         g_array_sort (to_delete_indexes, (GCompareFunc) _sort_indexes_cmp);
595                         nm_utils_array_remove_at_indexes (ipx_routes->entries, &g_array_index (to_delete_indexes, guint, 0), to_delete_indexes->len);
596                         nm_utils_array_remove_at_indexes (ipx_routes->effective_metrics_reverse, &g_array_index (to_delete_indexes, guint, 0), to_delete_indexes->len);
597                         g_array_unref (to_delete_indexes);
598                 }
599                 if (to_add_routes) {
600                         guint j = ipx_routes->effective_metrics_reverse->len;
601
602                         g_array_set_size (ipx_routes->effective_metrics_reverse, j + to_add_routes->len);
603
604                         for (i = 0; i < to_add_routes->len; i++) {
605                                 NMPlatformIPXRoute *ipx_route;
606
607                                 g_array_append_vals (ipx_routes->entries, g_ptr_array_index (to_add_routes, i), 1);
608
609                                 ipx_route = VTABLE_ROUTE_INDEX (vtable, ipx_routes->entries, ipx_routes->entries->len - 1);
610                                 ipx_route->rx.ifindex = ifindex;
611                                 ipx_route->rx.metric = vtable->vt->metric_normalize (ipx_route->rx.metric);
612
613                                 g_array_index (ipx_routes->effective_metrics_reverse, gint64, j++) = -1;
614
615                                 _LOGt (vtable->vt->addr_family, "%3d: STATE: added   #%u - %s", ifindex, ipx_routes->entries->len - 1,
616                                        vtable->vt->route_to_string (ipx_route, NULL, 0));
617                         }
618                         g_ptr_array_unref (to_add_routes);
619                 }
620                 g_free (ipx_routes->index);
621                 ipx_routes->index = _route_index_create (vtable, ipx_routes->entries);
622                 ipx_routes_changed = TRUE;
623                 ASSERT_route_index_valid (vtable, ipx_routes->entries, ipx_routes->index, TRUE);
624         }
625
626         if (ipx_routes_changed) {
627                 /***************************************************************************
628                  * Rebuild the list of effective metrics. In case of conflicting routes,
629                  * we configure device routes with a bumped metric. We do this, because non-direct
630                  * routes might require this direct route to reach the gateway (e.g. the default
631                  * route).
632                  *
633                  * We determine the effective metrics only based on our internal list @ipx_routes
634                  * and don't consider @plat_routes. That means, we might bump the metric of a route
635                  * and thereby cause a conflict with an existing route on an unmanaged device (which
636                  * causes the route on the unmanaged device to be replaced).
637                  * Still, that is not much different then from messing with unmanaged routes when
638                  * the effective and the intended metrics equal. The rules is: NM will leave routes
639                  * on unmanaged devices alone, unless they conflict with what NM wants to configure.
640                  ***************************************************************************/
641
642                 g_array_set_size (ipx_routes->effective_metrics, ipx_routes->entries->len);
643                 effective_metrics = &g_array_index (ipx_routes->effective_metrics, gint64, 0);
644
645                 /* Completely regenerate the list of effective metrics by walking through
646                  * ipx_routes->index and determining the effective metric. */
647
648                 for (i_ipx_routes = 0; i_ipx_routes < ipx_routes->index->len; i_ipx_routes++) {
649                         gint64 *p_effective_metric_before;
650                         gboolean is_shadowed;
651                         guint i_ipx_routes_before;
652
653                         cur_ipx_route = ipx_routes->index->entries[i_ipx_routes];
654                         p_effective_metric = &effective_metrics[i_ipx_routes];
655
656                         is_shadowed =    i_ipx_routes > 0
657                                       && vtable->route_dest_cmp (cur_ipx_route, ipx_routes->index->entries[i_ipx_routes - 1]) == 0;
658
659                         if (!is_shadowed) {
660                                 /* the route is not shadowed, the effective metric is just as specified. */
661                                 *p_effective_metric = cur_ipx_route->rx.metric;
662                                 goto next;
663                         }
664                         if (!VTABLE_IS_DEVICE_ROUTE (vtable, cur_ipx_route)) {
665                                 /* The route is not a device route. We want to add redundant device routes, because
666                                  * we might need the direct routes to the gateway. For non-direct routes, there is not much
667                                  * reason to do the metric increment. */
668                                 *p_effective_metric = -1;
669                                 goto next;
670                         }
671
672                         /* The current route might be shadowed by several other routes. Find the one with the highest metric,
673                          * i.e. the one with an effecive metric set and in the index before the current index. */
674                         i_ipx_routes_before = i_ipx_routes;
675                         while (TRUE) {
676                                 nm_assert (i_ipx_routes_before > 0);
677
678                                 i_ipx_routes_before--;
679
680                                 p_effective_metric_before = &effective_metrics[i_ipx_routes_before];
681
682                                 if (*p_effective_metric_before == -1) {
683                                         /* this route is also shadowed, continue search. */
684                                         continue;
685                                 }
686
687                                 if (*p_effective_metric_before < cur_ipx_route->rx.metric) {
688                                         /* the previous route has a lower metric. There is no conflict,
689                                          * just use the original metric. */
690                                         *p_effective_metric = cur_ipx_route->rx.metric;
691                                 } else if (*p_effective_metric_before == G_MAXUINT32) {
692                                         /* we cannot bump the metric. Don't configure this route. */
693                                         *p_effective_metric = -1;
694                                 } else {
695                                         /* bump the metric by one. */
696                                         *p_effective_metric = *p_effective_metric_before + 1;
697                                 }
698                                 break;
699                         }
700 next:
701                         _LOGt (vtable->vt->addr_family, "%3d: new metric     #%u - %s (%lld)",
702                                ifindex, i_ipx_routes,
703                                vtable->vt->route_to_string (cur_ipx_route, NULL, 0),
704                                (long long) *p_effective_metric);
705                 }
706         }
707
708         if (full_sync) {
709                 /***************************************************************************
710                  * Delete all routes in platform, that no longer exist in @ipx_routes
711                  *
712                  * Different from the delete action above, we delete every unknown route on
713                  * the interface.
714                  ***************************************************************************/
715
716                 /* iterate over @plat_routes and @ipx_routes */
717                 cur_plat_route = _get_next_plat_route (plat_routes_idx, TRUE, &i_plat_routes);
718                 cur_ipx_route = _get_next_ipx_route (ipx_routes->index, TRUE, &i_ipx_routes, ifindex);
719                 if (cur_ipx_route)
720                         p_effective_metric = &effective_metrics[i_ipx_routes];
721                 while (cur_plat_route) {
722                         int route_dest_cmp_result = 0;
723
724                         g_assert (cur_plat_route->rx.ifindex == ifindex);
725
726                         _LOGt (vtable->vt->addr_family, "%3d: platform rt    #%u - %s", ifindex, i_plat_routes, vtable->vt->route_to_string (cur_plat_route, NULL, 0));
727
728                         /* skip over @cur_ipx_route that are ordered before @cur_plat_route */
729                         while (   cur_ipx_route
730                                && ((route_dest_cmp_result = vtable->route_dest_cmp (cur_ipx_route, cur_plat_route)) <= 0)) {
731                                 if (   route_dest_cmp_result == 0
732                                     && *p_effective_metric != -1
733                                     && *p_effective_metric >= cur_plat_route->rx.metric) {
734                                         break;
735                                 }
736                                 cur_ipx_route = _get_next_ipx_route (ipx_routes->index, FALSE, &i_ipx_routes, ifindex);
737                                 if (cur_ipx_route)
738                                         p_effective_metric = &effective_metrics[i_ipx_routes];
739                         }
740
741                         /* if @cur_ipx_route is not equal to @plat_route, the route must be deleted. */
742                         if (   !cur_ipx_route
743                             || route_dest_cmp_result != 0
744                             || *p_effective_metric != cur_plat_route->rx.metric)
745                                 vtable->vt->route_delete (priv->platform, ifindex, cur_plat_route);
746
747                         cur_plat_route = _get_next_plat_route (plat_routes_idx, FALSE, &i_plat_routes);
748                 }
749         }
750
751         /***************************************************************************
752          * Restore shadowed routes. These routes are on an other @ifindex then what
753          * we are syncing now. But the current changes make it necessary to add those
754          * routes.
755          *
756          * Only add some routes that might be necessary. We don't delete any routes
757          * on other ifindexes here. I.e. we don't do a full sync, but only ~add~ routes
758          * that were shadowed previously, but should be now present with a different
759          * metric.
760          **************************************************************************/
761
762         if (ipx_routes_changed) {
763                 GArray *gateway_routes = NULL;
764
765                 /* @effective_metrics_reverse contains the list of assigned metrics from the last
766                  * sync. Walk through it and see what changes there are (and possibly restore a
767                  * shadowed route).
768                  * Thereby also update @effective_metrics_reverse to be up-to-date again. */
769                 for (i_ipx_routes = 0; i_ipx_routes < ipx_routes->entries->len; i_ipx_routes++) {
770                         guint i_ipx_routes_reverse;
771                         gint64 *p_effective_metric_reversed;
772
773                         p_effective_metric = &effective_metrics[i_ipx_routes];
774
775                         i_ipx_routes_reverse = _route_index_reverse_idx (vtable, ipx_routes->index, i_ipx_routes, ipx_routes->entries);
776                         p_effective_metric_reversed = &g_array_index (ipx_routes->effective_metrics_reverse, gint64, i_ipx_routes_reverse);
777
778                         if (*p_effective_metric_reversed == *p_effective_metric) {
779                                 /* The entry is up to date. No change, continue with the next one. */
780                                 continue;
781                         }
782                         *p_effective_metric_reversed = *p_effective_metric;
783
784                         if (*p_effective_metric == -1) {
785                                 /* the entry is shadowed. Nothing to do. */
786                                 continue;
787                         }
788
789                         cur_ipx_route = ipx_routes->index->entries[i_ipx_routes];
790                         if (cur_ipx_route->rx.ifindex == ifindex) {
791                                 /* @cur_ipx_route is on the current @ifindex. No need to special handling them
792                                  * because we are about to do a full sync of the ifindex. */
793                                 continue;
794                         }
795
796                         /* the effective metric from previous sync changed. While @cur_ipx_route is not on the
797                          * ifindex we are about to sync, we still must add this route. Possibly it was shadowed
798                          * before, and now we want to restore it.
799                          *
800                          * Note that we don't do a full sync on the other ifindex. Especially, we don't delete
801                          * or add any further routes then this. That means there might be some stale routes
802                          * (with a higher metric!). They will only be removed on the next sync of that other
803                          * ifindex. */
804
805                         if (!VTABLE_IS_DEVICE_ROUTE (vtable, cur_ipx_route)) {
806                                 /* the route to restore has a gateway. We can only restore the route
807                                  * when we also have a direct route to the gateway. There can be cases
808                                  * where the direct route is shadowed too, and we cannot restore the gateway
809                                  * route.
810                                  *
811                                  * Restore first the direct-routes, and gateway-routes afterwards.
812                                  * This can avoid some cases where we would fail to add the
813                                  * gateway route. */
814                                 if (!gateway_routes)
815                                         gateway_routes = g_array_new (FALSE, FALSE, sizeof (guint));
816                                 g_array_append_val (gateway_routes, i_ipx_routes);
817                         } else
818                                 vtable->vt->route_add (priv->platform, 0, cur_ipx_route, *p_effective_metric);
819                 }
820
821                 if (gateway_routes) {
822                         for (i = 0; i < gateway_routes->len; i++) {
823                                 i_ipx_routes = g_array_index (gateway_routes, guint, i);
824                                 vtable->vt->route_add (priv->platform, 0,
825                                                        ipx_routes->index->entries[i_ipx_routes],
826                                                        effective_metrics[i_ipx_routes]);
827                         }
828                         g_array_unref (gateway_routes);
829                 }
830         }
831
832         /***************************************************************************
833          * Sync @ipx_routes for @ifindex to platform
834          **************************************************************************/
835
836         for (i_type = 0; i_type < 2; i_type++) {
837                 /* iterate (twice) over @ipx_routes and @plat_routes */
838                 cur_plat_route = _get_next_plat_route (plat_routes_idx, TRUE, &i_plat_routes);
839                 cur_ipx_route = _get_next_ipx_route (ipx_routes->index, TRUE, &i_ipx_routes, ifindex);
840                 /* Iterate here over @ipx_routes instead of @known_routes. That is done because
841                  * we need to know whether a route is shadowed by another route, and that
842                  * requires to look at @ipx_routes. */
843                 for (; cur_ipx_route; cur_ipx_route = _get_next_ipx_route (ipx_routes->index, FALSE, &i_ipx_routes, ifindex)) {
844                         int route_dest_cmp_result = -1;
845
846                         if (   (i_type == 0 && !VTABLE_IS_DEVICE_ROUTE (vtable, cur_ipx_route))
847                             || (i_type == 1 && VTABLE_IS_DEVICE_ROUTE (vtable, cur_ipx_route))) {
848                                 /* Make two runs over the list of @ipx_routes. On the first, only add
849                                  * device routes, on the second the others (gateway routes). */
850                                 continue;
851                         }
852
853                         p_effective_metric = &effective_metrics[i_ipx_routes];
854
855                         if (*p_effective_metric == -1) {
856                                 /* @cur_ipx_route is shadewed by another route. */
857                                 continue;
858                         }
859
860                         /* skip over @plat_routes that are ordered before our @cur_ipx_route. */
861                         while (   cur_plat_route
862                                && (route_dest_cmp_result = vtable->route_dest_cmp (cur_plat_route, cur_ipx_route)) <= 0) {
863                                 if (   route_dest_cmp_result == 0
864                                     && cur_plat_route->rx.metric >= *p_effective_metric)
865                                         break;
866                                 cur_plat_route = _get_next_plat_route (plat_routes_idx, FALSE, &i_plat_routes);
867                         }
868
869                         /* only add the route if we don't have an identical route in @plat_routes,
870                          * i.e. if @cur_plat_route is different from @cur_ipx_route. */
871                         if (   !cur_plat_route
872                             || route_dest_cmp_result != 0
873                             || !_route_equals_ignoring_ifindex (vtable, cur_plat_route, cur_ipx_route, *p_effective_metric)) {
874
875                                 if (!vtable->vt->route_add (priv->platform, ifindex, cur_ipx_route, *p_effective_metric)) {
876                                         if (cur_ipx_route->rx.source < NM_IP_CONFIG_SOURCE_USER) {
877                                                 _LOGD (vtable->vt->addr_family,
878                                                        "ignore error adding IPv%c route to kernel: %s",
879                                                        vtable->vt->is_ip4 ? '4' : '6',
880                                                        vtable->vt->route_to_string (cur_ipx_route, NULL, 0));
881                                         } else {
882                                                 /* Remember that there was a failure, but for now continue trying
883                                                  * to sync the remaining routes. */
884                                                 success = FALSE;
885                                         }
886                                 }
887                         }
888                 }
889         }
890
891         g_free (known_routes_idx);
892         g_free (plat_routes_idx);
893         g_array_unref (plat_routes);
894
895         return success;
896 }
897
898 /**
899  * nm_route_manager_ip4_route_sync:
900  * @ifindex: Interface index
901  * @known_routes: List of routes
902  * @ignore_kernel_routes: if %TRUE, ignore kernel routes.
903  * @full_sync: whether to do a full sync and delete routes
904  *   that are configured on the interface but not currently
905  *   tracked by route-manager.
906  *
907  * A convenience function to synchronize routes for a specific interface
908  * with the least possible disturbance. It simply removes routes that are
909  * not listed and adds routes that are.
910  * Default routes are ignored (both in @known_routes and those already
911  * configured on the device).
912  *
913  * Returns: %TRUE on success.
914  */
915 gboolean
916 nm_route_manager_ip4_route_sync (NMRouteManager *self, int ifindex, const GArray *known_routes, gboolean ignore_kernel_routes, gboolean full_sync)
917 {
918         return _vx_route_sync (&vtable_v4, self, ifindex, known_routes, ignore_kernel_routes, full_sync);
919 }
920
921 /**
922  * nm_route_manager_ip6_route_sync:
923  * @ifindex: Interface index
924  * @known_routes: List of routes
925  * @ignore_kernel_routes: if %TRUE, ignore kernel routes.
926  * @full_sync: whether to do a full sync and delete routes
927  *   that are configured on the interface but not currently
928  *   tracked by route-manager.
929  *
930  * A convenience function to synchronize routes for a specific interface
931  * with the least possible disturbance. It simply removes routes that are
932  * not listed and adds routes that are.
933  * Default routes are ignored (both in @known_routes and those already
934  * configured on the device).
935  *
936  * Returns: %TRUE on success.
937  */
938 gboolean
939 nm_route_manager_ip6_route_sync (NMRouteManager *self, int ifindex, const GArray *known_routes, gboolean ignore_kernel_routes, gboolean full_sync)
940 {
941         return _vx_route_sync (&vtable_v6, self, ifindex, known_routes, ignore_kernel_routes, full_sync);
942 }
943
944 gboolean
945 nm_route_manager_route_flush (NMRouteManager *self, int ifindex)
946 {
947         bool success = TRUE;
948
949         success &= (bool) nm_route_manager_ip4_route_sync (self, ifindex, NULL, FALSE, TRUE);
950         success &= (bool) nm_route_manager_ip6_route_sync (self, ifindex, NULL, FALSE, TRUE);
951         return success;
952 }
953
954 /*********************************************************************************************/
955
956 static gboolean
957 _ip4_device_routes_entry_expired (const IP4DeviceRoutePurgeEntry *entry, gint64 now)
958 {
959         return entry->scheduled_at_ns + IP4_DEVICE_ROUTES_WAIT_TIME_NS < now;
960 }
961
962 static IP4DeviceRoutePurgeEntry *
963 _ip4_device_routes_purge_entry_create (NMRouteManager *self, const NMPlatformIP4Route *route, gint64 now_ns)
964 {
965         IP4DeviceRoutePurgeEntry *entry;
966
967         entry = g_slice_new (IP4DeviceRoutePurgeEntry);
968
969         entry->self = self;
970         entry->scheduled_at_ns = now_ns;
971         entry->idle_id = 0;
972         entry->obj = nmp_object_new (NMP_OBJECT_TYPE_IP4_ROUTE, (NMPlatformObject *) route);
973         return entry;
974 }
975
976 static void
977 _ip4_device_routes_purge_entry_free (IP4DeviceRoutePurgeEntry *entry)
978 {
979         nmp_object_unref (entry->obj);
980         nm_clear_g_source (&entry->idle_id);
981         g_slice_free (IP4DeviceRoutePurgeEntry, entry);
982 }
983
984 static gboolean
985 _ip4_device_routes_idle_cb (IP4DeviceRoutePurgeEntry *entry)
986 {
987         NMRouteManager *self;
988         NMRouteManagerPrivate *priv;
989
990         nm_clear_g_source (&entry->idle_id);
991
992         self = entry->self;
993         priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
994         if (_route_index_find (&vtable_v4, priv->ip4_routes.index, &entry->obj->ipx_route) >= 0) {
995                 /* we have an identical route in our list. Don't delete it. */
996                 return G_SOURCE_REMOVE;
997         }
998
999         _LOGt (vtable_v4.vt->addr_family, "device-route: delete %s", nmp_object_to_string (entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
1000
1001         nm_platform_ip4_route_delete (priv->platform,
1002                                       entry->obj->ip4_route.ifindex,
1003                                       entry->obj->ip4_route.network,
1004                                       entry->obj->ip4_route.plen,
1005                                       entry->obj->ip4_route.metric);
1006
1007         g_hash_table_remove (priv->ip4_device_routes.entries, entry->obj);
1008         _ip4_device_routes_cancel (self);
1009         return G_SOURCE_REMOVE;
1010 }
1011
1012 static void
1013 _ip4_device_routes_ip4_route_changed (NMPlatform *platform,
1014                                       NMPObjectType obj_type,
1015                                       int ifindex,
1016                                       const NMPlatformIP4Route *route,
1017                                       NMPlatformSignalChangeType change_type,
1018                                       NMRouteManager *self)
1019 {
1020         NMRouteManagerPrivate *priv;
1021         NMPObject obj_needle;
1022         IP4DeviceRoutePurgeEntry *entry;
1023
1024         if (change_type == NM_PLATFORM_SIGNAL_REMOVED)
1025                 return;
1026
1027         if (   route->source != NM_IP_CONFIG_SOURCE_RTPROT_KERNEL
1028             || route->metric != 0) {
1029                 /* we don't have an automatically created device route at hand. Bail out early. */
1030                 return;
1031         }
1032
1033         priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
1034
1035         entry = g_hash_table_lookup (priv->ip4_device_routes.entries,
1036                                      nmp_object_stackinit (&obj_needle, NMP_OBJECT_TYPE_IP4_ROUTE, (NMPlatformObject *) route));
1037         if (!entry)
1038                 return;
1039
1040         if (_ip4_device_routes_entry_expired (entry, nm_utils_get_monotonic_timestamp_ns ())) {
1041                 _LOGt (vtable_v4.vt->addr_family, "device-route: cleanup-ch %s", nmp_object_to_string (entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
1042                 g_hash_table_remove (priv->ip4_device_routes.entries, entry->obj);
1043                 _ip4_device_routes_cancel (self);
1044                 return;
1045         }
1046
1047         if (entry->idle_id == 0) {
1048                 _LOGt (vtable_v4.vt->addr_family, "device-route: schedule %s", nmp_object_to_string (entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
1049                 entry->idle_id = g_idle_add ((GSourceFunc) _ip4_device_routes_idle_cb, entry);
1050         }
1051 }
1052
1053 static gboolean
1054 _ip4_device_routes_cancel (NMRouteManager *self)
1055 {
1056         NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
1057
1058         if (priv->ip4_device_routes.gc_id) {
1059                 if (g_hash_table_size (priv->ip4_device_routes.entries) > 0)
1060                         return G_SOURCE_CONTINUE;
1061                 _LOGt (vtable_v4.vt->addr_family, "device-route: cancel");
1062                 if (priv->platform)
1063                         g_signal_handlers_disconnect_by_func (priv->platform, G_CALLBACK (_ip4_device_routes_ip4_route_changed), self);
1064                 nm_clear_g_source (&priv->ip4_device_routes.gc_id);
1065         }
1066         return G_SOURCE_REMOVE;
1067 }
1068
1069 static gboolean
1070 _ip4_device_routes_gc (NMRouteManager *self)
1071 {
1072         NMRouteManagerPrivate *priv;
1073         GHashTableIter iter;
1074         IP4DeviceRoutePurgeEntry *entry;
1075         gint64 now = nm_utils_get_monotonic_timestamp_ns ();
1076
1077         priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
1078
1079         g_hash_table_iter_init (&iter, priv->ip4_device_routes.entries);
1080         while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &entry)) {
1081                 if (_ip4_device_routes_entry_expired (entry, now)) {
1082                         _LOGt (vtable_v4.vt->addr_family, "device-route: cleanup-gc %s", nmp_object_to_string (entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
1083                         g_hash_table_iter_remove (&iter);
1084                 }
1085         }
1086
1087         return _ip4_device_routes_cancel (self);
1088 }
1089
1090 /**
1091  * nm_route_manager_ip4_route_register_device_route_purge_list:
1092  *
1093  * When adding an IPv4 address, kernel will automatically add a device route with
1094  * metric zero. We don't want that route and want to delete it. However, the route
1095  * by kernel immediately, but some time after. That means during nm_route_manager_ip4_route_sync()
1096  * such a route doesn't exist yet. We must remember that we expect such a route to appear later
1097  * and to remove it. */
1098 void
1099 nm_route_manager_ip4_route_register_device_route_purge_list (NMRouteManager *self, GArray *device_route_purge_list)
1100 {
1101         NMRouteManagerPrivate *priv;
1102         guint i;
1103         gint64 now_ns;
1104
1105         if (!device_route_purge_list || device_route_purge_list->len == 0)
1106                 return;
1107
1108         priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
1109
1110         now_ns = nm_utils_get_monotonic_timestamp_ns ();
1111         for (i = 0; i < device_route_purge_list->len; i++) {
1112                 IP4DeviceRoutePurgeEntry *entry;
1113
1114                 entry = _ip4_device_routes_purge_entry_create (self, &g_array_index (device_route_purge_list, NMPlatformIP4Route, i), now_ns);
1115                 _LOGt (vtable_v4.vt->addr_family, "device-route: watch (%s) %s",
1116                                                   g_hash_table_contains (priv->ip4_device_routes.entries, entry->obj)
1117                                                       ? "update" : "new",
1118                                                   nmp_object_to_string (entry->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
1119                 g_hash_table_replace (priv->ip4_device_routes.entries,
1120                                       nmp_object_ref (entry->obj),
1121                                       entry);
1122         }
1123         if (priv->ip4_device_routes.gc_id == 0) {
1124                 g_signal_connect (priv->platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, G_CALLBACK (_ip4_device_routes_ip4_route_changed), self);
1125                 priv->ip4_device_routes.gc_id = g_timeout_add (IP4_DEVICE_ROUTES_GC_INTERVAL_SEC, (GSourceFunc) _ip4_device_routes_gc, self);
1126         }
1127 }
1128
1129 /*********************************************************************************************/
1130
1131 static const VTableIP vtable_v4 = {
1132         .vt                             = &nm_platform_vtable_route_v4,
1133         .route_dest_cmp                 = (int (*) (const NMPlatformIPXRoute *, const NMPlatformIPXRoute *)) _v4_route_dest_cmp,
1134         .route_id_cmp                   = (int (*) (const NMPlatformIPXRoute *, const NMPlatformIPXRoute *)) _v4_route_id_cmp,
1135 };
1136
1137 static const VTableIP vtable_v6 = {
1138         .vt                             = &nm_platform_vtable_route_v6,
1139         .route_dest_cmp                 = (int (*) (const NMPlatformIPXRoute *, const NMPlatformIPXRoute *)) _v6_route_dest_cmp,
1140         .route_id_cmp                   = (int (*) (const NMPlatformIPXRoute *, const NMPlatformIPXRoute *)) _v6_route_id_cmp,
1141 };
1142
1143 /*********************************************************************************************/
1144
1145 static void
1146 set_property (GObject *object, guint prop_id,
1147               const GValue *value, GParamSpec *pspec)
1148 {
1149         NMRouteManager *self = NM_ROUTE_MANAGER (object);
1150         NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
1151
1152         switch (prop_id) {
1153         case PROP_PLATFORM:
1154                 /* construct-only */
1155                 priv->platform = g_value_get_object (value) ? : NM_PLATFORM_GET;
1156                 if (!priv->platform)
1157                         g_return_if_reached ();
1158                 g_object_ref (priv->platform);
1159                 break;
1160         default:
1161                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1162                 break;
1163         }
1164 }
1165
1166 static void
1167 nm_route_manager_init (NMRouteManager *self)
1168 {
1169         NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (self);
1170
1171         priv->ip4_routes.entries = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP4Route));
1172         priv->ip6_routes.entries = g_array_new (FALSE, FALSE, sizeof (NMPlatformIP6Route));
1173         priv->ip4_routes.effective_metrics = g_array_new (FALSE, FALSE, sizeof (gint64));
1174         priv->ip6_routes.effective_metrics = g_array_new (FALSE, FALSE, sizeof (gint64));
1175         priv->ip4_routes.effective_metrics_reverse = g_array_new (FALSE, FALSE, sizeof (gint64));
1176         priv->ip6_routes.effective_metrics_reverse = g_array_new (FALSE, FALSE, sizeof (gint64));
1177         priv->ip4_routes.index = _route_index_create (&vtable_v4, priv->ip4_routes.entries);
1178         priv->ip6_routes.index = _route_index_create (&vtable_v6, priv->ip6_routes.entries);
1179         priv->ip4_device_routes.entries = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
1180                                                                  (GEqualFunc) nmp_object_id_equal,
1181                                                                  (GDestroyNotify) nmp_object_unref,
1182                                                                  (GDestroyNotify) _ip4_device_routes_purge_entry_free);
1183 }
1184
1185 NMRouteManager *
1186 nm_route_manager_new (NMPlatform *platform)
1187 {
1188         return g_object_new (NM_TYPE_ROUTE_MANAGER,
1189                              NM_ROUTE_MANAGER_PLATFORM, platform,
1190                              NULL);
1191 }
1192
1193 static void
1194 dispose (GObject *object)
1195 {
1196         NMRouteManager *self = NM_ROUTE_MANAGER (object);
1197         NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (object);
1198
1199         g_hash_table_remove_all (priv->ip4_device_routes.entries);
1200         _ip4_device_routes_cancel (self);
1201
1202         G_OBJECT_CLASS (nm_route_manager_parent_class)->dispose (object);
1203 }
1204
1205 static void
1206 finalize (GObject *object)
1207 {
1208         NMRouteManagerPrivate *priv = NM_ROUTE_MANAGER_GET_PRIVATE (object);
1209
1210         g_array_free (priv->ip4_routes.entries, TRUE);
1211         g_array_free (priv->ip6_routes.entries, TRUE);
1212         g_array_free (priv->ip4_routes.effective_metrics, TRUE);
1213         g_array_free (priv->ip6_routes.effective_metrics, TRUE);
1214         g_array_free (priv->ip4_routes.effective_metrics_reverse, TRUE);
1215         g_array_free (priv->ip6_routes.effective_metrics_reverse, TRUE);
1216         g_free (priv->ip4_routes.index);
1217         g_free (priv->ip6_routes.index);
1218
1219         g_hash_table_unref (priv->ip4_device_routes.entries);
1220
1221         g_clear_object (&priv->platform);
1222
1223         G_OBJECT_CLASS (nm_route_manager_parent_class)->finalize (object);
1224 }
1225
1226 static void
1227 nm_route_manager_class_init (NMRouteManagerClass *klass)
1228 {
1229         GObjectClass *object_class = G_OBJECT_CLASS (klass);
1230
1231         g_type_class_add_private (klass, sizeof (NMRouteManagerPrivate));
1232
1233         /* virtual methods */
1234         object_class->set_property = set_property;
1235         object_class->dispose = dispose;
1236         object_class->finalize = finalize;
1237
1238         obj_properties[PROP_PLATFORM] =
1239             g_param_spec_object (NM_ROUTE_MANAGER_PLATFORM, "", "",
1240                                  NM_TYPE_PLATFORM,
1241                                  G_PARAM_WRITABLE |
1242                                  G_PARAM_CONSTRUCT_ONLY |
1243                                  G_PARAM_STATIC_STRINGS);
1244         g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
1245 }