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