ifwatchd.c revision 1.14 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 (at) 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