Home | History | Annotate | Line # | Download | only in ifwatchd
ifwatchd.c revision 1.36
      1 /*	$NetBSD: ifwatchd.c,v 1.36 2016/09/29 15:25:28 roy Exp $	*/
      2 #include <sys/cdefs.h>
      3  __RCSID("$NetBSD: ifwatchd.c,v 1.36 2016/09/29 15:25:28 roy 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/if_media.h>
     43 #include <net/route.h>
     44 #include <netinet/in.h>
     45 #include <netinet/in_var.h>
     46 #include <arpa/inet.h>
     47 
     48 #include <paths.h>
     49 #include <stdio.h>
     50 #include <stdlib.h>
     51 #include <string.h>
     52 #include <unistd.h>
     53 #include <netdb.h>
     54 #include <err.h>
     55 #include <ifaddrs.h>
     56 #include <syslog.h>
     57 
     58 enum event { ARRIVAL, DEPARTURE, UP, DOWN, CARRIER, NO_CARRIER };
     59 enum addrflag { NOTREADY, DETACHED, 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 struct sockaddr *sa, const struct sockaddr *dst,
     67     enum event ev, int ifindex, const char *ifname_hint);
     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 rescan_interfaces(void);
     72 static void free_interfaces(void);
     73 static struct interface_data * find_interface(int index);
     74 static void run_initial_ups(void);
     75 
     76 /* global variables */
     77 static int verbose = 0, quiet = 0;
     78 static int inhibit_initial = 0;
     79 static const char *arrival_script = NULL;
     80 static const char *departure_script = NULL;
     81 static const char *up_script = NULL;
     82 static const char *down_script = NULL;
     83 static const char *carrier_script = NULL;
     84 static const char *no_carrier_script = NULL;
     85 static const char DummyTTY[] = _PATH_DEVNULL;
     86 static const char DummySpeed[] = "9600";
     87 static const char **scripts[] = {
     88 	&arrival_script,
     89 	&departure_script,
     90 	&up_script,
     91 	&down_script,
     92 	&carrier_script,
     93 	&no_carrier_script
     94 };
     95 
     96 struct interface_data {
     97 	SLIST_ENTRY(interface_data) next;
     98 	int index;
     99 	int last_carrier_status;
    100 	char * ifname;
    101 };
    102 static SLIST_HEAD(,interface_data) ifs = SLIST_HEAD_INITIALIZER(ifs);
    103 
    104 int
    105 main(int argc, char **argv)
    106 {
    107 	int c, s, n;
    108 	int errs = 0;
    109 	struct msghdr msg;
    110 	struct iovec iov[1];
    111 	char buf[2048];
    112 
    113 	openlog(argv[0], LOG_PID|LOG_CONS, LOG_DAEMON);
    114 	while ((c = getopt(argc, argv, "qvhic:n:u:d:A:D:")) != -1) {
    115 		switch (c) {
    116 		case 'h':
    117 			usage();
    118 			return 0;
    119 
    120 		case 'i':
    121 			inhibit_initial = 1;
    122 			break;
    123 
    124 		case 'v':
    125 			verbose++;
    126 			break;
    127 
    128 		case 'q':
    129 			quiet = 1;
    130 			break;
    131 
    132 		case 'c':
    133 			carrier_script = optarg;
    134 			break;
    135 
    136 		case 'n':
    137 			no_carrier_script = optarg;
    138 			break;
    139 
    140 		case 'u':
    141 			up_script = optarg;
    142 			break;
    143 
    144 		case 'd':
    145 			down_script = optarg;
    146 			break;
    147 
    148 		case 'A':
    149 			arrival_script = optarg;
    150 			break;
    151 
    152 		case 'D':
    153 			departure_script = optarg;
    154 			break;
    155 
    156 		default:
    157 			errs++;
    158 			break;
    159 		}
    160 	}
    161 
    162 	if (errs)
    163 		usage();
    164 
    165 	argv += optind;
    166 	argc -= optind;
    167 
    168 	if (argc <= 0)
    169 		usage();
    170 
    171 	if (verbose) {
    172 		printf("up_script: %s\ndown_script: %s\n",
    173 			up_script, down_script);
    174 		printf("arrival_script: %s\ndeparture_script: %s\n",
    175 			arrival_script, departure_script);
    176 		printf("carrier_script: %s\nno_carrier_script: %s\n",
    177 			carrier_script, no_carrier_script);
    178 		printf("verbosity = %d\n", verbose);
    179 	}
    180 
    181 	while (argc > 0) {
    182 		list_interfaces(argv[0]);
    183 		argv++;
    184 		argc--;
    185 	}
    186 
    187 	if (!verbose)
    188 		daemon(0, 0);
    189 
    190 	s = socket(PF_ROUTE, SOCK_RAW, 0);
    191 	if (s < 0) {
    192 		syslog(LOG_ERR, "error opening routing socket: %m");
    193 		perror("open routing socket");
    194 		exit(EXIT_FAILURE);
    195 	}
    196 
    197 	if (!inhibit_initial)
    198 		run_initial_ups();
    199 
    200 	iov[0].iov_base = buf;
    201 	iov[0].iov_len = sizeof(buf);
    202 	memset(&msg, 0, sizeof(msg));
    203 	msg.msg_iov = iov;
    204 	msg.msg_iovlen = 1;
    205 
    206 	for (;;) {
    207 		n = recvmsg(s, &msg, 0);
    208 		if (n == -1) {
    209 			syslog(LOG_ERR, "recvmsg: %m");
    210 			exit(EXIT_FAILURE);
    211 		}
    212 		if (n != 0)
    213 			dispatch(iov[0].iov_base, n);
    214 	}
    215 
    216 	close(s);
    217 	free_interfaces();
    218 	closelog();
    219 
    220 	return EXIT_SUCCESS;
    221 }
    222 
    223 static void
    224 usage(void)
    225 {
    226 	fprintf(stderr,
    227 	    "usage:\n"
    228 	    "\tifwatchd [-hiqv] [-A arrival-script] [-D departure-script]\n"
    229 	    "\t\t  [-d down-script] [-u up-script]\n"
    230 	    "\t\t  [-c carrier-script] [-n no-carrier-script] ifname(s)\n"
    231 	    "\twhere:\n"
    232 	    "\t -A <cmd> specify command to run on interface arrival event\n"
    233 	    "\t -c <cmd> specify command to run on interface carrier-detect event\n"
    234 	    "\t -D <cmd> specify command to run on interface departure event\n"
    235 	    "\t -d <cmd> specify command to run on interface down event\n"
    236 	    "\t -n <cmd> specify command to run on interface no-carrier-detect event\n"
    237 	    "\t -h       show this help message\n"
    238 	    "\t -i       no (!) initial run of the up script if the interface\n"
    239 	    "\t          is already up on ifwatchd startup\n"
    240 	    "\t -q       quiet mode, don't syslog informational messages\n"
    241 	    "\t -u <cmd> specify command to run on interface up event\n"
    242 	    "\t -v       verbose/debug output, don't run in background\n");
    243 	exit(EXIT_FAILURE);
    244 }
    245 
    246 static void
    247 dispatch(const void *msg, size_t len)
    248 {
    249 	const struct rt_msghdr *hd = msg;
    250 
    251 	if (hd->rtm_version != RTM_VERSION)
    252 		return;
    253 
    254 	switch (hd->rtm_type) {
    255 	case RTM_NEWADDR:
    256 	case RTM_DELADDR:
    257 		check_addrs(msg);
    258 		break;
    259 	case RTM_IFANNOUNCE:
    260 		rescan_interfaces();
    261 		check_announce(msg);
    262 		break;
    263 	case RTM_IFINFO:
    264 		check_carrier(msg);
    265 		break;
    266 	case RTM_ADD:
    267 	case RTM_DELETE:
    268 	case RTM_CHANGE:
    269 	case RTM_LOSING:
    270 	case RTM_REDIRECT:
    271 	case RTM_MISS:
    272 	case RTM_IEEE80211:
    273 	case RTM_ONEWADDR:
    274 	case RTM_ODELADDR:
    275 	case RTM_OCHGADDR:
    276 		break;
    277 	default:
    278 		if (verbose)
    279 			printf("unknown message ignored (%d)\n", hd->rtm_type);
    280 		break;
    281 	}
    282 }
    283 
    284 static enum addrflag
    285 check_addrflags(int af, int addrflags)
    286 {
    287 
    288 	switch (af) {
    289 	case AF_INET:
    290 		if (addrflags & IN_IFF_NOTREADY)
    291 			return NOTREADY;
    292 		if (addrflags & IN_IFF_DETACHED)
    293 			return DETACHED;
    294 		break;
    295 	case AF_INET6:
    296 		if (addrflags & IN6_IFF_NOTREADY)
    297 			return NOTREADY;
    298 		if (addrflags & IN6_IFF_DETACHED)
    299 			return DETACHED;
    300 		break;
    301 	}
    302 	return READY;
    303 }
    304 
    305 static void
    306 check_addrs(const struct ifa_msghdr *ifam)
    307 {
    308 	const char *cp = (const char *)(ifam + 1);
    309 	const struct sockaddr *sa, *ifa = NULL, *brd = NULL;
    310 	unsigned i;
    311 	struct interface_data *ifd = NULL;
    312 	int aflag;
    313 	enum event ev;
    314 
    315 	if (ifam->ifam_addrs == 0)
    316 		return;
    317 	for (i = 1; i; i <<= 1) {
    318 		if ((i & ifam->ifam_addrs) == 0)
    319 			continue;
    320 		sa = (const struct sockaddr *)cp;
    321 		if (i == RTA_IFP) {
    322 			const struct sockaddr_dl *li;
    323 
    324 			li = (const struct sockaddr_dl *)sa;
    325 			if ((ifd = find_interface(li->sdl_index)) == NULL) {
    326 				if (verbose)
    327 					printf("ignoring change"
    328 					    " on interface #%d\n",
    329 					    li->sdl_index);
    330 				return;
    331 			}
    332 		} else if (i == RTA_IFA)
    333 			ifa = sa;
    334 		else if (i == RTA_BRD)
    335 			brd = sa;
    336 		RT_ADVANCE(cp, sa);
    337 	}
    338 	if (ifa != NULL && ifd != NULL) {
    339 		ev = ifam->ifam_type == RTM_DELADDR ? DOWN : UP;
    340 		aflag = check_addrflags(ifa->sa_family, ifam->ifam_addrflags);
    341 		if (ev == UP) {
    342 			if (aflag == NOTREADY)
    343 				return;
    344 			if (aflag == DETACHED)
    345 				return;		/* XXX set ev to DOWN? */
    346 		}
    347 		if ((ev == UP && aflag == READY) ||
    348 		    (ev == DOWN && aflag == DETACHED /* XXX why DETACHED? */))
    349 			invoke_script(ifa, brd, ev, ifd->index, ifd->ifname);
    350 	}
    351 }
    352 
    353 static void
    354 invoke_script(const struct sockaddr *sa, const struct sockaddr *dest,
    355     enum event ev, int ifindex, const char *ifname_hint)
    356 {
    357 	char addr[NI_MAXHOST], daddr[NI_MAXHOST], ifname_buf[IFNAMSIZ];
    358 	const char * volatile ifname;
    359 	const char *script;
    360 	int status;
    361 
    362 	if (sa != NULL) {
    363 		if (sa->sa_len == 0) {
    364 			fprintf(stderr,
    365 			    "illegal socket address (sa_len == 0)\n");
    366 			return;
    367 		}
    368 		switch (sa->sa_family) {
    369 		case AF_INET:
    370 		{
    371 			const struct sockaddr_in *sin;
    372 
    373 			sin = (const struct sockaddr_in *)sa;
    374 			if (sin->sin_addr.s_addr == INADDR_ANY ||
    375 			    sin->sin_addr.s_addr == INADDR_BROADCAST)
    376 				return;
    377 		}
    378 		case AF_INET6:
    379 		{
    380 			const struct sockaddr_in6 *sin6;
    381 
    382 			sin6 = (const struct sockaddr_in6 *)sa;
    383 			if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
    384 				return;
    385 		}
    386 		}
    387 	}
    388 
    389 	addr[0] = daddr[0] = 0;
    390 	ifname = if_indextoname(ifindex, ifname_buf);
    391 	ifname = ifname ? ifname : ifname_hint;
    392 	if (ifname == NULL)
    393 		return;
    394 
    395 	if (sa != NULL) {
    396 		if (getnameinfo(sa, sa->sa_len, addr, sizeof addr, NULL, 0,
    397 		    NI_NUMERICHOST)) {
    398 			if (verbose)
    399 				printf("getnameinfo failed\n");
    400 			return;	/* this address can not be handled */
    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 	script = *scripts[ev];
    413 	if (script == NULL) return;
    414 
    415 	if (verbose)
    416 		(void) printf("calling: %s %s %s %s %s %s\n",
    417 		    script, ifname, DummyTTY, DummySpeed, addr, daddr);
    418 	if (!quiet)
    419 		syslog(LOG_INFO, "calling: %s %s %s %s %s %s\n",
    420 		    script, ifname, DummyTTY, DummySpeed, addr, daddr);
    421 
    422 	switch (vfork()) {
    423 	case -1:
    424 		fprintf(stderr, "cannot fork\n");
    425 		break;
    426 	case 0:
    427 		if (execl(script, script, ifname, DummyTTY, DummySpeed,
    428 		    addr, daddr, NULL) == -1) {
    429 			syslog(LOG_ERR, "could not execute \"%s\": %m",
    430 			    script);
    431 			perror(script);
    432 		}
    433 		_exit(EXIT_FAILURE);
    434 	default:
    435 		(void) wait(&status);
    436 	}
    437 }
    438 
    439 static void
    440 list_interfaces(const char *ifnames)
    441 {
    442 	char * names = strdup(ifnames);
    443 	char * name, *lasts;
    444 	static const char sep[] = " \t";
    445 	struct interface_data * p;
    446 
    447 	for (name = strtok_r(names, sep, &lasts);
    448 	    name != NULL;
    449 	    name = strtok_r(NULL, sep, &lasts)) {
    450 		p = malloc(sizeof(*p));
    451 		SLIST_INSERT_HEAD(&ifs, p, next);
    452 		p->last_carrier_status = -1;
    453 		p->ifname = strdup(name);
    454 		p->index = if_nametoindex(p->ifname);
    455 		if (!quiet)
    456 			syslog(LOG_INFO, "watching interface %s", p->ifname);
    457 		if (verbose)
    458 			printf("interface \"%s\" has index %d\n",
    459 			    p->ifname, p->index);
    460 	}
    461 	free(names);
    462 }
    463 
    464 static void
    465 check_carrier(const struct if_msghdr *ifm)
    466 {
    467 	struct interface_data * p;
    468 	int carrier_status;
    469 	enum event ev;
    470 
    471 	SLIST_FOREACH(p, &ifs, next)
    472 		if (p->index == ifm->ifm_index)
    473 			break;
    474 
    475 	if (p == NULL)
    476 		return;
    477 
    478 	/*
    479 	 * Treat it as an event worth handling if:
    480 	 * - the carrier status changed, or
    481 	 * - this is the first time we've been called, and
    482 	 * inhibit_initial is not set
    483 	 */
    484 	carrier_status = ifm->ifm_data.ifi_link_state;
    485 	if ((carrier_status != p->last_carrier_status) ||
    486 	    ((p->last_carrier_status == -1) && !inhibit_initial)) {
    487 		switch (carrier_status) {
    488 		case LINK_STATE_UP:
    489 			ev = CARRIER;
    490 			break;
    491 		case LINK_STATE_DOWN:
    492 			ev = NO_CARRIER;
    493 			break;
    494 		default:
    495 			if (verbose)
    496 				printf("unknown link status ignored\n");
    497 			return;
    498 		}
    499 		invoke_script(NULL, NULL, ev, ifm->ifm_index, p->ifname);
    500 		p->last_carrier_status = carrier_status;
    501 	}
    502 }
    503 
    504 static void
    505 check_announce(const struct if_announcemsghdr *ifan)
    506 {
    507 	struct interface_data * p;
    508 	const char *ifname = ifan->ifan_name;
    509 
    510 	SLIST_FOREACH(p, &ifs, next) {
    511 		if (strcmp(p->ifname, ifname) != 0)
    512 			continue;
    513 
    514 		switch (ifan->ifan_what) {
    515 		case IFAN_ARRIVAL:
    516 			invoke_script(NULL, NULL, ARRIVAL, p->index,
    517 			    NULL);
    518 			break;
    519 		case IFAN_DEPARTURE:
    520 			invoke_script(NULL, NULL, DEPARTURE, p->index,
    521 			    p->ifname);
    522 			break;
    523 		default:
    524 			if (verbose)
    525 				(void) printf("unknown announce: "
    526 				    "what=%d\n", ifan->ifan_what);
    527 			break;
    528 		}
    529 		return;
    530 	}
    531 }
    532 
    533 static void
    534 rescan_interfaces(void)
    535 {
    536 	struct interface_data * p;
    537 
    538 	SLIST_FOREACH(p, &ifs, next) {
    539 		p->index = if_nametoindex(p->ifname);
    540 		if (verbose)
    541 			printf("interface \"%s\" has index %d\n", p->ifname,
    542 			    p->index);
    543 	}
    544 }
    545 
    546 static void
    547 free_interfaces(void)
    548 {
    549 	struct interface_data * p;
    550 
    551 	while (!SLIST_EMPTY(&ifs)) {
    552 		p = SLIST_FIRST(&ifs);
    553 		SLIST_REMOVE_HEAD(&ifs, next);
    554 		free(p->ifname);
    555 		free(p);
    556 	}
    557 }
    558 
    559 static struct interface_data *
    560 find_interface(int idx)
    561 {
    562 	struct interface_data * p;
    563 
    564 	SLIST_FOREACH(p, &ifs, next)
    565 		if (p->index == idx)
    566 			return p;
    567 	return NULL;
    568 }
    569 
    570 static void
    571 run_initial_ups(void)
    572 {
    573 	struct interface_data * ifd;
    574 	struct ifaddrs *res = NULL, *p;
    575 	struct sockaddr *ifa;
    576 	int s, aflag;
    577 
    578 	s = socket(AF_INET, SOCK_DGRAM, 0);
    579 	if (s < 0)
    580 		return;
    581 
    582 	if (getifaddrs(&res) != 0)
    583 		goto out;
    584 
    585 	for (p = res; p; p = p->ifa_next) {
    586 		SLIST_FOREACH(ifd, &ifs, next) {
    587 			if (strcmp(ifd->ifname, p->ifa_name) == 0)
    588 				break;
    589 		}
    590 		if (ifd == NULL)
    591 			continue;
    592 
    593 		ifa = p->ifa_addr;
    594 		if (ifa != NULL && ifa->sa_family == AF_LINK)
    595 			invoke_script(NULL, NULL, ARRIVAL, ifd->index,
    596 			    NULL);
    597 
    598 		if ((p->ifa_flags & IFF_UP) == 0)
    599 			continue;
    600 		if (ifa == NULL)
    601 			continue;
    602 		if (ifa->sa_family == AF_LINK) {
    603 			struct ifmediareq ifmr;
    604 
    605 			memset(&ifmr, 0, sizeof(ifmr));
    606 			strncpy(ifmr.ifm_name, ifd->ifname,
    607 			    sizeof(ifmr.ifm_name));
    608 			if (ioctl(s, SIOCGIFMEDIA, &ifmr) != -1
    609 			    && (ifmr.ifm_status & IFM_AVALID)
    610 			    && (ifmr.ifm_status & IFM_ACTIVE)) {
    611 				invoke_script(NULL, NULL, CARRIER,
    612 				    ifd->index, ifd->ifname);
    613 				ifd->last_carrier_status =
    614 				    LINK_STATE_UP;
    615 			    }
    616 			continue;
    617 		}
    618 		aflag = check_addrflags(ifa->sa_family, p->ifa_addrflags);
    619 		if (aflag != READY)
    620 			continue;
    621 		invoke_script(ifa, p->ifa_dstaddr, UP, ifd->index, ifd->ifname);
    622 	}
    623 	freeifaddrs(res);
    624 out:
    625 	close(s);
    626 }
    627