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