xref: /netbsd-src/usr.sbin/ifwatchd/ifwatchd.c (revision d3f564cbbf291cbd0d0c1f55d6a3da8fe6e9d75e)
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@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
main(int argc,char ** argv)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
usage(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
dispatch(const void * msg,size_t len)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
check_addrflags(int af,int addrflags)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
check_addrs(const struct ifa_msghdr * ifam)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
invoke_script(const char * ifname,enum event ev,const struct sockaddr * sa,const struct sockaddr * dest)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
list_interfaces(const char * ifnames)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
check_carrier(const struct if_msghdr * ifm)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
do_announce(struct interface_data * ifd,unsigned short what,unsigned short index)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
check_announce(const struct if_announcemsghdr * ifan)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
free_interfaces(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 *
find_interface(int idx)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
run_initial_ups(bool do_addrs)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