1 /* $NetBSD: ifwatchd.c,v 1.47 2023/07/01 12:36:10 mlelstv Exp $ */ 2 #include <sys/cdefs.h> 3 __RCSID("$NetBSD: ifwatchd.c,v 1.47 2023/07/01 12:36:10 mlelstv Exp $"); 4 5 /*- 6 * Copyright (c) 2002, 2003 The NetBSD Foundation, Inc. 7 * All rights reserved. 8 * 9 * This code is derived from software contributed to The NetBSD Foundation 10 * by Martin Husemann <martin (at) NetBSD.org>. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 23 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 * POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include <sys/types.h> 35 #include <sys/param.h> 36 #include <sys/ioctl.h> 37 #include <sys/socket.h> 38 #include <sys/queue.h> 39 #include <sys/wait.h> 40 #include <net/if.h> 41 #include <net/if_dl.h> 42 #include <net/route.h> 43 #include <netinet/in.h> 44 #include <netinet/in_var.h> 45 #include <arpa/inet.h> 46 47 #include <err.h> 48 #include <errno.h> 49 #include <ifaddrs.h> 50 #include <netdb.h> 51 #include <paths.h> 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <string.h> 55 #include <syslog.h> 56 #include <unistd.h> 57 58 enum event { ARRIVAL, DEPARTURE, UP, DOWN, CARRIER, NO_CARRIER }; 59 enum addrflag { NOTREADY, DETACHED, DEPRECATED, READY }; 60 61 /* local functions */ 62 __dead static void usage(void); 63 static void dispatch(const void *, size_t); 64 static enum addrflag check_addrflags(int af, int addrflags); 65 static void check_addrs(const struct ifa_msghdr *ifam); 66 static void invoke_script(const char *ifname, enum event ev, 67 const struct sockaddr *sa, const struct sockaddr *dst); 68 static void list_interfaces(const char *ifnames); 69 static void check_announce(const struct if_announcemsghdr *ifan); 70 static void check_carrier(const struct if_msghdr *ifm); 71 static void free_interfaces(void); 72 static struct interface_data * find_interface(int index); 73 static void run_initial_ups(bool); 74 75 /* global variables */ 76 static int verbose = 0, quiet = 0; 77 static int inhibit_initial = 0; 78 static const char *arrival_script = NULL; 79 static const char *departure_script = NULL; 80 static const char *up_script = NULL; 81 static const char *down_script = NULL; 82 static const char *carrier_script = NULL; 83 static const char *no_carrier_script = NULL; 84 static const char DummyTTY[] = _PATH_DEVNULL; 85 static const char DummySpeed[] = "9600"; 86 static const char **scripts[] = { 87 &arrival_script, 88 &departure_script, 89 &up_script, 90 &down_script, 91 &carrier_script, 92 &no_carrier_script 93 }; 94 95 struct interface_data { 96 SLIST_ENTRY(interface_data) next; 97 int index; 98 int last_carrier_status; 99 char * ifname; 100 }; 101 static SLIST_HEAD(,interface_data) ifs = SLIST_HEAD_INITIALIZER(ifs); 102 103 int 104 main(int argc, char **argv) 105 { 106 int c, s, n; 107 int errs = 0; 108 struct msghdr msg; 109 struct iovec iov[1]; 110 char buf[2048]; 111 unsigned char msgfilter[] = { 112 RTM_IFINFO, RTM_IFANNOUNCE, 113 RTM_NEWADDR, RTM_DELADDR, 114 }; 115 116 openlog(argv[0], LOG_PID|LOG_CONS, LOG_DAEMON); 117 while ((c = getopt(argc, argv, "qvhic:n:u:d:A:D:")) != -1) { 118 switch (c) { 119 case 'h': 120 usage(); 121 return 0; 122 123 case 'i': 124 inhibit_initial = 1; 125 break; 126 127 case 'v': 128 verbose++; 129 break; 130 131 case 'q': 132 quiet = 1; 133 break; 134 135 case 'c': 136 carrier_script = optarg; 137 break; 138 139 case 'n': 140 no_carrier_script = optarg; 141 break; 142 143 case 'u': 144 up_script = optarg; 145 break; 146 147 case 'd': 148 down_script = optarg; 149 break; 150 151 case 'A': 152 arrival_script = optarg; 153 break; 154 155 case 'D': 156 departure_script = optarg; 157 break; 158 159 default: 160 errs++; 161 break; 162 } 163 } 164 165 if (errs) 166 usage(); 167 168 argv += optind; 169 argc -= optind; 170 171 if (argc <= 0) 172 usage(); 173 174 if (verbose) { 175 printf("up_script: %s\ndown_script: %s\n", 176 up_script, down_script); 177 printf("arrival_script: %s\ndeparture_script: %s\n", 178 arrival_script, departure_script); 179 printf("carrier_script: %s\nno_carrier_script: %s\n", 180 carrier_script, no_carrier_script); 181 printf("verbosity = %d\n", verbose); 182 } 183 184 while (argc > 0) { 185 list_interfaces(argv[0]); 186 argv++; 187 argc--; 188 } 189 190 if (!verbose) 191 daemon(0, 0); 192 193 s = socket(PF_ROUTE, SOCK_RAW, 0); 194 if (s < 0) { 195 syslog(LOG_ERR, "error opening routing socket: %m"); 196 exit(EXIT_FAILURE); 197 } 198 if (setsockopt(s, PF_ROUTE, RO_MSGFILTER, 199 &msgfilter, sizeof(msgfilter)) < 0) 200 syslog(LOG_ERR, "RO_MSGFILTER: %m"); 201 n = 1; 202 if (setsockopt(s, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) < 0) 203 syslog(LOG_ERR, "SO_RERROR: %m"); 204 205 if (!inhibit_initial) 206 run_initial_ups(true); 207 208 iov[0].iov_base = buf; 209 iov[0].iov_len = sizeof(buf); 210 memset(&msg, 0, sizeof(msg)); 211 msg.msg_iov = iov; 212 msg.msg_iovlen = 1; 213 214 for (;;) { 215 n = recvmsg(s, &msg, 0); 216 if (n == -1) { 217 if (errno == ENOBUFS) { 218 syslog(LOG_ERR, 219 "routing socket overflow detected"); 220 /* XXX We don't track addresses, so they 221 * won't be reported. */ 222 if (!inhibit_initial) 223 run_initial_ups(false); 224 continue; 225 } 226 syslog(LOG_ERR, "recvmsg: %m"); 227 exit(EXIT_FAILURE); 228 } 229 if (n != 0) 230 dispatch(iov[0].iov_base, n); 231 } 232 233 close(s); 234 free_interfaces(); 235 closelog(); 236 237 return EXIT_SUCCESS; 238 } 239 240 static void 241 usage(void) 242 { 243 fprintf(stderr, 244 "usage:\n" 245 "\tifwatchd [-hiqv] [-A arrival-script] [-D departure-script]\n" 246 "\t\t [-d down-script] [-u up-script]\n" 247 "\t\t [-c carrier-script] [-n no-carrier-script] ifname(s)\n" 248 "\twhere:\n" 249 "\t -A <cmd> specify command to run on interface arrival event\n" 250 "\t -c <cmd> specify command to run on interface carrier-detect event\n" 251 "\t -D <cmd> specify command to run on interface departure event\n" 252 "\t -d <cmd> specify command to run on interface down event\n" 253 "\t -n <cmd> specify command to run on interface no-carrier-detect event\n" 254 "\t -h show this help message\n" 255 "\t -i no (!) initial run of the up script if the interface\n" 256 "\t is already up on ifwatchd startup\n" 257 "\t -q quiet mode, don't syslog informational messages\n" 258 "\t -u <cmd> specify command to run on interface up event\n" 259 "\t -v verbose/debug output, don't run in background\n"); 260 exit(EXIT_FAILURE); 261 } 262 263 static void 264 dispatch(const void *msg, size_t len) 265 { 266 const struct rt_msghdr *hd = msg; 267 268 if (hd->rtm_version != RTM_VERSION) 269 return; 270 271 switch (hd->rtm_type) { 272 case RTM_NEWADDR: 273 case RTM_DELADDR: 274 check_addrs(msg); 275 break; 276 case RTM_IFANNOUNCE: 277 check_announce(msg); 278 break; 279 case RTM_IFINFO: 280 check_carrier(msg); 281 break; 282 default: 283 /* Should be impossible as we filter messages. */ 284 if (verbose) 285 printf("unknown message ignored (%d)\n", hd->rtm_type); 286 break; 287 } 288 } 289 290 static enum addrflag 291 check_addrflags(int af, int addrflags) 292 { 293 294 switch (af) { 295 case AF_INET: 296 if (addrflags & IN_IFF_NOTREADY) 297 return NOTREADY; 298 if (addrflags & IN_IFF_DETACHED) 299 return DETACHED; 300 break; 301 case AF_INET6: 302 if (addrflags & IN6_IFF_NOTREADY) 303 return NOTREADY; 304 if (addrflags & IN6_IFF_DETACHED) 305 return DETACHED; 306 if (addrflags & IN6_IFF_DEPRECATED) 307 return DEPRECATED; 308 break; 309 } 310 return READY; 311 } 312 313 static void 314 check_addrs(const struct ifa_msghdr *ifam) 315 { 316 const char *cp = (const char *)(ifam + 1); 317 const struct sockaddr *sa, *ifa = NULL, *brd = NULL; 318 unsigned i; 319 struct interface_data *ifd = NULL; 320 int aflag; 321 enum event ev; 322 323 if (ifam->ifam_addrs == 0) 324 return; 325 for (i = 1; i; i <<= 1) { 326 if ((i & ifam->ifam_addrs) == 0) 327 continue; 328 sa = (const struct sockaddr *)cp; 329 if (i == RTA_IFP) { 330 const struct sockaddr_dl *li; 331 332 li = (const struct sockaddr_dl *)sa; 333 if ((ifd = find_interface(li->sdl_index)) == NULL) { 334 if (verbose) 335 printf("ignoring change" 336 " on interface #%d\n", 337 li->sdl_index); 338 return; 339 } 340 } else if (i == RTA_IFA) 341 ifa = sa; 342 else if (i == RTA_BRD) 343 brd = sa; 344 RT_ADVANCE(cp, sa); 345 } 346 if (ifa != NULL && ifd != NULL) { 347 ev = ifam->ifam_type == RTM_DELADDR ? DOWN : UP; 348 aflag = check_addrflags(ifa->sa_family, ifam->ifam_addrflags); 349 if ((ev == UP && aflag == READY) || ev == DOWN) 350 invoke_script(ifd->ifname, ev, ifa, brd); 351 } 352 } 353 354 static void 355 invoke_script(const char *ifname, enum event ev, 356 const struct sockaddr *sa, const struct sockaddr *dest) 357 { 358 char addr[NI_MAXHOST], daddr[NI_MAXHOST]; 359 const char *script; 360 int status; 361 362 if (ifname == NULL) 363 return; 364 365 script = *scripts[ev]; 366 if (script == NULL) 367 return; 368 369 addr[0] = daddr[0] = 0; 370 if (sa != NULL) { 371 const struct sockaddr_in *sin; 372 const struct sockaddr_in6 *sin6; 373 374 if (sa->sa_len == 0) { 375 syslog(LOG_ERR, 376 "illegal socket address (sa_len == 0)"); 377 return; 378 } 379 switch (sa->sa_family) { 380 case AF_INET: 381 sin = (const struct sockaddr_in *)sa; 382 if (sin->sin_addr.s_addr == INADDR_ANY || 383 sin->sin_addr.s_addr == INADDR_BROADCAST) 384 return; 385 break; 386 case AF_INET6: 387 sin6 = (const struct sockaddr_in6 *)sa; 388 if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) 389 return; 390 break; 391 default: 392 break; 393 } 394 395 if (getnameinfo(sa, sa->sa_len, addr, sizeof addr, NULL, 0, 396 NI_NUMERICHOST)) { 397 if (verbose) 398 printf("getnameinfo failed\n"); 399 return; /* this address can not be handled */ 400 } 401 } 402 403 if (dest != NULL) { 404 if (getnameinfo(dest, dest->sa_len, daddr, sizeof daddr, 405 NULL, 0, NI_NUMERICHOST)) { 406 if (verbose) 407 printf("getnameinfo failed\n"); 408 return; /* this address can not be handled */ 409 } 410 } 411 412 if (verbose) 413 (void) printf("calling: %s %s %s %s %s %s\n", 414 script, ifname, DummyTTY, DummySpeed, addr, daddr); 415 if (!quiet) 416 syslog(LOG_INFO, "calling: %s %s %s %s %s %s\n", 417 script, ifname, DummyTTY, DummySpeed, addr, daddr); 418 419 switch (vfork()) { 420 case -1: 421 syslog(LOG_ERR, "cannot fork: %m"); 422 break; 423 case 0: 424 if (execl(script, script, ifname, DummyTTY, DummySpeed, 425 addr, daddr, NULL) == -1) { 426 syslog(LOG_ERR, "could not execute \"%s\": %m", 427 script); 428 } 429 _exit(EXIT_FAILURE); 430 default: 431 (void) wait(&status); 432 } 433 } 434 435 static void 436 list_interfaces(const char *ifnames) 437 { 438 char * names = strdup(ifnames); 439 char * name, *lasts; 440 static const char sep[] = " \t"; 441 struct interface_data * p; 442 443 for (name = strtok_r(names, sep, &lasts); 444 name != NULL; 445 name = strtok_r(NULL, sep, &lasts)) { 446 p = malloc(sizeof(*p)); 447 SLIST_INSERT_HEAD(&ifs, p, next); 448 p->last_carrier_status = -1; 449 p->ifname = strdup(name); 450 p->index = if_nametoindex(p->ifname); 451 if (!quiet) 452 syslog(LOG_INFO, "watching interface %s", p->ifname); 453 if (verbose) 454 printf("interface \"%s\" has index %d\n", 455 p->ifname, p->index); 456 } 457 free(names); 458 } 459 460 static void 461 check_carrier(const struct if_msghdr *ifm) 462 { 463 struct interface_data * p; 464 int carrier_status; 465 enum event ev; 466 467 SLIST_FOREACH(p, &ifs, next) 468 if (p->index == ifm->ifm_index) 469 break; 470 471 if (p == NULL) 472 return; 473 474 /* 475 * Treat it as an event worth handling if: 476 * - the carrier status changed, or 477 * - this is the first time we've been called, and 478 * inhibit_initial is not set 479 */ 480 carrier_status = ifm->ifm_data.ifi_link_state; 481 if (carrier_status != p->last_carrier_status) { 482 switch (carrier_status) { 483 case LINK_STATE_UP: 484 ev = CARRIER; 485 break; 486 case LINK_STATE_DOWN: 487 ev = NO_CARRIER; 488 break; 489 default: 490 if (verbose) 491 printf("unknown link status ignored\n"); 492 return; 493 } 494 invoke_script(p->ifname, ev, NULL, NULL); 495 p->last_carrier_status = carrier_status; 496 } 497 } 498 499 static void 500 do_announce(struct interface_data *ifd, 501 unsigned short what, unsigned short index) 502 { 503 504 switch (what) { 505 case IFAN_ARRIVAL: 506 ifd->index = index; 507 invoke_script(ifd->ifname, ARRIVAL, NULL, NULL); 508 break; 509 case IFAN_DEPARTURE: 510 ifd->index = -1; 511 ifd->last_carrier_status = -1; 512 invoke_script(ifd->ifname, DEPARTURE, NULL, NULL); 513 break; 514 default: 515 if (verbose) 516 (void) printf("unknown announce: what=%d\n", what); 517 break; 518 } 519 } 520 521 static void 522 check_announce(const struct if_announcemsghdr *ifan) 523 { 524 struct interface_data * p; 525 const char *ifname = ifan->ifan_name; 526 527 SLIST_FOREACH(p, &ifs, next) { 528 if (strcmp(p->ifname, ifname) != 0) 529 continue; 530 531 do_announce(p, ifan->ifan_what, ifan->ifan_index); 532 return; 533 } 534 } 535 536 static void 537 free_interfaces(void) 538 { 539 struct interface_data * p; 540 541 while (!SLIST_EMPTY(&ifs)) { 542 p = SLIST_FIRST(&ifs); 543 SLIST_REMOVE_HEAD(&ifs, next); 544 free(p->ifname); 545 free(p); 546 } 547 } 548 549 static struct interface_data * 550 find_interface(int idx) 551 { 552 struct interface_data * p; 553 554 SLIST_FOREACH(p, &ifs, next) 555 if (p->index == idx) 556 return p; 557 return NULL; 558 } 559 560 static void 561 run_initial_ups(bool do_addrs) 562 { 563 struct interface_data * ifd; 564 struct ifaddrs *res = NULL, *p; 565 struct sockaddr *ifa; 566 const struct if_data *ifi; 567 int s, aflag; 568 569 s = socket(AF_INET, SOCK_DGRAM, 0); 570 if (s < 0) 571 return; 572 573 if (getifaddrs(&res) != 0) 574 goto out; 575 576 /* Check if any interfaces vanished */ 577 SLIST_FOREACH(ifd, &ifs, next) { 578 for (p = res; p; p = p->ifa_next) { 579 if (strcmp(ifd->ifname, p->ifa_name) != 0) 580 continue; 581 ifa = p->ifa_addr; 582 if (ifa != NULL && ifa->sa_family == AF_LINK) 583 break; 584 } 585 if (p == NULL) 586 do_announce(ifd, IFAN_DEPARTURE, ifd->index); 587 } 588 589 for (p = res; p; p = p->ifa_next) { 590 SLIST_FOREACH(ifd, &ifs, next) { 591 if (strcmp(ifd->ifname, p->ifa_name) == 0) 592 break; 593 } 594 if (ifd == NULL) 595 continue; 596 597 ifa = p->ifa_addr; 598 if (ifa != NULL && ifa->sa_family == AF_LINK && 599 ifd->index == -1) 600 invoke_script(ifd->ifname, ARRIVAL, NULL, NULL); 601 602 if ((p->ifa_flags & IFF_UP) == 0) 603 continue; 604 if (ifa == NULL) 605 continue; 606 if (ifa->sa_family == AF_LINK) { 607 ifi = (const struct if_data *)p->ifa_data; 608 if (ifd->last_carrier_status == ifi->ifi_link_state) 609 continue; 610 switch (ifi->ifi_link_state) { 611 case LINK_STATE_UP: 612 invoke_script(ifd->ifname, CARRIER, NULL, NULL); 613 break; 614 case LINK_STATE_DOWN: 615 if (ifd->last_carrier_status == -1) 616 break; 617 invoke_script(ifd->ifname, CARRIER, NULL, NULL); 618 break; 619 } 620 ifd->last_carrier_status = ifi->ifi_link_state; 621 continue; 622 } 623 if (!do_addrs) 624 continue; 625 aflag = check_addrflags(ifa->sa_family, p->ifa_addrflags); 626 if (aflag != READY) 627 continue; 628 invoke_script(ifd->ifname, UP, ifa, p->ifa_dstaddr); 629 } 630 freeifaddrs(res); 631 out: 632 close(s); 633 } 634