xref: /freebsd/tools/tools/net80211/wlanwds/wlanwds.c (revision 9768746b)
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 discover_on_join = 0;
88 
89 static	void scanforvaps(int s);
90 static	void handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen);
91 static	void wds_discovery(const char *ifname,
92 		const uint8_t bssid[IEEE80211_ADDR_LEN]);
93 static	void wds_destroy(const char *ifname);
94 static	void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]);
95 static	int wds_vap_create(const char *ifname, uint8_t macaddr[ETHER_ADDR_LEN],
96 	    struct wds *);
97 static	int wds_vap_destroy(const char *ifname);
98 
99 static void
100 usage(const char *progname)
101 {
102 	fprintf(stderr, "usage: %s [-efjtv] [-P pidfile] [-s <set_scriptname>] [ifnet0 ... | any]\n",
103 		progname);
104 	exit(-1);
105 }
106 
107 int
108 main(int argc, char *argv[])
109 {
110 	const char *progname = argv[0];
111 	const char *pidfile = NULL;
112 	int s, c, logmask, bg = 1;
113 	char msg[2048];
114 	int log_stderr = 0;
115 
116 	logmask = LOG_UPTO(LOG_INFO);
117 	while ((c = getopt(argc, argv, "efjP:s:tv")) != -1)
118 		switch (c) {
119 		case 'e':
120 			log_stderr = LOG_PERROR;
121 			break;
122 		case 'f':
123 			bg = 0;
124 			break;
125 		case 'j':
126 			discover_on_join = 1;
127 			break;
128 		case 'P':
129 			pidfile = optarg;
130 			break;
131 		case 's':
132 			script = optarg;
133 			break;
134 		case 't':
135 			logmask = LOG_UPTO(LOG_ERR);
136 			break;
137 		case 'v':
138 			logmask = LOG_UPTO(LOG_DEBUG);
139 			break;
140 		case '?':
141 			usage(progname);
142 			/*NOTREACHED*/
143 		}
144 	argc -= optind, argv += optind;
145 	if (argc == 0) {
146 		fprintf(stderr, "%s: no ifnet's specified to monitor\n",
147 		    progname);
148 		usage(progname);
149 	}
150 	ifnets = argv;
151 	nifnets = argc;
152 
153 	s = socket(PF_ROUTE, SOCK_RAW, 0);
154 	if (s < 0)
155 		err(EX_OSERR, "socket");
156 	/*
157 	 * Scan for inherited state.
158 	 */
159 	scanforvaps(s);
160 
161 	/* XXX what directory to work in? */
162 	if (bg && daemon(0, 0) < 0)
163 		err(EX_OSERR, "daemon");
164 
165 	openlog("wlanwds", log_stderr | LOG_PID | LOG_CONS, LOG_DAEMON);
166 	setlogmask(logmask);
167 
168 	for (;;) {
169 		ssize_t n = read(s, msg, sizeof(msg));
170 		handle_rtmsg((struct rt_msghdr *)msg, n);
171 	}
172 	return 0;
173 }
174 
175 static const char *
176 ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN])
177 {
178 	static char buf[32];
179 
180 	snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
181 		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
182 	return buf;
183 }
184 
185 /*
186  * Fetch a vap's parent ifnet name.
187  */
188 static int
189 getparent(const char *ifname, char parent[IFNAMSIZ+1])
190 {
191 	char oid[256];
192 	size_t parentlen;
193 
194 	/* fetch parent interface name */
195 	snprintf(oid, sizeof(oid), "net.wlan.%s.%%parent", ifname+4);
196 	parentlen = IFNAMSIZ;
197 	if (sysctlbyname(oid, parent, &parentlen, NULL, 0) < 0)
198 		return -1;
199 	parent[parentlen] = '\0';
200 	return 0;
201 }
202 
203 /*
204  * Check if the specified ifnet is one we're supposed to monitor.
205  * The ifnet is assumed to be a vap; we find it's parent and check
206  * it against the set of ifnet's specified on the command line.
207  *
208  * TODO: extend this to also optionally allow the specific DWDS
209  * VAP to be monitored, instead of assuming all VAPs on a parent
210  * physical interface are being monitored by this instance of
211  * wlanwds.
212  */
213 static int
214 checkifnet(const char *ifname, int complain)
215 {
216 	char parent[256];
217 	int i;
218 
219 	if (getparent(ifname, parent) < 0) {
220 		if (complain)
221 			syslog(LOG_ERR,
222 			   "%s: no pointer to parent interface: %m", ifname);
223 		return 0;
224 	}
225 
226 	for (i = 0; i < nifnets; i++)
227 		if (strcasecmp(ifnets[i], "any") == 0 ||
228 		    strcmp(ifnets[i], parent) == 0)
229 			return 1;
230 	syslog(LOG_DEBUG, "%s: parent %s not being monitored", ifname, parent);
231 	return 0;
232 }
233 
234 /*
235  * Return 1 if the specified ifnet is a WDS vap.
236  */
237 static int
238 iswdsvap(int s, const char *ifname)
239 {
240 	struct ifmediareq ifmr;
241 
242 	memset(&ifmr, 0, sizeof(ifmr));
243 	strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
244 	if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
245 		err(-1, "%s: cannot get media", ifname);
246 	return (ifmr.ifm_current & IFM_IEEE80211_WDS) != 0;
247 }
248 
249 /*
250  * Fetch the bssid for an ifnet.  The caller is assumed
251  * to have already verified this is possible.
252  */
253 static void
254 getbssid(int s, const char *ifname, uint8_t bssid[IEEE80211_ADDR_LEN])
255 {
256 	struct ieee80211req ireq;
257 
258 	memset(&ireq, 0, sizeof(ireq));
259 	strncpy(ireq.i_name, ifname, sizeof(ireq.i_name));
260 	ireq.i_type = IEEE80211_IOC_BSSID;
261 	ireq.i_data = bssid;
262 	ireq.i_len = IEEE80211_ADDR_LEN;
263 	if (ioctl(s, SIOCG80211, &ireq) < 0)
264 		err(-1, "%s: cannot fetch bssid", ifname);
265 }
266 
267 /*
268  * Fetch the mac address configured for a given ifnet.
269  * (Note - the current link level address, NOT hwaddr.)
270  *
271  * This is currently, sigh, O(n) because there's no current kernel
272  * API that will do it for a single interface.
273  *
274  * Return 0 if successful, -1 if failure.
275  */
276 static int
277 getlladdr(const char *ifname, uint8_t macaddr[ETHER_ADDR_LEN])
278 {
279 	struct ifaddrs *ifap, *ifa;
280 	struct sockaddr_dl *sdl;
281 
282 	if (getifaddrs(&ifap) < 0) {
283 		warn("%s: getifaddrs", __func__);
284 		return (-1);
285 	}
286 
287 	/* Look for a matching interface */
288 	for (ifa = ifap; ifa != NULL; ifa++) {
289 		if (strcmp(ifname, ifa->ifa_name) != 0)
290 			continue;
291 
292 		/* Found it - check if there's an ifa_addr */
293 		if (ifa->ifa_addr == NULL) {
294 			syslog(LOG_CRIT, "%s: ifname %s; ifa_addr is NULL\n",
295 			    __func__, ifname);
296 			goto err;
297 		}
298 
299 		/* Check address family */
300 		sdl = (struct sockaddr_dl *) ifa->ifa_addr;
301 		if (sdl->sdl_type != IFT_ETHER) {
302 			syslog(LOG_CRIT, "%s: %s: unknown aftype (%d)\n",
303 			    __func__,
304 			    ifname,
305 			    sdl->sdl_type);
306 			goto err;
307 		}
308 		if (sdl->sdl_alen != ETHER_ADDR_LEN) {
309 			syslog(LOG_CRIT, "%s: %s: aflen too short (%d)\n",
310 			    __func__,
311 			    ifname,
312 			    sdl->sdl_alen);
313 			goto err;
314 		}
315 
316 		/* Ok, found it */
317 		memcpy(macaddr, (void *) LLADDR(sdl), ETHER_ADDR_LEN);
318 		goto ok;
319 	}
320 	syslog(LOG_CRIT, "%s: couldn't find ifname %s\n", __func__, ifname);
321 	/* FALLTHROUGH */
322 err:
323 	freeifaddrs(ifap);
324 	return (-1);
325 
326 ok:
327 	freeifaddrs(ifap);
328 	return (0);
329 }
330 
331 /*
332  * Scan the system for WDS vaps associated with the ifnet's we're
333  * supposed to monitor.  Any vaps are added to our internal table
334  * so we can find them (and destroy them) on station leave.
335  */
336 static void
337 scanforvaps(int s)
338 {
339 	char ifname[IFNAMSIZ+1];
340 	uint8_t bssid[IEEE80211_ADDR_LEN];
341 	int i;
342 
343 	/* XXX brutal; should just walk sysctl tree */
344 	for (i = 0; i < 128; i++) {
345 		snprintf(ifname, sizeof(ifname), "wlan%d", i);
346 		if (checkifnet(ifname, 0) && iswdsvap(s, ifname)) {
347 			struct wds *p = malloc(sizeof(struct wds));
348 			if (p == NULL)
349 				err(-1, "%s: malloc failed", __func__);
350 			strlcpy(p->ifname, ifname, IFNAMSIZ);
351 			getbssid(s, ifname, p->bssid);
352 			p->next = wds;
353 			wds = p;
354 
355 			syslog(LOG_INFO, "[%s] discover wds vap %s",
356 			    ether_sprintf(bssid), ifname);
357 		}
358 	}
359 }
360 
361 /*
362  * Process a routing socket message.  We handle messages related
363  * to dynamic WDS:
364  * o on WDS discovery (rx of a 4-address frame with DWDS enabled)
365  *   we create a WDS vap for the specified mac address
366  * o on station leave we destroy any associated WDS vap
367  * o on ifnet destroy we update state if this is manual destroy of
368  *   a WDS vap in our table
369  * o if the -j option is supplied on the command line we create
370  *   WDS vaps on station join/rejoin, this is useful for some setups
371  *   where a WDS vap is required for 4-address traffic to flow
372  */
373 static void
374 handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen)
375 {
376 	struct if_announcemsghdr *ifan;
377 
378 	(void) msglen; /* UNUSED */
379 
380 	if (rtm->rtm_version != RTM_VERSION) {
381 		syslog(LOG_ERR, "routing message version %d not understood",
382 		    rtm->rtm_version);
383 		return;
384 	}
385 	switch (rtm->rtm_type) {
386 	case RTM_IFANNOUNCE:
387 		ifan = (struct if_announcemsghdr *)rtm;
388 		switch (ifan->ifan_what) {
389 		case IFAN_ARRIVAL:
390 			syslog(LOG_DEBUG,
391 			    "RTM_IFANNOUNCE: if# %d, what: arrival",
392 			    ifan->ifan_index);
393 			break;
394 		case IFAN_DEPARTURE:
395 			syslog(LOG_DEBUG,
396 			    "RTM_IFANNOUNCE: if# %d, what: departure",
397 			    ifan->ifan_index);
398 			/* NB: ok to call w/ unmonitored ifnets */
399 			wds_destroy(ifan->ifan_name);
400 			break;
401 		}
402 		break;
403 	case RTM_IEEE80211:
404 #define	V(type)	((struct type *)(&ifan[1]))
405 		ifan = (struct if_announcemsghdr *)rtm;
406 		switch (ifan->ifan_what) {
407 		case RTM_IEEE80211_DISASSOC:
408 			if (!discover_on_join)
409 				break;
410 			/* fall thru... */
411 		case RTM_IEEE80211_LEAVE:
412 			if (!checkifnet(ifan->ifan_name, 1))
413 				break;
414 			syslog(LOG_INFO, "[%s] station leave",
415 			    ether_sprintf(V(ieee80211_leave_event)->iev_addr));
416 			wds_leave(V(ieee80211_leave_event)->iev_addr);
417 			break;
418 		case RTM_IEEE80211_JOIN:
419 		case RTM_IEEE80211_REJOIN:
420 		case RTM_IEEE80211_ASSOC:
421 		case RTM_IEEE80211_REASSOC:
422 			if (!discover_on_join)
423 				break;
424 			/* fall thru... */
425 		case RTM_IEEE80211_WDS:
426 			syslog(LOG_INFO, "[%s] wds discovery",
427 			    ether_sprintf(V(ieee80211_wds_event)->iev_addr));
428 			if (!checkifnet(ifan->ifan_name, 1))
429 				break;
430 			wds_discovery(ifan->ifan_name,
431 			    V(ieee80211_wds_event)->iev_addr);
432 			break;
433 		}
434 		break;
435 #undef V
436 	}
437 }
438 
439 /*
440  * Handle WDS discovery; create a WDS vap for the specified bssid.
441  * If a vap already exists then do nothing (can happen when a flood
442  * of 4-address frames causes multiple events to be queued before
443  * we create a vap).
444  */
445 static void
446 wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN])
447 {
448 	struct wds *p;
449 	char parent[256];
450 	char cmd[1024];
451 	uint8_t macaddr[ETHER_ADDR_LEN];
452 	int status;
453 
454 	for (p = wds; p != NULL; p = p->next)
455 		if (IEEE80211_ADDR_EQ(p->bssid, bssid)) {
456 			syslog(LOG_INFO, "[%s] wds vap already created (%s)",
457 			    ether_sprintf(bssid), ifname);
458 			return;
459 		}
460 	if (getparent(ifname, parent) < 0) {
461 		syslog(LOG_ERR, "%s: no pointer to parent interface: %m",
462 		    ifname);
463 		return;
464 	}
465 
466 	if (getlladdr(ifname, macaddr) < 0) {
467 		syslog(LOG_ERR, "%s: couldn't get lladdr for parent interface: %m",
468 		    ifname);
469 		return;
470 	}
471 
472 	p = malloc(sizeof(struct wds));
473 	if (p == NULL) {
474 		syslog(LOG_ERR, "%s: malloc failed: %m", __func__);
475 		return;
476 	}
477 	IEEE80211_ADDR_COPY(p->bssid, bssid);
478 	if (wds_vap_create(parent, macaddr, p) < 0) {
479 		free(p);
480 		return;
481 	}
482 	/*
483 	 * Add to table and launch setup script.
484 	 */
485 	p->next = wds;
486 	wds = p;
487 	syslog(LOG_INFO, "[%s] create wds vap %s, parent %s (%s)",
488 	    ether_sprintf(bssid),
489 	    p->ifname,
490 	    ifname,
491 	    parent);
492 	if (script != NULL) {
493 		snprintf(cmd, sizeof(cmd), "%s %s", script, p->ifname);
494 		status = system(cmd);
495 		if (status)
496 			syslog(LOG_ERR, "vap setup script %s exited with "
497 			    "status %d", script, status);
498 	}
499 }
500 
501 /*
502  * Destroy a WDS vap (if known).
503  */
504 static void
505 wds_destroy(const char *ifname)
506 {
507 	struct wds *p, **pp;
508 
509 	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
510 		if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0)
511 			break;
512 	if (p != NULL) {
513 		*pp = p->next;
514 		/* NB: vap already destroyed */
515 		free(p);
516 		return;
517 	}
518 }
519 
520 /*
521  * Handle a station leave event; destroy any associated WDS vap.
522  */
523 static void
524 wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN])
525 {
526 	struct wds *p, **pp;
527 
528 	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
529 		if (IEEE80211_ADDR_EQ(p->bssid, bssid))
530 			break;
531 	if (p != NULL) {
532 		*pp = p->next;
533 		if (wds_vap_destroy(p->ifname) >= 0)
534 			syslog(LOG_INFO, "[%s] wds vap %s destroyed",
535 			    ether_sprintf(bssid), p->ifname);
536 		free(p);
537 	}
538 }
539 
540 static int
541 wds_vap_create(const char *parent, uint8_t macaddr[ETHER_ADDR_LEN],
542     struct wds *p)
543 {
544 	struct ieee80211_clone_params cp;
545 	struct ifreq ifr;
546 	int s, status;
547 	char bssid_str[32], macaddr_str[32];
548 
549 	memset(&cp, 0, sizeof(cp));
550 
551 	/* Parent interface */
552 	strncpy(cp.icp_parent, parent, IFNAMSIZ);
553 
554 	/* WDS interface */
555 	cp.icp_opmode = IEEE80211_M_WDS;
556 
557 	/* BSSID for the current node */
558 	IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid);
559 
560 	/*
561 	 * Set the MAC address to match the actual interface
562 	 * that we received the discovery event from.
563 	 * That way we can run WDS on any VAP rather than
564 	 * only the first VAP and then correctly set the
565 	 * MAC address.
566 	 */
567 	cp.icp_flags |= IEEE80211_CLONE_MACADDR;
568 	IEEE80211_ADDR_COPY(cp.icp_macaddr, macaddr);
569 
570 	memset(&ifr, 0, sizeof(ifr));
571 	strncpy(ifr.ifr_name, "wlan", IFNAMSIZ);
572 	ifr.ifr_data = (void *) &cp;
573 
574 	status = -1;
575 	s = socket(AF_INET, SOCK_DGRAM, 0);
576 	if (s >= 0) {
577 		if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) {
578 			strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ);
579 			status = 0;
580 		} else {
581 			syslog(LOG_ERR, "SIOCIFCREATE2("
582 			    "mode %u flags 0x%x parent %s bssid %s macaddr %s): %m",
583 			    cp.icp_opmode, cp.icp_flags, parent,
584 			    ether_ntoa_r((void *) cp.icp_bssid, bssid_str),
585 			    ether_ntoa_r((void *) cp.icp_macaddr, macaddr_str));
586 		}
587 		close(s);
588 	} else
589 		syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
590 	return status;
591 }
592 
593 static int
594 wds_vap_destroy(const char *ifname)
595 {
596 	struct ieee80211req ifr;
597 	int s, status;
598 
599 	s = socket(AF_INET, SOCK_DGRAM, 0);
600 	if (s < 0) {
601 		syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
602 		return -1;
603 	}
604 	memset(&ifr, 0, sizeof(ifr));
605 	strncpy(ifr.i_name, ifname, IFNAMSIZ);
606 	if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) {
607 		syslog(LOG_ERR, "ioctl(SIOCIFDESTROY): %m");
608 		status = -1;
609 	} else
610 		status = 0;
611 	close(s);
612 	return status;
613 }
614