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