xref: /freebsd/tools/tools/net80211/wlanwds/wlanwds.c (revision 38069501)
1 /*-
2  * Copyright (c) 2006-2009 Sam Leffler, Errno Consulting
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13  *    redistribution must be conditioned upon including a substantially
14  *    similar Disclaimer requirement for further binary redistribution.
15  *
16  * NO WARRANTY
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27  * THE POSSIBILITY OF SUCH DAMAGES.
28  *
29  * $FreeBSD$
30  */
31 
32 /*
33  * Test app to demonstrate how to handle dynamic WDS links:
34  * o monitor 802.11 events for wds discovery events
35  * o create wds vap's in response to wds discovery events
36  *   and launch a script to handle adding the vap to the
37  *   bridge, etc.
38  * o destroy wds vap's when station leaves
39  */
40 #include <sys/param.h>
41 #include <sys/file.h>
42 #include <sys/socket.h>
43 #include <sys/ioctl.h>
44 #include <sys/sysctl.h>
45 #include <sys/types.h>
46 
47 #include <net/if.h>
48 #include "net/if_media.h"
49 #include <net/route.h>
50 #include <net/if_dl.h>
51 #include <netinet/in.h>
52 #include <netinet/if_ether.h>
53 #include "net80211/ieee80211_ioctl.h"
54 #include "net80211/ieee80211_freebsd.h"
55 #include <arpa/inet.h>
56 #include <netdb.h>
57 
58 #include <net/if.h>
59 #include <net/if_types.h>
60 
61 #include <ctype.h>
62 #include <err.h>
63 #include <errno.h>
64 #include <paths.h>
65 #include <stdarg.h>
66 #include <stdio.h>
67 #include <stdlib.h>
68 #include <string.h>
69 #include <sysexits.h>
70 #include <syslog.h>
71 #include <unistd.h>
72 #include <ifaddrs.h>
73 
74 #define	IEEE80211_ADDR_EQ(a1,a2)	(memcmp(a1,a2,IEEE80211_ADDR_LEN) == 0)
75 #define	IEEE80211_ADDR_COPY(dst,src)	memcpy(dst,src,IEEE80211_ADDR_LEN)
76 
77 struct wds {
78 	struct wds *next;
79 	uint8_t	bssid[IEEE80211_ADDR_LEN];	/* bssid of associated sta */
80 	char	ifname[IFNAMSIZ];		/* vap interface name */
81 };
82 static struct wds *wds;
83 
84 static	const char *script = NULL;
85 static	char **ifnets;
86 static	int nifnets = 0;
87 static	int verbose = 0;
88 static	int discover_on_join = 0;
89 
90 static	void scanforvaps(int s);
91 static	void handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen);
92 static	void wds_discovery(const char *ifname,
93 		const uint8_t bssid[IEEE80211_ADDR_LEN]);
94 static	void wds_destroy(const char *ifname);
95 static	void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]);
96 static	int wds_vap_create(const char *ifname, uint8_t macaddr[ETHER_ADDR_LEN],
97 	    struct wds *);
98 static	int wds_vap_destroy(const char *ifname);
99 
100 static void
101 usage(const char *progname)
102 {
103 	fprintf(stderr, "usage: %s [-efjtv] [-P pidfile] [-s <set_scriptname>] [ifnet0 ... | any]\n",
104 		progname);
105 	exit(-1);
106 }
107 
108 int
109 main(int argc, char *argv[])
110 {
111 	const char *progname = argv[0];
112 	const char *pidfile = NULL;
113 	int s, c, logmask, bg = 1;
114 	char msg[2048];
115 	int log_stderr = 0;
116 
117 	logmask = LOG_UPTO(LOG_INFO);
118 	while ((c = getopt(argc, argv, "efjP:s:tv")) != -1)
119 		switch (c) {
120 		case 'e':
121 			log_stderr = LOG_PERROR;
122 			break;
123 		case 'f':
124 			bg = 0;
125 			break;
126 		case 'j':
127 			discover_on_join = 1;
128 			break;
129 		case 'P':
130 			pidfile = optarg;
131 			break;
132 		case 's':
133 			script = optarg;
134 			break;
135 		case 't':
136 			logmask = LOG_UPTO(LOG_ERR);
137 			break;
138 		case 'v':
139 			logmask = LOG_UPTO(LOG_DEBUG);
140 			break;
141 		case '?':
142 			usage(progname);
143 			/*NOTREACHED*/
144 		}
145 	argc -= optind, argv += optind;
146 	if (argc == 0) {
147 		fprintf(stderr, "%s: no ifnet's specified to monitor\n",
148 		    progname);
149 		usage(progname);
150 	}
151 	ifnets = argv;
152 	nifnets = argc;
153 
154 	s = socket(PF_ROUTE, SOCK_RAW, 0);
155 	if (s < 0)
156 		err(EX_OSERR, "socket");
157 	/*
158 	 * Scan for inherited state.
159 	 */
160 	scanforvaps(s);
161 
162 	/* XXX what directory to work in? */
163 	if (bg && daemon(0, 0) < 0)
164 		err(EX_OSERR, "daemon");
165 
166 	openlog("wlanwds", log_stderr | LOG_PID | LOG_CONS, LOG_DAEMON);
167 	setlogmask(logmask);
168 
169 	for (;;) {
170 		ssize_t n = read(s, msg, sizeof(msg));
171 		handle_rtmsg((struct rt_msghdr *)msg, n);
172 	}
173 	return 0;
174 }
175 
176 static const char *
177 ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN])
178 {
179 	static char buf[32];
180 
181 	snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
182 		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
183 	return buf;
184 }
185 
186 /*
187  * Fetch a vap's parent ifnet name.
188  */
189 static int
190 getparent(const char *ifname, char parent[IFNAMSIZ+1])
191 {
192 	char oid[256];
193 	size_t parentlen;
194 
195 	/* fetch parent interface name */
196 	snprintf(oid, sizeof(oid), "net.wlan.%s.%%parent", ifname+4);
197 	parentlen = IFNAMSIZ;
198 	if (sysctlbyname(oid, parent, &parentlen, NULL, 0) < 0)
199 		return -1;
200 	parent[parentlen] = '\0';
201 	return 0;
202 }
203 
204 /*
205  * Check if the specified ifnet is one we're supposed to monitor.
206  * The ifnet is assumed to be a vap; we find it's parent and check
207  * it against the set of ifnet's specified on the command line.
208  *
209  * TODO: extend this to also optionally allow the specific DWDS
210  * VAP to be monitored, instead of assuming all VAPs on a parent
211  * physical interface are being monitored by this instance of
212  * wlanwds.
213  */
214 static int
215 checkifnet(const char *ifname, int complain)
216 {
217 	char parent[256];
218 	int i;
219 
220 	if (getparent(ifname, parent) < 0) {
221 		if (complain)
222 			syslog(LOG_ERR,
223 			   "%s: no pointer to parent interface: %m", ifname);
224 		return 0;
225 	}
226 
227 	for (i = 0; i < nifnets; i++)
228 		if (strcasecmp(ifnets[i], "any") == 0 ||
229 		    strcmp(ifnets[i], parent) == 0)
230 			return 1;
231 	syslog(LOG_DEBUG, "%s: parent %s not being monitored", ifname, parent);
232 	return 0;
233 }
234 
235 /*
236  * Return 1 if the specified ifnet is a WDS vap.
237  */
238 static int
239 iswdsvap(int s, const char *ifname)
240 {
241 	struct ifmediareq ifmr;
242 
243 	memset(&ifmr, 0, sizeof(ifmr));
244 	strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
245 	if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
246 		err(-1, "%s: cannot get media", ifname);
247 	return (ifmr.ifm_current & IFM_IEEE80211_WDS) != 0;
248 }
249 
250 /*
251  * Fetch the bssid for an ifnet.  The caller is assumed
252  * to have already verified this is possible.
253  */
254 static void
255 getbssid(int s, const char *ifname, uint8_t bssid[IEEE80211_ADDR_LEN])
256 {
257 	struct ieee80211req ireq;
258 
259 	memset(&ireq, 0, sizeof(ireq));
260 	strncpy(ireq.i_name, ifname, sizeof(ireq.i_name));
261 	ireq.i_type = IEEE80211_IOC_BSSID;
262 	ireq.i_data = bssid;
263 	ireq.i_len = IEEE80211_ADDR_LEN;
264 	if (ioctl(s, SIOCG80211, &ireq) < 0)
265 		err(-1, "%s: cannot fetch bssid", ifname);
266 }
267 
268 /*
269  * Fetch the mac address configured for a given ifnet.
270  * (Note - the current link level address, NOT hwaddr.)
271  *
272  * This is currently, sigh, O(n) because there's no current kernel
273  * API that will do it for a single interface.
274  *
275  * Return 0 if successful, -1 if failure.
276  */
277 static int
278 getlladdr(const char *ifname, uint8_t macaddr[ETHER_ADDR_LEN])
279 {
280 	struct ifaddrs *ifap, *ifa;
281 	struct sockaddr_dl *sdl;
282 
283 	if (getifaddrs(&ifap) < 0) {
284 		warn("%s: getifaddrs", __func__);
285 		return (-1);
286 	}
287 
288 	/* Look for a matching interface */
289 	for (ifa = ifap; ifa != NULL; ifa++) {
290 		if (strcmp(ifname, ifa->ifa_name) != 0)
291 			continue;
292 
293 		/* Found it - check if there's an ifa_addr */
294 		if (ifa->ifa_addr == NULL) {
295 			syslog(LOG_CRIT, "%s: ifname %s; ifa_addr is NULL\n",
296 			    __func__, ifname);
297 			goto err;
298 		}
299 
300 		/* Check address family */
301 		sdl = (struct sockaddr_dl *) ifa->ifa_addr;
302 		if (sdl->sdl_type != IFT_ETHER) {
303 			syslog(LOG_CRIT, "%s: %s: unknown aftype (%d)\n",
304 			    __func__,
305 			    ifname,
306 			    sdl->sdl_type);
307 			goto err;
308 		}
309 		if (sdl->sdl_alen != ETHER_ADDR_LEN) {
310 			syslog(LOG_CRIT, "%s: %s: aflen too short (%d)\n",
311 			    __func__,
312 			    ifname,
313 			    sdl->sdl_alen);
314 			goto err;
315 		}
316 
317 		/* Ok, found it */
318 		memcpy(macaddr, (void *) LLADDR(sdl), ETHER_ADDR_LEN);
319 		goto ok;
320 	}
321 	syslog(LOG_CRIT, "%s: couldn't find ifname %s\n", __func__, ifname);
322 	/* FALLTHROUGH */
323 err:
324 	freeifaddrs(ifap);
325 	return (-1);
326 
327 ok:
328 	freeifaddrs(ifap);
329 	return (0);
330 }
331 
332 /*
333  * Scan the system for WDS vaps associated with the ifnet's we're
334  * supposed to monitor.  Any vaps are added to our internal table
335  * so we can find them (and destroy them) on station leave.
336  */
337 static void
338 scanforvaps(int s)
339 {
340 	char ifname[IFNAMSIZ+1];
341 	uint8_t bssid[IEEE80211_ADDR_LEN];
342 	int i;
343 
344 	/* XXX brutal; should just walk sysctl tree */
345 	for (i = 0; i < 128; i++) {
346 		snprintf(ifname, sizeof(ifname), "wlan%d", i);
347 		if (checkifnet(ifname, 0) && iswdsvap(s, ifname)) {
348 			struct wds *p = malloc(sizeof(struct wds));
349 			if (p == NULL)
350 				err(-1, "%s: malloc failed", __func__);
351 			strlcpy(p->ifname, ifname, IFNAMSIZ);
352 			getbssid(s, ifname, p->bssid);
353 			p->next = wds;
354 			wds = p;
355 
356 			syslog(LOG_INFO, "[%s] discover wds vap %s",
357 			    ether_sprintf(bssid), ifname);
358 		}
359 	}
360 }
361 
362 /*
363  * Process a routing socket message.  We handle messages related
364  * to dynamic WDS:
365  * o on WDS discovery (rx of a 4-address frame with DWDS enabled)
366  *   we create a WDS vap for the specified mac address
367  * o on station leave we destroy any associated WDS vap
368  * o on ifnet destroy we update state if this is manual destroy of
369  *   a WDS vap in our table
370  * o if the -j option is supplied on the command line we create
371  *   WDS vaps on station join/rejoin, this is useful for some setups
372  *   where a WDS vap is required for 4-address traffic to flow
373  */
374 static void
375 handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen)
376 {
377 	struct if_announcemsghdr *ifan;
378 
379 	if (rtm->rtm_version != RTM_VERSION) {
380 		syslog(LOG_ERR, "routing message version %d not understood",
381 		    rtm->rtm_version);
382 		return;
383 	}
384 	switch (rtm->rtm_type) {
385 	case RTM_IFANNOUNCE:
386 		ifan = (struct if_announcemsghdr *)rtm;
387 		switch (ifan->ifan_what) {
388 		case IFAN_ARRIVAL:
389 			syslog(LOG_DEBUG,
390 			    "RTM_IFANNOUNCE: if# %d, what: arrival",
391 			    ifan->ifan_index);
392 			break;
393 		case IFAN_DEPARTURE:
394 			syslog(LOG_DEBUG,
395 			    "RTM_IFANNOUNCE: if# %d, what: departure",
396 			    ifan->ifan_index);
397 			/* NB: ok to call w/ unmonitored ifnets */
398 			wds_destroy(ifan->ifan_name);
399 			break;
400 		}
401 		break;
402 	case RTM_IEEE80211:
403 #define	V(type)	((struct type *)(&ifan[1]))
404 		ifan = (struct if_announcemsghdr *)rtm;
405 		switch (ifan->ifan_what) {
406 		case RTM_IEEE80211_DISASSOC:
407 			if (!discover_on_join)
408 				break;
409 			/* fall thru... */
410 		case RTM_IEEE80211_LEAVE:
411 			if (!checkifnet(ifan->ifan_name, 1))
412 				break;
413 			syslog(LOG_INFO, "[%s] station leave",
414 			    ether_sprintf(V(ieee80211_leave_event)->iev_addr));
415 			wds_leave(V(ieee80211_leave_event)->iev_addr);
416 			break;
417 		case RTM_IEEE80211_JOIN:
418 		case RTM_IEEE80211_REJOIN:
419 		case RTM_IEEE80211_ASSOC:
420 		case RTM_IEEE80211_REASSOC:
421 			if (!discover_on_join)
422 				break;
423 			/* fall thru... */
424 		case RTM_IEEE80211_WDS:
425 			syslog(LOG_INFO, "[%s] wds discovery",
426 			    ether_sprintf(V(ieee80211_wds_event)->iev_addr));
427 			if (!checkifnet(ifan->ifan_name, 1))
428 				break;
429 			wds_discovery(ifan->ifan_name,
430 			    V(ieee80211_wds_event)->iev_addr);
431 			break;
432 		}
433 		break;
434 #undef V
435 	}
436 }
437 
438 /*
439  * Handle WDS discovery; create a WDS vap for the specified bssid.
440  * If a vap already exists then do nothing (can happen when a flood
441  * of 4-address frames causes multiple events to be queued before
442  * we create a vap).
443  */
444 static void
445 wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN])
446 {
447 	struct wds *p;
448 	char parent[256];
449 	char cmd[1024];
450 	uint8_t macaddr[ETHER_ADDR_LEN];
451 	int status;
452 
453 	for (p = wds; p != NULL; p = p->next)
454 		if (IEEE80211_ADDR_EQ(p->bssid, bssid)) {
455 			syslog(LOG_INFO, "[%s] wds vap already created (%s)",
456 			    ether_sprintf(bssid), ifname);
457 			return;
458 		}
459 	if (getparent(ifname, parent) < 0) {
460 		syslog(LOG_ERR, "%s: no pointer to parent interface: %m",
461 		    ifname);
462 		return;
463 	}
464 
465 	if (getlladdr(ifname, macaddr) < 0) {
466 		syslog(LOG_ERR, "%s: couldn't get lladdr for parent interface: %m",
467 		    ifname);
468 		return;
469 	}
470 
471 	p = malloc(sizeof(struct wds));
472 	if (p == NULL) {
473 		syslog(LOG_ERR, "%s: malloc failed: %m", __func__);
474 		return;
475 	}
476 	IEEE80211_ADDR_COPY(p->bssid, bssid);
477 	if (wds_vap_create(parent, macaddr, p) < 0) {
478 		free(p);
479 		return;
480 	}
481 	/*
482 	 * Add to table and launch setup script.
483 	 */
484 	p->next = wds;
485 	wds = p;
486 	syslog(LOG_INFO, "[%s] create wds vap %s, parent %s (%s)",
487 	    ether_sprintf(bssid),
488 	    p->ifname,
489 	    ifname,
490 	    parent);
491 	if (script != NULL) {
492 		snprintf(cmd, sizeof(cmd), "%s %s", script, p->ifname);
493 		status = system(cmd);
494 		if (status)
495 			syslog(LOG_ERR, "vap setup script %s exited with "
496 			    "status %d", script, status);
497 	}
498 }
499 
500 /*
501  * Destroy a WDS vap (if known).
502  */
503 static void
504 wds_destroy(const char *ifname)
505 {
506 	struct wds *p, **pp;
507 
508 	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
509 		if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0)
510 			break;
511 	if (p != NULL) {
512 		*pp = p->next;
513 		/* NB: vap already destroyed */
514 		free(p);
515 		return;
516 	}
517 }
518 
519 /*
520  * Handle a station leave event; destroy any associated WDS vap.
521  */
522 static void
523 wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN])
524 {
525 	struct wds *p, **pp;
526 
527 	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
528 		if (IEEE80211_ADDR_EQ(p->bssid, bssid))
529 			break;
530 	if (p != NULL) {
531 		*pp = p->next;
532 		if (wds_vap_destroy(p->ifname) >= 0)
533 			syslog(LOG_INFO, "[%s] wds vap %s destroyed",
534 			    ether_sprintf(bssid), p->ifname);
535 		free(p);
536 	}
537 }
538 
539 static int
540 wds_vap_create(const char *parent, uint8_t macaddr[ETHER_ADDR_LEN],
541     struct wds *p)
542 {
543 	struct ieee80211_clone_params cp;
544 	struct ifreq ifr;
545 	int s, status;
546 	char bssid_str[32], macaddr_str[32];
547 
548 	memset(&cp, 0, sizeof(cp));
549 
550 	/* Parent interface */
551 	strncpy(cp.icp_parent, parent, IFNAMSIZ);
552 
553 	/* WDS interface */
554 	cp.icp_opmode = IEEE80211_M_WDS;
555 
556 	/* BSSID for the current node */
557 	IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid);
558 
559 	/*
560 	 * Set the MAC address to match the actual interface
561 	 * that we received the discovery event from.
562 	 * That way we can run WDS on any VAP rather than
563 	 * only the first VAP and then correctly set the
564 	 * MAC address.
565 	 */
566 	cp.icp_flags |= IEEE80211_CLONE_MACADDR;
567 	IEEE80211_ADDR_COPY(cp.icp_macaddr, macaddr);
568 
569 	memset(&ifr, 0, sizeof(ifr));
570 	strncpy(ifr.ifr_name, "wlan", IFNAMSIZ);
571 	ifr.ifr_data = (void *) &cp;
572 
573 	status = -1;
574 	s = socket(AF_INET, SOCK_DGRAM, 0);
575 	if (s >= 0) {
576 		if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) {
577 			strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ);
578 			status = 0;
579 		} else {
580 			syslog(LOG_ERR, "SIOCIFCREATE2("
581 			    "mode %u flags 0x%x parent %s bssid %s macaddr %s): %m",
582 			    cp.icp_opmode, cp.icp_flags, parent,
583 			    ether_ntoa_r((void *) cp.icp_bssid, bssid_str),
584 			    ether_ntoa_r((void *) cp.icp_macaddr, macaddr_str));
585 		}
586 		close(s);
587 	} else
588 		syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
589 	return status;
590 }
591 
592 static int
593 wds_vap_destroy(const char *ifname)
594 {
595 	struct ieee80211req ifr;
596 	int s, status;
597 
598 	s = socket(AF_INET, SOCK_DGRAM, 0);
599 	if (s < 0) {
600 		syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
601 		return -1;
602 	}
603 	memset(&ifr, 0, sizeof(ifr));
604 	strncpy(ifr.i_name, ifname, IFNAMSIZ);
605 	if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) {
606 		syslog(LOG_ERR, "ioctl(SIOCIFDESTROY): %m");
607 		status = -1;
608 	} else
609 		status = 0;
610 	close(s);
611 	return status;
612 }
613