Home | History | Annotate | Line # | Download | only in ifwatchd
ifwatchd.c revision 1.43
      1 /*	$NetBSD: ifwatchd.c,v 1.43 2018/03/07 10:06:41 roy Exp $	*/
      2 #include <sys/cdefs.h>
      3 __RCSID("$NetBSD: ifwatchd.c,v 1.43 2018/03/07 10:06:41 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 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(void);
     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 
    202 	if (!inhibit_initial)
    203 		run_initial_ups();
    204 
    205 	iov[0].iov_base = buf;
    206 	iov[0].iov_len = sizeof(buf);
    207 	memset(&msg, 0, sizeof(msg));
    208 	msg.msg_iov = iov;
    209 	msg.msg_iovlen = 1;
    210 
    211 	for (;;) {
    212 		n = recvmsg(s, &msg, 0);
    213 		if (n == -1) {
    214 			syslog(LOG_ERR, "recvmsg: %m");
    215 			exit(EXIT_FAILURE);
    216 		}
    217 		if (n != 0)
    218 			dispatch(iov[0].iov_base, n);
    219 	}
    220 
    221 	close(s);
    222 	free_interfaces();
    223 	closelog();
    224 
    225 	return EXIT_SUCCESS;
    226 }
    227 
    228 static void
    229 usage(void)
    230 {
    231 	fprintf(stderr,
    232 	    "usage:\n"
    233 	    "\tifwatchd [-hiqv] [-A arrival-script] [-D departure-script]\n"
    234 	    "\t\t  [-d down-script] [-u up-script]\n"
    235 	    "\t\t  [-c carrier-script] [-n no-carrier-script] ifname(s)\n"
    236 	    "\twhere:\n"
    237 	    "\t -A <cmd> specify command to run on interface arrival event\n"
    238 	    "\t -c <cmd> specify command to run on interface carrier-detect event\n"
    239 	    "\t -D <cmd> specify command to run on interface departure event\n"
    240 	    "\t -d <cmd> specify command to run on interface down event\n"
    241 	    "\t -n <cmd> specify command to run on interface no-carrier-detect event\n"
    242 	    "\t -h       show this help message\n"
    243 	    "\t -i       no (!) initial run of the up script if the interface\n"
    244 	    "\t          is already up on ifwatchd startup\n"
    245 	    "\t -q       quiet mode, don't syslog informational messages\n"
    246 	    "\t -u <cmd> specify command to run on interface up event\n"
    247 	    "\t -v       verbose/debug output, don't run in background\n");
    248 	exit(EXIT_FAILURE);
    249 }
    250 
    251 static void
    252 dispatch(const void *msg, size_t len)
    253 {
    254 	const struct rt_msghdr *hd = msg;
    255 
    256 	if (hd->rtm_version != RTM_VERSION)
    257 		return;
    258 
    259 	switch (hd->rtm_type) {
    260 	case RTM_NEWADDR:
    261 	case RTM_DELADDR:
    262 		check_addrs(msg);
    263 		break;
    264 	case RTM_IFANNOUNCE:
    265 		check_announce(msg);
    266 		break;
    267 	case RTM_IFINFO:
    268 		check_carrier(msg);
    269 		break;
    270 	default:
    271 		/* Should be impossible as we filter messages. */
    272 		if (verbose)
    273 			printf("unknown message ignored (%d)\n", hd->rtm_type);
    274 		break;
    275 	}
    276 }
    277 
    278 static enum addrflag
    279 check_addrflags(int af, int addrflags)
    280 {
    281 
    282 	switch (af) {
    283 	case AF_INET:
    284 		if (addrflags & IN_IFF_NOTREADY)
    285 			return NOTREADY;
    286 		if (addrflags & IN_IFF_DETACHED)
    287 			return DETACHED;
    288 		break;
    289 	case AF_INET6:
    290 		if (addrflags & IN6_IFF_NOTREADY)
    291 			return NOTREADY;
    292 		if (addrflags & IN6_IFF_DETACHED)
    293 			return DETACHED;
    294 		break;
    295 	}
    296 	return READY;
    297 }
    298 
    299 static void
    300 check_addrs(const struct ifa_msghdr *ifam)
    301 {
    302 	const char *cp = (const char *)(ifam + 1);
    303 	const struct sockaddr *sa, *ifa = NULL, *brd = NULL;
    304 	unsigned i;
    305 	struct interface_data *ifd = NULL;
    306 	int aflag;
    307 	enum event ev;
    308 
    309 	if (ifam->ifam_addrs == 0)
    310 		return;
    311 	for (i = 1; i; i <<= 1) {
    312 		if ((i & ifam->ifam_addrs) == 0)
    313 			continue;
    314 		sa = (const struct sockaddr *)cp;
    315 		if (i == RTA_IFP) {
    316 			const struct sockaddr_dl *li;
    317 
    318 			li = (const struct sockaddr_dl *)sa;
    319 			if ((ifd = find_interface(li->sdl_index)) == NULL) {
    320 				if (verbose)
    321 					printf("ignoring change"
    322 					    " on interface #%d\n",
    323 					    li->sdl_index);
    324 				return;
    325 			}
    326 		} else if (i == RTA_IFA)
    327 			ifa = sa;
    328 		else if (i == RTA_BRD)
    329 			brd = sa;
    330 		RT_ADVANCE(cp, sa);
    331 	}
    332 	if (ifa != NULL && ifd != NULL) {
    333 		ev = ifam->ifam_type == RTM_DELADDR ? DOWN : UP;
    334 		aflag = check_addrflags(ifa->sa_family, ifam->ifam_addrflags);
    335 		if ((ev == UP && aflag == READY) || ev == DOWN)
    336 			invoke_script(ifd->ifname, ev, ifa, brd);
    337 	}
    338 }
    339 
    340 static void
    341 invoke_script(const char *ifname, enum event ev,
    342     const struct sockaddr *sa, const struct sockaddr *dest)
    343 {
    344 	char addr[NI_MAXHOST], daddr[NI_MAXHOST];
    345 	const char *script;
    346 	int status;
    347 
    348 	if (ifname == NULL)
    349 		return;
    350 
    351 	script = *scripts[ev];
    352 	if (script == NULL)
    353 		return;
    354 
    355 	addr[0] = daddr[0] = 0;
    356 	if (sa != NULL) {
    357 		const struct sockaddr_in *sin;
    358 		const struct sockaddr_in6 *sin6;
    359 
    360 		if (sa->sa_len == 0) {
    361 			syslog(LOG_ERR,
    362 			    "illegal socket address (sa_len == 0)");
    363 			return;
    364 		}
    365 		switch (sa->sa_family) {
    366 		case AF_INET:
    367 			sin = (const struct sockaddr_in *)sa;
    368 			if (sin->sin_addr.s_addr == INADDR_ANY ||
    369 			    sin->sin_addr.s_addr == INADDR_BROADCAST)
    370 				return;
    371 			break;
    372 		case AF_INET6:
    373 			sin6 = (const struct sockaddr_in6 *)sa;
    374 			if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
    375 				return;
    376 			break;
    377 		default:
    378 			break;
    379 		}
    380 
    381 		if (getnameinfo(sa, sa->sa_len, addr, sizeof addr, NULL, 0,
    382 		    NI_NUMERICHOST)) {
    383 			if (verbose)
    384 				printf("getnameinfo failed\n");
    385 			return;	/* this address can not be handled */
    386 		}
    387 	}
    388 
    389 	if (dest != NULL) {
    390 		if (getnameinfo(dest, dest->sa_len, daddr, sizeof daddr,
    391 		    NULL, 0, NI_NUMERICHOST)) {
    392 			if (verbose)
    393 				printf("getnameinfo failed\n");
    394 			return;	/* this address can not be handled */
    395 		}
    396 	}
    397 
    398 	if (verbose)
    399 		(void) printf("calling: %s %s %s %s %s %s\n",
    400 		    script, ifname, DummyTTY, DummySpeed, addr, daddr);
    401 	if (!quiet)
    402 		syslog(LOG_INFO, "calling: %s %s %s %s %s %s\n",
    403 		    script, ifname, DummyTTY, DummySpeed, addr, daddr);
    404 
    405 	switch (vfork()) {
    406 	case -1:
    407 		syslog(LOG_ERR, "cannot fork: %m");
    408 		break;
    409 	case 0:
    410 		if (execl(script, script, ifname, DummyTTY, DummySpeed,
    411 		    addr, daddr, NULL) == -1) {
    412 			syslog(LOG_ERR, "could not execute \"%s\": %m",
    413 			    script);
    414 		}
    415 		_exit(EXIT_FAILURE);
    416 	default:
    417 		(void) wait(&status);
    418 	}
    419 }
    420 
    421 static void
    422 list_interfaces(const char *ifnames)
    423 {
    424 	char * names = strdup(ifnames);
    425 	char * name, *lasts;
    426 	static const char sep[] = " \t";
    427 	struct interface_data * p;
    428 
    429 	for (name = strtok_r(names, sep, &lasts);
    430 	    name != NULL;
    431 	    name = strtok_r(NULL, sep, &lasts)) {
    432 		p = malloc(sizeof(*p));
    433 		SLIST_INSERT_HEAD(&ifs, p, next);
    434 		p->last_carrier_status = -1;
    435 		p->ifname = strdup(name);
    436 		p->index = if_nametoindex(p->ifname);
    437 		if (!quiet)
    438 			syslog(LOG_INFO, "watching interface %s", p->ifname);
    439 		if (verbose)
    440 			printf("interface \"%s\" has index %d\n",
    441 			    p->ifname, p->index);
    442 	}
    443 	free(names);
    444 }
    445 
    446 static void
    447 check_carrier(const struct if_msghdr *ifm)
    448 {
    449 	struct interface_data * p;
    450 	int carrier_status;
    451 	enum event ev;
    452 
    453 	SLIST_FOREACH(p, &ifs, next)
    454 		if (p->index == ifm->ifm_index)
    455 			break;
    456 
    457 	if (p == NULL)
    458 		return;
    459 
    460 	/*
    461 	 * Treat it as an event worth handling if:
    462 	 * - the carrier status changed, or
    463 	 * - this is the first time we've been called, and
    464 	 * inhibit_initial is not set
    465 	 */
    466 	carrier_status = ifm->ifm_data.ifi_link_state;
    467 	if (carrier_status != p->last_carrier_status) {
    468 		switch (carrier_status) {
    469 		case LINK_STATE_UP:
    470 			ev = CARRIER;
    471 			break;
    472 		case LINK_STATE_DOWN:
    473 			ev = NO_CARRIER;
    474 			break;
    475 		default:
    476 			if (verbose)
    477 				printf("unknown link status ignored\n");
    478 			return;
    479 		}
    480 		invoke_script(p->ifname, ev, NULL, NULL);
    481 		p->last_carrier_status = carrier_status;
    482 	}
    483 }
    484 
    485 static void
    486 check_announce(const struct if_announcemsghdr *ifan)
    487 {
    488 	struct interface_data * p;
    489 	const char *ifname = ifan->ifan_name;
    490 
    491 	SLIST_FOREACH(p, &ifs, next) {
    492 		if (strcmp(p->ifname, ifname) != 0)
    493 			continue;
    494 
    495 		switch (ifan->ifan_what) {
    496 		case IFAN_ARRIVAL:
    497 			p->index = ifan->ifan_index;
    498 			invoke_script(p->ifname, ARRIVAL, NULL, NULL);
    499 			break;
    500 		case IFAN_DEPARTURE:
    501 			p->index = -1;
    502 			p->last_carrier_status = -1;
    503 			invoke_script(p->ifname, DEPARTURE, NULL, NULL);
    504 			break;
    505 		default:
    506 			if (verbose)
    507 				(void) printf("unknown announce: "
    508 				    "what=%d\n", ifan->ifan_what);
    509 			break;
    510 		}
    511 		return;
    512 	}
    513 }
    514 
    515 static void
    516 free_interfaces(void)
    517 {
    518 	struct interface_data * p;
    519 
    520 	while (!SLIST_EMPTY(&ifs)) {
    521 		p = SLIST_FIRST(&ifs);
    522 		SLIST_REMOVE_HEAD(&ifs, next);
    523 		free(p->ifname);
    524 		free(p);
    525 	}
    526 }
    527 
    528 static struct interface_data *
    529 find_interface(int idx)
    530 {
    531 	struct interface_data * p;
    532 
    533 	SLIST_FOREACH(p, &ifs, next)
    534 		if (p->index == idx)
    535 			return p;
    536 	return NULL;
    537 }
    538 
    539 static void
    540 run_initial_ups(void)
    541 {
    542 	struct interface_data * ifd;
    543 	struct ifaddrs *res = NULL, *p;
    544 	struct sockaddr *ifa;
    545 	int s, aflag;
    546 
    547 	s = socket(AF_INET, SOCK_DGRAM, 0);
    548 	if (s < 0)
    549 		return;
    550 
    551 	if (getifaddrs(&res) != 0)
    552 		goto out;
    553 
    554 	for (p = res; p; p = p->ifa_next) {
    555 		SLIST_FOREACH(ifd, &ifs, next) {
    556 			if (strcmp(ifd->ifname, p->ifa_name) == 0)
    557 				break;
    558 		}
    559 		if (ifd == NULL)
    560 			continue;
    561 
    562 		ifa = p->ifa_addr;
    563 		if (ifa != NULL && ifa->sa_family == AF_LINK)
    564 			invoke_script(ifd->ifname, ARRIVAL, NULL, NULL);
    565 
    566 		if ((p->ifa_flags & IFF_UP) == 0)
    567 			continue;
    568 		if (ifa == NULL)
    569 			continue;
    570 		if (ifa->sa_family == AF_LINK) {
    571 			struct ifmediareq ifmr;
    572 
    573 			memset(&ifmr, 0, sizeof(ifmr));
    574 			strncpy(ifmr.ifm_name, ifd->ifname,
    575 			    sizeof(ifmr.ifm_name));
    576 			if (ioctl(s, SIOCGIFMEDIA, &ifmr) != -1
    577 			    && (ifmr.ifm_status & IFM_AVALID)
    578 			    && (ifmr.ifm_status & IFM_ACTIVE)) {
    579 				invoke_script(ifd->ifname, CARRIER, NULL, NULL);
    580 				ifd->last_carrier_status =
    581 				    LINK_STATE_UP;
    582 			    }
    583 			continue;
    584 		}
    585 		aflag = check_addrflags(ifa->sa_family, p->ifa_addrflags);
    586 		if (aflag != READY)
    587 			continue;
    588 		invoke_script(ifd->ifname, UP, ifa, p->ifa_dstaddr);
    589 	}
    590 	freeifaddrs(res);
    591 out:
    592 	close(s);
    593 }
    594