xref: /netbsd-src/usr.sbin/ifwatchd/ifwatchd.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
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@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