xref: /dragonfly/contrib/dhcpcd/src/route.c (revision c80c9bba1b2fa2824af94c686145cb7eb7db2cd5)
1 /* SPDX-License-Identifier: BSD-2-Clause */
2 /*
3  * dhcpcd - route management
4  * Copyright (c) 2006-2023 Roy Marples <roy@marples.name>
5  * All rights reserved
6 
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <assert.h>
30 #include <ctype.h>
31 #include <errno.h>
32 #include <stdbool.h>
33 #include <stddef.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 
38 #include "config.h"
39 #include "common.h"
40 #include "dhcpcd.h"
41 #include "if.h"
42 #include "if-options.h"
43 #include "ipv4.h"
44 #include "ipv4ll.h"
45 #include "ipv6.h"
46 #include "logerr.h"
47 #include "route.h"
48 #include "sa.h"
49 
50 /* Needed for NetBSD-6, 7 and 8. */
51 #ifndef RB_TREE_FOREACH_SAFE
52 #ifndef RB_TREE_PREV
53 #define RB_TREE_NEXT(T, N) rb_tree_iterate((T), (N), RB_DIR_RIGHT)
54 #define RB_TREE_PREV(T, N) rb_tree_iterate((T), (N), RB_DIR_LEFT)
55 #endif
56 #define RB_TREE_FOREACH_SAFE(N, T, S) \
57     for ((N) = RB_TREE_MIN(T); \
58         (N) && ((S) = RB_TREE_NEXT((T), (N)), 1); \
59         (N) = (S))
60 #define RB_TREE_FOREACH_REVERSE_SAFE(N, T, S) \
61     for ((N) = RB_TREE_MAX(T); \
62         (N) && ((S) = RB_TREE_PREV((T), (N)), 1); \
63         (N) = (S))
64 #endif
65 
66 #ifdef RT_FREE_ROUTE_TABLE_STATS
67 static size_t croutes;
68 static size_t nroutes;
69 static size_t froutes;
70 static size_t mroutes;
71 #endif
72 
73 static void
rt_maskedaddr(struct sockaddr * dst,const struct sockaddr * addr,const struct sockaddr * netmask)74 rt_maskedaddr(struct sockaddr *dst,
75           const struct sockaddr *addr, const struct sockaddr *netmask)
76 {
77           const char *addrp = addr->sa_data, *netmaskp = netmask->sa_data;
78           char *dstp = dst->sa_data;
79           const char *addre = (char *)dst + sa_len(addr);
80           const char *netmaske = (char *)dst + MIN(sa_len(addr), sa_len(netmask));
81 
82           dst->sa_family = addr->sa_family;
83 #ifdef HAVE_SA_LEN
84           dst->sa_len = addr->sa_len;
85 #endif
86 
87           if (sa_is_unspecified(netmask)) {
88                     if (addre > dstp)
89                               memcpy(dstp, addrp, (size_t)(addre - dstp));
90                     return;
91           }
92 
93           while (dstp < netmaske)
94                     *dstp++ = *addrp++ & *netmaskp++;
95           if (dstp < addre)
96                     memset(dstp, 0, (size_t)(addre - dstp));
97 }
98 
99 /*
100  * On some systems, host routes have no need for a netmask.
101  * However DHCP specifies host routes using an all-ones netmask.
102  * This handy function allows easy comparison when the two
103  * differ.
104  */
105 static int
rt_cmp_netmask(const struct rt * rt1,const struct rt * rt2)106 rt_cmp_netmask(const struct rt *rt1, const struct rt *rt2)
107 {
108 
109           if (rt1->rt_flags & RTF_HOST && rt2->rt_flags & RTF_HOST)
110                     return 0;
111           return sa_cmp(&rt1->rt_netmask, &rt2->rt_netmask);
112 }
113 
114 int
rt_cmp_dest(const struct rt * rt1,const struct rt * rt2)115 rt_cmp_dest(const struct rt *rt1, const struct rt *rt2)
116 {
117           union sa_ss ma1 = { .sa.sa_family = AF_UNSPEC };
118           union sa_ss ma2 = { .sa.sa_family = AF_UNSPEC };
119           int c;
120 
121           rt_maskedaddr(&ma1.sa, &rt1->rt_dest, &rt1->rt_netmask);
122           rt_maskedaddr(&ma2.sa, &rt2->rt_dest, &rt2->rt_netmask);
123           c = sa_cmp(&ma1.sa, &ma2.sa);
124           if (c != 0)
125                     return c;
126 
127           return rt_cmp_netmask(rt1, rt2);
128 }
129 
130 static int
rt_compare_os(__unused void * context,const void * node1,const void * node2)131 rt_compare_os(__unused void *context, const void *node1, const void *node2)
132 {
133           const struct rt *rt1 = node1, *rt2 = node2;
134           int c;
135 
136           /* Sort by masked destination. */
137           c = rt_cmp_dest(rt1, rt2);
138           if (c != 0)
139                     return c;
140 
141 #ifdef HAVE_ROUTE_METRIC
142           c = (int)(rt1->rt_ifp->metric - rt2->rt_ifp->metric);
143 #endif
144           return c;
145 }
146 
147 static int
rt_compare_list(__unused void * context,const void * node1,const void * node2)148 rt_compare_list(__unused void *context, const void *node1, const void *node2)
149 {
150           const struct rt *rt1 = node1, *rt2 = node2;
151 
152           if (rt1->rt_order > rt2->rt_order)
153                     return 1;
154           if (rt1->rt_order < rt2->rt_order)
155                     return -1;
156           return 0;
157 }
158 
159 static int
rt_compare_proto(void * context,const void * node1,const void * node2)160 rt_compare_proto(void *context, const void *node1, const void *node2)
161 {
162           const struct rt *rt1 = node1, *rt2 = node2;
163           int c;
164           struct interface *ifp1, *ifp2;
165 
166           assert(rt1->rt_ifp != NULL);
167           assert(rt2->rt_ifp != NULL);
168           ifp1 = rt1->rt_ifp;
169           ifp2 = rt2->rt_ifp;
170 
171           /* Prefer interfaces with a carrier. */
172           c = ifp1->carrier - ifp2->carrier;
173           if (c != 0)
174                     return -c;
175 
176           /* Prefer roaming over non roaming if both carriers are down. */
177           if (ifp1->carrier == LINK_DOWN && ifp2->carrier == LINK_DOWN) {
178                     bool roam1 = if_roaming(ifp1);
179                     bool roam2 = if_roaming(ifp2);
180 
181                     if (roam1 != roam2)
182                               return roam1 ? 1 : -1;
183           }
184 
185 #ifdef INET
186           /* IPv4LL routes always come last */
187           if (rt1->rt_dflags & RTDF_IPV4LL && !(rt2->rt_dflags & RTDF_IPV4LL))
188                     return -1;
189           else if (!(rt1->rt_dflags & RTDF_IPV4LL) && rt2->rt_dflags & RTDF_IPV4LL)
190                     return 1;
191 #endif
192 
193           /* Lower metric interfaces come first. */
194           c = (int)(ifp1->metric - ifp2->metric);
195           if (c != 0)
196                     return c;
197 
198           /* Finally the order in which the route was given to us. */
199           return rt_compare_list(context, rt1, rt2);
200 }
201 
202 static const rb_tree_ops_t rt_compare_os_ops = {
203           .rbto_compare_nodes = rt_compare_os,
204           .rbto_compare_key = rt_compare_os,
205           .rbto_node_offset = offsetof(struct rt, rt_tree),
206           .rbto_context = NULL
207 };
208 
209 const rb_tree_ops_t rt_compare_list_ops = {
210           .rbto_compare_nodes = rt_compare_list,
211           .rbto_compare_key = rt_compare_list,
212           .rbto_node_offset = offsetof(struct rt, rt_tree),
213           .rbto_context = NULL
214 };
215 
216 const rb_tree_ops_t rt_compare_proto_ops = {
217           .rbto_compare_nodes = rt_compare_proto,
218           .rbto_compare_key = rt_compare_proto,
219           .rbto_node_offset = offsetof(struct rt, rt_tree),
220           .rbto_context = NULL
221 };
222 
223 #ifdef RT_FREE_ROUTE_TABLE
224 static int
rt_compare_free(__unused void * context,const void * node1,const void * node2)225 rt_compare_free(__unused void *context, const void *node1, const void *node2)
226 {
227 
228           return node1 == node2 ? 0 : node1 < node2 ? -1 : 1;
229 }
230 
231 static const rb_tree_ops_t rt_compare_free_ops = {
232           .rbto_compare_nodes = rt_compare_free,
233           .rbto_compare_key = rt_compare_free,
234           .rbto_node_offset = offsetof(struct rt, rt_tree),
235           .rbto_context = NULL
236 };
237 #endif
238 
239 void
rt_init(struct dhcpcd_ctx * ctx)240 rt_init(struct dhcpcd_ctx *ctx)
241 {
242 
243           rb_tree_init(&ctx->routes, &rt_compare_os_ops);
244 #ifdef RT_FREE_ROUTE_TABLE
245           rb_tree_init(&ctx->froutes, &rt_compare_free_ops);
246 #endif
247 }
248 
249 bool
rt_is_default(const struct rt * rt)250 rt_is_default(const struct rt *rt)
251 {
252 
253           return sa_is_unspecified(&rt->rt_dest) &&
254               sa_is_unspecified(&rt->rt_netmask);
255 }
256 
257 static void
rt_desc(const char * cmd,const struct rt * rt)258 rt_desc(const char *cmd, const struct rt *rt)
259 {
260           char dest[INET_MAX_ADDRSTRLEN], gateway[INET_MAX_ADDRSTRLEN];
261           int prefix;
262           const char *ifname;
263           bool gateway_unspec;
264 
265           assert(cmd != NULL);
266           assert(rt != NULL);
267 
268           sa_addrtop(&rt->rt_dest, dest, sizeof(dest));
269           prefix = sa_toprefix(&rt->rt_netmask);
270           sa_addrtop(&rt->rt_gateway, gateway, sizeof(gateway));
271           gateway_unspec = sa_is_unspecified(&rt->rt_gateway);
272           ifname = rt->rt_ifp == NULL ? "(null)" : rt->rt_ifp->name;
273 
274           if (rt->rt_flags & RTF_HOST) {
275                     if (gateway_unspec)
276                               loginfox("%s: %s host route to %s",
277                                   ifname, cmd, dest);
278                     else
279                               loginfox("%s: %s host route to %s via %s",
280                                   ifname, cmd, dest, gateway);
281           } else if (rt_is_default(rt)) {
282                     if (gateway_unspec)
283                               loginfox("%s: %s default route",
284                                   ifname, cmd);
285                     else
286                               loginfox("%s: %s default route via %s",
287                                   ifname, cmd, gateway);
288           } else if (gateway_unspec)
289                     loginfox("%s: %s%s route to %s/%d",
290                         ifname, cmd,
291                         rt->rt_flags & RTF_REJECT ? " reject" : "",
292                         dest, prefix);
293           else
294                     loginfox("%s: %s%s route to %s/%d via %s",
295                         ifname, cmd,
296                         rt->rt_flags & RTF_REJECT ? " reject" : "",
297                         dest, prefix, gateway);
298 }
299 
300 void
rt_headclear0(struct dhcpcd_ctx * ctx,rb_tree_t * rts,int af)301 rt_headclear0(struct dhcpcd_ctx *ctx, rb_tree_t *rts, int af)
302 {
303           struct rt *rt, *rtn;
304 
305           if (rts == NULL)
306                     return;
307           assert(ctx != NULL);
308 #ifdef RT_FREE_ROUTE_TABLE
309           assert(&ctx->froutes != rts);
310 #endif
311 
312           RB_TREE_FOREACH_SAFE(rt, rts, rtn) {
313                     if (af != AF_UNSPEC &&
314                         rt->rt_dest.sa_family != af &&
315                         rt->rt_gateway.sa_family != af)
316                               continue;
317                     rb_tree_remove_node(rts, rt);
318                     rt_free(rt);
319           }
320 }
321 
322 void
rt_headclear(rb_tree_t * rts,int af)323 rt_headclear(rb_tree_t *rts, int af)
324 {
325           struct rt *rt;
326 
327           if (rts == NULL || (rt = RB_TREE_MIN(rts)) == NULL)
328                     return;
329           rt_headclear0(rt->rt_ifp->ctx, rts, af);
330 }
331 
332 static void
rt_headfree(rb_tree_t * rts)333 rt_headfree(rb_tree_t *rts)
334 {
335           struct rt *rt;
336 
337           while ((rt = RB_TREE_MIN(rts)) != NULL) {
338                     rb_tree_remove_node(rts, rt);
339                     free(rt);
340           }
341 }
342 
343 void
rt_dispose(struct dhcpcd_ctx * ctx)344 rt_dispose(struct dhcpcd_ctx *ctx)
345 {
346 
347           assert(ctx != NULL);
348           rt_headfree(&ctx->routes);
349 #ifdef RT_FREE_ROUTE_TABLE
350           rt_headfree(&ctx->froutes);
351 #ifdef RT_FREE_ROUTE_TABLE_STATS
352           logdebugx("free route list used %zu times", froutes);
353           logdebugx("new routes from route free list %zu", nroutes);
354           logdebugx("maximum route free list size %zu", mroutes);
355 #endif
356 #endif
357 }
358 
359 struct rt *
rt_new0(struct dhcpcd_ctx * ctx)360 rt_new0(struct dhcpcd_ctx *ctx)
361 {
362           struct rt *rt;
363 
364           assert(ctx != NULL);
365 #ifdef RT_FREE_ROUTE_TABLE
366           if ((rt = RB_TREE_MIN(&ctx->froutes)) != NULL) {
367                     rb_tree_remove_node(&ctx->froutes, rt);
368 #ifdef RT_FREE_ROUTE_TABLE_STATS
369                     croutes--;
370                     nroutes++;
371 #endif
372           } else
373 #endif
374           if ((rt = malloc(sizeof(*rt))) == NULL) {
375                     logerr(__func__);
376                     return NULL;
377           }
378           memset(rt, 0, sizeof(*rt));
379           return rt;
380 }
381 
382 void
rt_setif(struct rt * rt,struct interface * ifp)383 rt_setif(struct rt *rt, struct interface *ifp)
384 {
385 
386           assert(rt != NULL);
387           assert(ifp != NULL);
388           rt->rt_ifp = ifp;
389 #ifdef HAVE_ROUTE_METRIC
390           rt->rt_metric = ifp->metric;
391           if (if_roaming(ifp))
392                     rt->rt_metric += RTMETRIC_ROAM;
393 #endif
394 }
395 
396 struct rt *
rt_new(struct interface * ifp)397 rt_new(struct interface *ifp)
398 {
399           struct rt *rt;
400 
401           assert(ifp != NULL);
402           if ((rt = rt_new0(ifp->ctx)) == NULL)
403                     return NULL;
404           rt_setif(rt, ifp);
405           return rt;
406 }
407 
408 struct rt *
rt_proto_add_ctx(rb_tree_t * tree,struct rt * rt,struct dhcpcd_ctx * ctx)409 rt_proto_add_ctx(rb_tree_t *tree, struct rt *rt, struct dhcpcd_ctx *ctx)
410 {
411 
412           rt->rt_order = ctx->rt_order++;
413           if (rb_tree_insert_node(tree, rt) == rt)
414                     return rt;
415 
416           rt_free(rt);
417           errno = EEXIST;
418           return NULL;
419 }
420 
421 struct rt *
rt_proto_add(rb_tree_t * tree,struct rt * rt)422 rt_proto_add(rb_tree_t *tree, struct rt *rt)
423 {
424 
425           assert (rt->rt_ifp != NULL);
426           return rt_proto_add_ctx(tree, rt, rt->rt_ifp->ctx);
427 }
428 
429 void
rt_free(struct rt * rt)430 rt_free(struct rt *rt)
431 {
432 #ifdef RT_FREE_ROUTE_TABLE
433           struct dhcpcd_ctx *ctx;
434 
435           assert(rt != NULL);
436           if (rt->rt_ifp == NULL) {
437                     free(rt);
438                     return;
439           }
440 
441           ctx = rt->rt_ifp->ctx;
442           rb_tree_insert_node(&ctx->froutes, rt);
443 #ifdef RT_FREE_ROUTE_TABLE_STATS
444           croutes++;
445           froutes++;
446           if (croutes > mroutes)
447                     mroutes = croutes;
448 #endif
449 #else
450           free(rt);
451 #endif
452 }
453 
454 void
rt_freeif(struct interface * ifp)455 rt_freeif(struct interface *ifp)
456 {
457           struct dhcpcd_ctx *ctx;
458           struct rt *rt, *rtn;
459 
460           if (ifp == NULL)
461                     return;
462           ctx = ifp->ctx;
463           RB_TREE_FOREACH_SAFE(rt, &ctx->routes, rtn) {
464                     if (rt->rt_ifp == ifp) {
465                               rb_tree_remove_node(&ctx->routes, rt);
466                               rt_free(rt);
467                     }
468           }
469 }
470 
471 /* If something other than dhcpcd removes a route,
472  * we need to remove it from our internal table. */
473 void
rt_recvrt(int cmd,const struct rt * rt,pid_t pid)474 rt_recvrt(int cmd, const struct rt *rt, pid_t pid)
475 {
476           struct dhcpcd_ctx *ctx;
477           struct rt *f;
478 
479           assert(rt != NULL);
480           assert(rt->rt_ifp != NULL);
481           assert(rt->rt_ifp->ctx != NULL);
482 
483           ctx = rt->rt_ifp->ctx;
484 
485           switch(cmd) {
486           case RTM_DELETE:
487                     f = rb_tree_find_node(&ctx->routes, rt);
488                     if (f != NULL) {
489                               char buf[32];
490 
491                               rb_tree_remove_node(&ctx->routes, f);
492                               snprintf(buf, sizeof(buf), "pid %d deleted", pid);
493                               rt_desc(buf, f);
494                               rt_free(f);
495                     }
496                     break;
497           }
498 
499 #if defined(IPV4LL) && defined(HAVE_ROUTE_METRIC)
500           if (rt->rt_dest.sa_family == AF_INET)
501                     ipv4ll_recvrt(cmd, rt);
502 #endif
503 }
504 
505 static bool
rt_add(rb_tree_t * kroutes,struct rt * nrt,struct rt * ort)506 rt_add(rb_tree_t *kroutes, struct rt *nrt, struct rt *ort)
507 {
508           struct dhcpcd_ctx *ctx;
509           bool change, kroute, result;
510 
511           assert(nrt != NULL);
512           ctx = nrt->rt_ifp->ctx;
513 
514           /*
515            * Don't install a gateway if not asked to.
516            * This option is mainly for VPN users who want their VPN to be the
517            * default route.
518            * Because VPN's generally don't care about route management
519            * beyond their own, a longer term solution would be to remove this
520            * and get the VPN to inject the default route into dhcpcd somehow.
521            */
522           if (((nrt->rt_ifp->active &&
523               !(nrt->rt_ifp->options->options & DHCPCD_GATEWAY)) ||
524               (!nrt->rt_ifp->active && !(ctx->options & DHCPCD_GATEWAY))) &&
525               sa_is_unspecified(&nrt->rt_dest) &&
526               sa_is_unspecified(&nrt->rt_netmask))
527                     return false;
528 
529           rt_desc(ort == NULL ? "adding" : "changing", nrt);
530 
531           change = kroute = result = false;
532           if (ort == NULL) {
533                     ort = rb_tree_find_node(kroutes, nrt);
534                     if (ort != NULL &&
535                         ((ort->rt_flags & RTF_REJECT &&
536                           nrt->rt_flags & RTF_REJECT) ||
537                          (ort->rt_ifp == nrt->rt_ifp &&
538 #ifdef HAVE_ROUTE_METRIC
539                         ort->rt_metric == nrt->rt_metric &&
540 #endif
541                         sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)))
542                     {
543                               if (ort->rt_mtu == nrt->rt_mtu)
544                                         return true;
545                               change = true;
546                               kroute = true;
547                     }
548           } else if (ort->rt_dflags & RTDF_FAKE &&
549               !(nrt->rt_dflags & RTDF_FAKE) &&
550               ort->rt_ifp == nrt->rt_ifp &&
551 #ifdef HAVE_ROUTE_METRIC
552               ort->rt_metric == nrt->rt_metric &&
553 #endif
554               sa_cmp(&ort->rt_dest, &nrt->rt_dest) == 0 &&
555               rt_cmp_netmask(ort, nrt) == 0 &&
556               sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)
557           {
558                     if (ort->rt_mtu == nrt->rt_mtu)
559                               return true;
560                     change = true;
561           }
562 
563 #ifdef RTF_CLONING
564           /* BSD can set routes to be cloning routes.
565            * Cloned routes inherit the parent flags.
566            * As such, we need to delete and re-add the route to flush children
567            * to correct the flags. */
568           if (change && ort != NULL && ort->rt_flags & RTF_CLONING)
569                     change = false;
570 #endif
571 
572           if (change) {
573                     if (if_route(RTM_CHANGE, nrt) != -1) {
574                               result = true;
575                               goto out;
576                     }
577                     if (errno != ESRCH)
578                               logerr("if_route (CHG)");
579           }
580 
581 #ifdef HAVE_ROUTE_METRIC
582           /* With route metrics, we can safely add the new route before
583            * deleting the old route. */
584           if (if_route(RTM_ADD, nrt) != -1) {
585                     if (ort != NULL) {
586                               if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
587                                         logerr("if_route (DEL)");
588                     }
589                     result = true;
590                     goto out;
591           }
592 
593           /* If the kernel claims the route exists we need to rip out the
594            * old one first. */
595           if (errno != EEXIST || ort == NULL)
596                     goto logerr;
597 #endif
598 
599           /* No route metrics, we need to delete the old route before
600            * adding the new one. */
601 #ifdef ROUTE_PER_GATEWAY
602           errno = 0;
603 #endif
604           if (ort != NULL) {
605                     if (if_route(RTM_DELETE, ort) == -1 && errno != ESRCH)
606                               logerr("if_route (DEL)");
607                     else
608                               kroute = false;
609           }
610 #ifdef ROUTE_PER_GATEWAY
611           /* The OS allows many routes to the same dest with different gateways.
612            * dhcpcd does not support this yet, so for the time being just keep on
613            * deleting the route until there is an error. */
614           if (ort != NULL && errno == 0) {
615                     for (;;) {
616                               if (if_route(RTM_DELETE, ort) == -1)
617                                         break;
618                     }
619           }
620 #endif
621 
622           /* Shouldn't need to check for EEXIST, but some kernels don't
623            * dump the subnet route just after we added the address. */
624           if (if_route(RTM_ADD, nrt) != -1 || errno == EEXIST) {
625                     result = true;
626                     goto out;
627           }
628 
629 #ifdef HAVE_ROUTE_METRIC
630 logerr:
631 #endif
632           logerr("if_route (ADD)");
633 
634 out:
635           if (kroute) {
636                     rb_tree_remove_node(kroutes, ort);
637                     rt_free(ort);
638           }
639           return result;
640 }
641 
642 static bool
rt_delete(struct rt * rt)643 rt_delete(struct rt *rt)
644 {
645           int retval;
646 
647           rt_desc("deleting", rt);
648           retval = if_route(RTM_DELETE, rt) == -1 ? false : true;
649           if (!retval && errno != ENOENT && errno != ESRCH)
650                     logerr(__func__);
651           return retval;
652 }
653 
654 static bool
rt_cmp(const struct rt * r1,const struct rt * r2)655 rt_cmp(const struct rt *r1, const struct rt *r2)
656 {
657 
658           return (r1->rt_ifp == r2->rt_ifp &&
659 #ifdef HAVE_ROUTE_METRIC
660               r1->rt_metric == r2->rt_metric &&
661 #endif
662               sa_cmp(&r1->rt_gateway, &r2->rt_gateway) == 0);
663 }
664 
665 static bool
rt_doroute(rb_tree_t * kroutes,struct rt * rt)666 rt_doroute(rb_tree_t *kroutes, struct rt *rt)
667 {
668           struct dhcpcd_ctx *ctx;
669           struct rt *or;
670 
671           ctx = rt->rt_ifp->ctx;
672           /* Do we already manage it? */
673           or = rb_tree_find_node(&ctx->routes, rt);
674           if (or != NULL) {
675                     if (rt->rt_dflags & RTDF_FAKE)
676                               return true;
677                     if (or->rt_dflags & RTDF_FAKE ||
678                         !rt_cmp(rt, or) ||
679                         (rt->rt_ifa.sa_family != AF_UNSPEC &&
680                         sa_cmp(&or->rt_ifa, &rt->rt_ifa) != 0) ||
681                         or->rt_mtu != rt->rt_mtu)
682                     {
683                               if (!rt_add(kroutes, rt, or))
684                                         return false;
685                     }
686                     rb_tree_remove_node(&ctx->routes, or);
687                     rt_free(or);
688           } else {
689                     if (rt->rt_dflags & RTDF_FAKE) {
690                               or = rb_tree_find_node(kroutes, rt);
691                               if (or == NULL)
692                                         return false;
693                               if (!rt_cmp(rt, or))
694                                         return false;
695                     } else {
696                               if (!rt_add(kroutes, rt, NULL))
697                                         return false;
698                     }
699           }
700 
701           return true;
702 }
703 
704 void
rt_build(struct dhcpcd_ctx * ctx,int af)705 rt_build(struct dhcpcd_ctx *ctx, int af)
706 {
707           rb_tree_t routes, added, kroutes;
708           struct rt *rt, *rtn;
709           unsigned long long o;
710 
711           rb_tree_init(&routes, &rt_compare_proto_ops);
712           rb_tree_init(&added, &rt_compare_os_ops);
713           rb_tree_init(&kroutes, &rt_compare_os_ops);
714           if (if_initrt(ctx, &kroutes, af) != 0)
715                     logerr("%s: if_initrt", __func__);
716           ctx->rt_order = 0;
717           ctx->options |= DHCPCD_RTBUILD;
718 
719 #ifdef INET
720           if (!inet_getroutes(ctx, &routes))
721                     goto getfail;
722 #endif
723 #ifdef INET6
724           if (!inet6_getroutes(ctx, &routes))
725                     goto getfail;
726 #endif
727 
728 #ifdef BSD
729           /* Rewind the miss filter */
730           ctx->rt_missfilterlen = 0;
731 #endif
732 
733           RB_TREE_FOREACH_SAFE(rt, &routes, rtn) {
734                     if (rt->rt_ifp->active) {
735                               if (!(rt->rt_ifp->options->options & DHCPCD_CONFIGURE))
736                                         continue;
737                     } else if (!(ctx->options & DHCPCD_CONFIGURE))
738                               continue;
739 #ifdef BSD
740                     if (rt_is_default(rt) &&
741                         if_missfilter(rt->rt_ifp, &rt->rt_gateway) == -1)
742                               logerr("if_missfilter");
743 #endif
744                     if ((rt->rt_dest.sa_family != af &&
745                         rt->rt_dest.sa_family != AF_UNSPEC) ||
746                         (rt->rt_gateway.sa_family != af &&
747                         rt->rt_gateway.sa_family != AF_UNSPEC))
748                               continue;
749                     /* Is this route already in our table? */
750                     if (rb_tree_find_node(&added, rt) != NULL)
751                               continue;
752                     if (rt_doroute(&kroutes, rt)) {
753                               rb_tree_remove_node(&routes, rt);
754                               if (rb_tree_insert_node(&added, rt) != rt) {
755                                         errno = EEXIST;
756                                         logerr(__func__);
757                                         rt_free(rt);
758                               }
759                     }
760           }
761 
762 #ifdef BSD
763           if (if_missfilter_apply(ctx) == -1 && errno != ENOTSUP)
764                     logerr("if_missfilter_apply");
765 #endif
766 
767           /* Remove old routes we used to manage. */
768           RB_TREE_FOREACH_REVERSE_SAFE(rt, &ctx->routes, rtn) {
769                     if ((rt->rt_dest.sa_family != af &&
770                         rt->rt_dest.sa_family != AF_UNSPEC) ||
771                         (rt->rt_gateway.sa_family != af &&
772                         rt->rt_gateway.sa_family != AF_UNSPEC))
773                               continue;
774                     rb_tree_remove_node(&ctx->routes, rt);
775                     if (rb_tree_find_node(&added, rt) == NULL) {
776                               o = rt->rt_ifp->options ?
777                                   rt->rt_ifp->options->options :
778                                   ctx->options;
779                               if ((o &
780                                         (DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
781                                         (DHCPCD_EXITING | DHCPCD_PERSISTENT))
782                                         rt_delete(rt);
783                     }
784                     rt_free(rt);
785           }
786 
787           /* XXX This needs to be optimised. */
788           while ((rt = RB_TREE_MIN(&added)) != NULL) {
789                     rb_tree_remove_node(&added, rt);
790                     if (rb_tree_insert_node(&ctx->routes, rt) != rt) {
791                               errno = EEXIST;
792                               logerr(__func__);
793                               rt_free(rt);
794                     }
795           }
796 
797 getfail:
798           rt_headclear(&routes, AF_UNSPEC);
799           rt_headclear(&kroutes, AF_UNSPEC);
800 }
801