xref: /netbsd-src/usr.sbin/ifwatchd/ifwatchd.c (revision d710132b4b8ce7f7cccaaf660cb16aa16b4077a0)
1 /*	$NetBSD: ifwatchd.c,v 1.14 2003/06/23 21:50:12 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 };
70 /* local functions */
71 static void usage(void);
72 static void dispatch(void*, size_t);
73 static void check_addrs(char *cp, int addrs, enum event ev);
74 static void invoke_script(struct sockaddr *sa, struct sockaddr *dst, enum event ev, int ifindex, const char *ifname_hint);
75 static void list_interfaces(const char *ifnames);
76 static void check_announce(struct if_announcemsghdr *ifan);
77 static void rescan_interfaces(void);
78 static void free_interfaces(void);
79 static int find_interface(int index);
80 static void run_initial_ups(void);
81 
82 #ifdef SPPP_IF_SUPPORT
83 static int if_is_connected(const char * ifname);
84 #else
85 #define	if_is_connected(X)	1
86 #endif
87 
88 /* stolen from /sbin/route */
89 #define ROUNDUP(a) \
90 	((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
91 #define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
92 
93 /* global variables */
94 static int verbose = 0, quiet = 0;
95 static int inhibit_initial = 0;
96 static const char *arrival_script = NULL;
97 static const char *departure_script = NULL;
98 static const char *up_script = NULL;
99 static const char *down_script = NULL;
100 static char DummyTTY[] = _PATH_DEVNULL;
101 static char DummySpeed[] = "9600";
102 static const char **scripts[] = {
103 	&arrival_script,
104 	&departure_script,
105 	&up_script,
106 	&down_script
107 };
108 
109 struct interface_data {
110 	SLIST_ENTRY(interface_data) next;
111 	int index;
112 	char * ifname;
113 };
114 SLIST_HEAD(,interface_data) ifs = SLIST_HEAD_INITIALIZER(ifs);
115 
116 int
117 main(int argc, char **argv)
118 {
119 	int c, s, n;
120 	int errs = 0;
121 	char msg[2048], *msgp;
122 
123 	openlog(argv[0], LOG_PID|LOG_CONS, LOG_DAEMON);
124 	while ((c = getopt(argc, argv, "qvhiu:d:A:D:")) != -1)
125 		switch (c) {
126 		case 'h':
127 			usage();
128 			return 0;
129 		case 'i':
130 			inhibit_initial = 1;
131 			break;
132 		case 'v':
133 			verbose++;
134 			break;
135 		case 'q':
136 			quiet = 1;
137 			break;
138 
139 		case 'u':
140 			up_script = optarg;
141 			break;
142 
143 		case 'd':
144 			down_script = optarg;
145 			break;
146 
147 		case 'A':
148 			arrival_script = optarg;
149 			break;
150 
151 		case 'D':
152 			departure_script = optarg;
153 			break;
154 
155 		default:
156 			errs++;
157 			break;
158 		}
159 
160 	if (errs)
161 		usage();
162 
163 	argv += optind;
164 	argc -= optind;
165 
166 	if (argc <= 0)
167 		usage();
168 
169 	if (verbose) {
170 		printf("up_script: %s\ndown_script: %s\n",
171 			up_script, down_script);
172 		printf("arrival_script: %s\ndeparture_script: %s\n",
173 			arrival_script, departure_script);
174 		printf("verbosity = %d\n", verbose);
175 	}
176 
177 	while (argc > 0) {
178 		list_interfaces(argv[0]);
179 		argv++;
180 		argc--;
181 	}
182 
183 	if (!verbose)
184 		daemon(0,0);
185 
186 	s = socket(PF_ROUTE, SOCK_RAW, 0);
187 	if (s < 0) {
188 		syslog(LOG_ERR, "error opening routing socket: %m");
189 		perror("open routing socket");
190 		exit(EXIT_FAILURE);
191 	}
192 
193 	if (!inhibit_initial)
194 		run_initial_ups();
195 
196 	for (;;) {
197 		n = read(s, msg, sizeof msg);
198 		msgp = msg;
199 		for (msgp = msg; n > 0; n -= ((struct rt_msghdr*)msgp)->rtm_msglen, msgp += ((struct rt_msghdr*)msgp)->rtm_msglen) {
200 			dispatch(msgp, n);
201 
202 		}
203 	}
204 
205 	close(s);
206 	free_interfaces();
207 	closelog();
208 
209 	return EXIT_SUCCESS;
210 }
211 
212 static void
213 usage()
214 {
215 	fprintf(stderr,
216 	    "usage:\n"
217 	    "\tifwatchd [-hiv] [-A arrival-script] [-D departure-script]\n"
218 	    "\t\t  [-d down-script] [-u up-script] ifname(s)\n"
219 	    "\twhere:\n"
220 	    "\t -A <cmd> specify command to run on interface arrival event\n"
221 	    "\t -D <cmd> specify command to run on interface departure event\n"
222 	    "\t -d <cmd> specify command to run on interface down event\n"
223 	    "\t -h       show this help message\n"
224 	    "\t -i       no (!) initial run of the up script if the interface\n"
225 	    "\t          is already up on ifwatchd startup\n"
226 	    "\t -u <cmd> specify command to run on interface up event\n"
227 	    "\t -v       verbose/debug output, don't run in background\n"
228 	    "\t -q       quiet mode, don't syslog informational messages\n");
229 	exit(EXIT_FAILURE);
230 }
231 
232 static void
233 dispatch(void *msg, size_t len)
234 {
235 	struct rt_msghdr *hd = msg;
236 	struct ifa_msghdr *ifam;
237 	enum event ev;
238 
239 	switch (hd->rtm_type) {
240 	case RTM_NEWADDR:
241 		ev = UP;
242 		goto work;
243 	case RTM_DELADDR:
244 		ev = DOWN;
245 		goto work;
246 	case RTM_IFANNOUNCE:
247 		rescan_interfaces();
248 		check_announce((struct if_announcemsghdr *)msg);
249 		return;
250 	}
251 	if (verbose)
252 		printf("unknown message ignored\n");
253 	return;
254 
255 work:
256 	ifam = (struct ifa_msghdr *)msg;
257 	check_addrs((char *)(ifam + 1), ifam->ifam_addrs, ev);
258 }
259 
260 static void
261 check_addrs(cp, addrs, ev)
262 	char    *cp;
263 	int     addrs;
264 	enum event ev;
265 {
266 	struct sockaddr *sa, *ifa = NULL, *brd = NULL;
267 	int ifndx = 0, i;
268 
269 	if (addrs == 0)
270 		return;
271 	for (i = 1; i; i <<= 1) {
272 		if (i & addrs) {
273 			sa = (struct sockaddr *)cp;
274 			if (i == RTA_IFP) {
275 				struct sockaddr_dl * li = (struct sockaddr_dl*)sa;
276 				ifndx = li->sdl_index;
277 				if (!find_interface(ifndx)) {
278 					if (verbose)
279 						printf("ignoring change on interface #%d\n", ifndx);
280 					return;
281 				}
282 			} else if (i == RTA_IFA) {
283 				ifa = sa;
284 			} else if (i == RTA_BRD) {
285 				brd = sa;
286 			}
287 			ADVANCE(cp, sa);
288 		}
289 	}
290 	if (ifa != NULL)
291 		invoke_script(ifa, brd, ev, ifndx, NULL);
292 }
293 
294 static void
295 invoke_script(sa, dest, ev, ifindex, ifname_hint)
296 	struct sockaddr *sa, *dest;
297 	enum event ev;
298 	int ifindex;
299 	const char *ifname_hint;
300 {
301 	char addr[NI_MAXHOST], daddr[NI_MAXHOST], ifname_buf[IFNAMSIZ];
302 	const char *ifname;
303 	const char *script;
304 	int status;
305 
306 	if (sa != NULL && sa->sa_len == 0) {
307 		fprintf(stderr, "illegal socket address (sa_len == 0)\n");
308 		return;
309 	}
310 	if (sa != NULL && sa->sa_family == AF_INET6) {
311 		struct sockaddr_in6 sin6;
312 
313 		(void) memcpy(&sin6, (struct sockaddr_in6 *)sa, sizeof (sin6));
314 		if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr))
315 			return;
316 	}
317 
318 	addr[0] = daddr[0] = 0;
319 	ifname = if_indextoname(ifindex, ifname_buf);
320 	ifname = ifname ? ifname : ifname_hint;
321 	if (ifname == NULL)
322 		return;
323 
324 	if (sa != NULL) {
325 		if (getnameinfo(sa, sa->sa_len, addr, sizeof addr, NULL, 0,
326 		    NI_NUMERICHOST)) {
327 			if (verbose)
328 				printf("getnameinfo failed\n");
329 			return;	/* this address can not be handled */
330 		}
331 	}
332 	if (dest != NULL) {
333 		if (getnameinfo(dest, dest->sa_len, daddr, sizeof daddr,
334 		    NULL, 0, NI_NUMERICHOST)) {
335 			if (verbose)
336 				printf("getnameinfo failed\n");
337 			return;	/* this address can not be handled */
338 		}
339 	}
340 
341 	script = *scripts[ev];
342 	if (script == NULL) return;
343 
344 	if (verbose)
345 		(void) printf("calling: %s %s %s %s %s %s\n",
346 		    script, ifname, DummyTTY, DummySpeed, addr, daddr);
347 	if (!quiet)
348 		syslog(LOG_INFO, "calling: %s %s %s %s %s %s\n",
349 		    script, ifname, DummyTTY, DummySpeed, addr, daddr);
350 
351 	switch (vfork()) {
352 	case -1:
353 		fprintf(stderr, "cannot fork\n");
354 		break;
355 	case 0:
356 		if (execl(script, script, ifname, DummyTTY, DummySpeed,
357 		    addr, daddr, NULL) == -1) {
358 			syslog(LOG_ERR, "could not execute \"%s\": %m",
359 			    script);
360 			perror(script);
361 		}
362 		_exit(EXIT_FAILURE);
363 	default:
364 		(void) wait(&status);
365 	}
366 }
367 
368 static void list_interfaces(const char *ifnames)
369 {
370 	char * names = strdup(ifnames);
371 	char * name, *lasts;
372 	static const char sep[] = " \t";
373 	struct interface_data * p;
374 
375 	for (name = strtok_r(names, sep, &lasts);
376 	    name != NULL;
377 	    name = strtok_r(NULL, sep, &lasts)) {
378 		p = malloc(sizeof(*p));
379 		SLIST_INSERT_HEAD(&ifs, p, next);
380 		p->ifname = strdup(name);
381 		p->index = if_nametoindex(p->ifname);
382 		if (!quiet)
383 			syslog(LOG_INFO, "watching interface %s", p->ifname);
384 		if (verbose)
385 			printf("interface \"%s\" has index %d\n",
386 			    p->ifname, p->index);
387 	}
388 	free(names);
389 }
390 
391 static void
392 check_announce(struct if_announcemsghdr *ifan)
393 {
394 	struct interface_data * p;
395 	const char *ifname = ifan->ifan_name;
396 
397 	SLIST_FOREACH(p, &ifs, next) {
398 		if (strcmp(p->ifname, ifname) == 0) {
399 			switch (ifan->ifan_what) {
400 			case IFAN_ARRIVAL:
401 				invoke_script(NULL, NULL, ARRIVAL, p->index,
402 				    NULL);
403 				break;
404 			case IFAN_DEPARTURE:
405 				invoke_script(NULL, NULL, DEPARTURE, p->index,
406 				    p->ifname);
407 				break;
408 			default:
409 				if (verbose)
410 					(void) printf("unknown announce: "
411 					    "what=%d\n", ifan->ifan_what);
412 				break;
413 			}
414 			return;
415 		}
416 	}
417 }
418 
419 static void rescan_interfaces()
420 {
421 	struct interface_data * p;
422 
423 	SLIST_FOREACH(p, &ifs, next) {
424 		p->index = if_nametoindex(p->ifname);
425 		if (verbose)
426 			printf("interface \"%s\" has index %d\n", p->ifname,
427 			    p->index);
428 	}
429 }
430 
431 static void free_interfaces()
432 {
433 	struct interface_data * p;
434 
435 	while (!SLIST_EMPTY(&ifs)) {
436 		p = SLIST_FIRST(&ifs);
437 		SLIST_REMOVE_HEAD(&ifs, next);
438 		free(p->ifname);
439 		free(p);
440 	}
441 }
442 
443 static int find_interface(index)
444 	int index;
445 {
446 	struct interface_data * p;
447 
448 	SLIST_FOREACH(p, &ifs, next)
449 		if (p->index == index)
450 			return 1;
451 	return 0;
452 }
453 
454 static void run_initial_ups()
455 {
456 	struct interface_data * ifd;
457 	struct ifaddrs *res = NULL, *p;
458 
459 	if (getifaddrs(&res) == 0) {
460 		for (p = res; p; p = p->ifa_next) {
461 			SLIST_FOREACH(ifd, &ifs, next) {
462 				if (strcmp(ifd->ifname, p->ifa_name) == 0)
463 					break;
464 			}
465 			if (ifd == NULL)
466 				continue;
467 
468 			if (p->ifa_addr && p->ifa_addr->sa_family == AF_LINK)
469 				invoke_script(NULL, NULL, ARRIVAL, ifd->index,
470 				    NULL);
471 
472 			if ((p->ifa_flags & IFF_UP) == 0)
473 				continue;
474 			if (p->ifa_addr == NULL)
475 				continue;
476 			if (p->ifa_addr->sa_family == AF_LINK)
477 				continue;
478 			if (if_is_connected(ifd->ifname))
479 				invoke_script(p->ifa_addr, p->ifa_dstaddr, UP,
480 				    ifd->index, ifd->ifname);
481 		}
482 		freeifaddrs(res);
483 	}
484 }
485 
486 #ifdef SPPP_IF_SUPPORT
487 /*
488  * Special case support for in-kernel PPP interfaces.
489  * If these are IFF_UP, but have not yet connected or completed authentication
490  * we don't want to call the up script in the initial interface scan (there
491  * will be an UP event generated later, when IPCP completes, anyway).
492  *
493  * If this is no if_spppsubr.c based interface, this ioctl just fails and we
494  * treat is as connected.
495  */
496 static int
497 if_is_connected(const char * ifname)
498 {
499 	int s, err;
500 	struct spppstatus status;
501 
502 	memset(&status, 0, sizeof status);
503 	strncpy(status.ifname, ifname, sizeof status.ifname);
504 	s = socket(AF_INET, SOCK_DGRAM, 0);
505 	if (s < 0)
506 		return 1;	/* no idea how to handle this... */
507 	err = ioctl(s, SPPPGETSTATUS, &status);
508 	if (err != 0)
509 		/* not if_spppsubr.c based - call it connected */
510 		status.phase = SPPP_PHASE_NETWORK;
511 	close(s);
512 	return status.phase == SPPP_PHASE_NETWORK;
513 }
514 #endif
515