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