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