xref: /freebsd/tools/tools/net80211/wlanwds/wlanwds.c (revision 99429157e8615dc3b7f11afbe3ed92de7476a5db)
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 <ctype.h>
59 #include <err.h>
60 #include <errno.h>
61 #include <paths.h>
62 #include <stdarg.h>
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <sysexits.h>
67 #include <syslog.h>
68 #include <unistd.h>
69 #include <ifaddrs.h>
70 
71 #define	IEEE80211_ADDR_EQ(a1,a2)	(memcmp(a1,a2,IEEE80211_ADDR_LEN) == 0)
72 #define	IEEE80211_ADDR_COPY(dst,src)	memcpy(dst,src,IEEE80211_ADDR_LEN)
73 
74 struct wds {
75 	struct wds *next;
76 	uint8_t	bssid[IEEE80211_ADDR_LEN];	/* bssid of associated sta */
77 	char	ifname[IFNAMSIZ];		/* vap interface name */
78 };
79 static struct wds *wds;
80 
81 static	const char *script = NULL;
82 static	char **ifnets;
83 static	int nifnets = 0;
84 static	int verbose = 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, struct wds *);
94 static	int wds_vap_destroy(const char *ifname);
95 
96 static void
97 usage(const char *progname)
98 {
99 	fprintf(stderr, "usage: %s [-efjtv] [-P pidfile] [-s <set_scriptname>] [ifnet0 ... | any]\n",
100 		progname);
101 	exit(-1);
102 }
103 
104 int
105 main(int argc, char *argv[])
106 {
107 	const char *progname = argv[0];
108 	const char *pidfile = NULL;
109 	int s, c, logmask, bg = 1;
110 	char msg[2048];
111 	int log_stderr = 0;
112 
113 	logmask = LOG_UPTO(LOG_INFO);
114 	while ((c = getopt(argc, argv, "efjP:s:tv")) != -1)
115 		switch (c) {
116 		case 'e':
117 			log_stderr = LOG_PERROR;
118 			break;
119 		case 'f':
120 			bg = 0;
121 			break;
122 		case 'j':
123 			discover_on_join = 1;
124 			break;
125 		case 'P':
126 			pidfile = optarg;
127 			break;
128 		case 's':
129 			script = optarg;
130 			break;
131 		case 't':
132 			logmask = LOG_UPTO(LOG_ERR);
133 			break;
134 		case 'v':
135 			logmask = LOG_UPTO(LOG_DEBUG);
136 			break;
137 		case '?':
138 			usage(progname);
139 			/*NOTREACHED*/
140 		}
141 	argc -= optind, argv += optind;
142 	if (argc == 0) {
143 		fprintf(stderr, "%s: no ifnet's specified to monitor\n",
144 		    progname);
145 		usage(progname);
146 	}
147 	ifnets = argv;
148 	nifnets = argc;
149 
150 	s = socket(PF_ROUTE, SOCK_RAW, 0);
151 	if (s < 0)
152 		err(EX_OSERR, "socket");
153 	/*
154 	 * Scan for inherited state.
155 	 */
156 	scanforvaps(s);
157 
158 	/* XXX what directory to work in? */
159 	if (bg && daemon(0, 0) < 0)
160 		err(EX_OSERR, "daemon");
161 
162 	openlog("wlanwds", log_stderr | LOG_PID | LOG_CONS, LOG_DAEMON);
163 	setlogmask(logmask);
164 
165 	for (;;) {
166 		ssize_t n = read(s, msg, sizeof(msg));
167 		handle_rtmsg((struct rt_msghdr *)msg, n);
168 	}
169 	return 0;
170 }
171 
172 static const char *
173 ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN])
174 {
175 	static char buf[32];
176 
177 	snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
178 		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
179 	return buf;
180 }
181 
182 /*
183  * Fetch a vap's parent ifnet name.
184  */
185 static int
186 getparent(const char *ifname, char parent[IFNAMSIZ+1])
187 {
188 	char oid[256];
189 	size_t parentlen;
190 
191 	/* fetch parent interface name */
192 	snprintf(oid, sizeof(oid), "net.wlan.%s.%%parent", ifname+4);
193 	parentlen = IFNAMSIZ;
194 	if (sysctlbyname(oid, parent, &parentlen, NULL, 0) < 0)
195 		return -1;
196 	parent[parentlen] = '\0';
197 	return 0;
198 }
199 
200 /*
201  * Check if the specified ifnet is one we're supposed to monitor.
202  * The ifnet is assumed to be a vap; we find it's parent and check
203  * it against the set of ifnet's specified on the command line.
204  */
205 static int
206 checkifnet(const char *ifname, int complain)
207 {
208 	char parent[256];
209 	int i;
210 
211 	if (getparent(ifname, parent) < 0) {
212 		if (complain)
213 			syslog(LOG_ERR,
214 			   "%s: no pointer to parent interface: %m", ifname);
215 		return 0;
216 	}
217 
218 	for (i = 0; i < nifnets; i++)
219 		if (strcasecmp(ifnets[i], "any") == 0 ||
220 		    strcmp(ifnets[i], parent) == 0)
221 			return 1;
222 	syslog(LOG_DEBUG, "%s: parent %s not being monitored", ifname, parent);
223 	return 0;
224 }
225 
226 /*
227  * Return 1 if the specified ifnet is a WDS vap.
228  */
229 static int
230 iswdsvap(int s, const char *ifname)
231 {
232 	struct ifmediareq ifmr;
233 
234 	memset(&ifmr, 0, sizeof(ifmr));
235 	strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
236 	if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
237 		err(-1, "%s: cannot get media", ifname);
238 	return (ifmr.ifm_current & IFM_IEEE80211_WDS) != 0;
239 }
240 
241 /*
242  * Fetch the bssid for an ifnet.  The caller is assumed
243  * to have already verified this is possible.
244  */
245 static void
246 getbssid(int s, const char *ifname, uint8_t bssid[IEEE80211_ADDR_LEN])
247 {
248 	struct ieee80211req ireq;
249 
250 	memset(&ireq, 0, sizeof(ireq));
251 	strncpy(ireq.i_name, ifname, sizeof(ireq.i_name));
252 	ireq.i_type = IEEE80211_IOC_BSSID;
253 	ireq.i_data = bssid;
254 	ireq.i_len = IEEE80211_ADDR_LEN;
255 	if (ioctl(s, SIOCG80211, &ireq) < 0)
256 		err(-1, "%s: cannot fetch bssid", ifname);
257 }
258 
259 /*
260  * Scan the system for WDS vaps associated with the ifnet's we're
261  * supposed to monitor.  Any vaps are added to our internal table
262  * so we can find them (and destroy them) on station leave.
263  */
264 static void
265 scanforvaps(int s)
266 {
267 	char ifname[IFNAMSIZ+1];
268 	uint8_t bssid[IEEE80211_ADDR_LEN];
269 	int i;
270 
271 	/* XXX brutal; should just walk sysctl tree */
272 	for (i = 0; i < 128; i++) {
273 		snprintf(ifname, sizeof(ifname), "wlan%d", i);
274 		if (checkifnet(ifname, 0) && iswdsvap(s, ifname)) {
275 			struct wds *p = malloc(sizeof(struct wds));
276 			if (p == NULL)
277 				err(-1, "%s: malloc failed", __func__);
278 			strlcpy(p->ifname, ifname, IFNAMSIZ);
279 			getbssid(s, ifname, p->bssid);
280 			p->next = wds;
281 			wds = p;
282 
283 			syslog(LOG_INFO, "[%s] discover wds vap %s",
284 			    ether_sprintf(bssid), ifname);
285 		}
286 	}
287 }
288 
289 /*
290  * Process a routing socket message.  We handle messages related
291  * to dynamic WDS:
292  * o on WDS discovery (rx of a 4-address frame with DWDS enabled)
293  *   we create a WDS vap for the specified mac address
294  * o on station leave we destroy any associated WDS vap
295  * o on ifnet destroy we update state if this is manual destroy of
296  *   a WDS vap in our table
297  * o if the -j option is supplied on the command line we create
298  *   WDS vaps on station join/rejoin, this is useful for some setups
299  *   where a WDS vap is required for 4-address traffic to flow
300  */
301 static void
302 handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen)
303 {
304 	struct if_announcemsghdr *ifan;
305 
306 	if (rtm->rtm_version != RTM_VERSION) {
307 		syslog(LOG_ERR, "routing message version %d not understood",
308 		    rtm->rtm_version);
309 		return;
310 	}
311 	switch (rtm->rtm_type) {
312 	case RTM_IFANNOUNCE:
313 		ifan = (struct if_announcemsghdr *)rtm;
314 		switch (ifan->ifan_what) {
315 		case IFAN_ARRIVAL:
316 			syslog(LOG_DEBUG,
317 			    "RTM_IFANNOUNCE: if# %d, what: arrival",
318 			    ifan->ifan_index);
319 			break;
320 		case IFAN_DEPARTURE:
321 			syslog(LOG_DEBUG,
322 			    "RTM_IFANNOUNCE: if# %d, what: departure",
323 			    ifan->ifan_index);
324 			/* NB: ok to call w/ unmonitored ifnets */
325 			wds_destroy(ifan->ifan_name);
326 			break;
327 		}
328 		break;
329 	case RTM_IEEE80211:
330 #define	V(type)	((struct type *)(&ifan[1]))
331 		ifan = (struct if_announcemsghdr *)rtm;
332 		switch (ifan->ifan_what) {
333 		case RTM_IEEE80211_DISASSOC:
334 			if (!discover_on_join)
335 				break;
336 			/* fall thru... */
337 		case RTM_IEEE80211_LEAVE:
338 			if (!checkifnet(ifan->ifan_name, 1))
339 				break;
340 			syslog(LOG_INFO, "[%s] station leave",
341 			    ether_sprintf(V(ieee80211_leave_event)->iev_addr));
342 			wds_leave(V(ieee80211_leave_event)->iev_addr);
343 			break;
344 		case RTM_IEEE80211_JOIN:
345 		case RTM_IEEE80211_REJOIN:
346 		case RTM_IEEE80211_ASSOC:
347 		case RTM_IEEE80211_REASSOC:
348 			if (!discover_on_join)
349 				break;
350 			/* fall thru... */
351 		case RTM_IEEE80211_WDS:
352 			syslog(LOG_INFO, "[%s] wds discovery",
353 			    ether_sprintf(V(ieee80211_wds_event)->iev_addr));
354 			if (!checkifnet(ifan->ifan_name, 1))
355 				break;
356 			wds_discovery(ifan->ifan_name,
357 			    V(ieee80211_wds_event)->iev_addr);
358 			break;
359 		}
360 		break;
361 #undef V
362 	}
363 }
364 
365 /*
366  * Handle WDS discovery; create a WDS vap for the specified bssid.
367  * If a vap already exists then do nothing (can happen when a flood
368  * of 4-address frames causes multiple events to be queued before
369  * we create a vap).
370  */
371 static void
372 wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN])
373 {
374 	struct wds *p;
375 	char parent[256];
376 	char cmd[1024];
377 	int status;
378 
379 	for (p = wds; p != NULL; p = p->next)
380 		if (IEEE80211_ADDR_EQ(p->bssid, bssid)) {
381 			syslog(LOG_INFO, "[%s] wds vap already created (%s)",
382 			    ether_sprintf(bssid), ifname);
383 			return;
384 		}
385 	if (getparent(ifname, parent) < 0) {
386 		syslog(LOG_ERR, "%s: no pointer to parent interface: %m",
387 		    ifname);
388 		return;
389 	}
390 
391 	p = malloc(sizeof(struct wds));
392 	if (p == NULL) {
393 		syslog(LOG_ERR, "%s: malloc failed: %m", __func__);
394 		return;
395 	}
396 	IEEE80211_ADDR_COPY(p->bssid, bssid);
397 	if (wds_vap_create(parent, p) < 0) {
398 		free(p);
399 		return;
400 	}
401 	/*
402 	 * Add to table and launch setup script.
403 	 */
404 	p->next = wds;
405 	wds = p;
406 	syslog(LOG_INFO, "[%s] create wds vap %s", ether_sprintf(bssid),
407 	    p->ifname);
408 	if (script != NULL) {
409 		snprintf(cmd, sizeof(cmd), "%s %s", script, p->ifname);
410 		status = system(cmd);
411 		if (status)
412 			syslog(LOG_ERR, "vap setup script %s exited with "
413 			    "status %d", script, status);
414 	}
415 }
416 
417 /*
418  * Destroy a WDS vap (if known).
419  */
420 static void
421 wds_destroy(const char *ifname)
422 {
423 	struct wds *p, **pp;
424 
425 	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
426 		if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0)
427 			break;
428 	if (p != NULL) {
429 		*pp = p->next;
430 		/* NB: vap already destroyed */
431 		free(p);
432 		return;
433 	}
434 }
435 
436 /*
437  * Handle a station leave event; destroy any associated WDS vap.
438  */
439 static void
440 wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN])
441 {
442 	struct wds *p, **pp;
443 
444 	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
445 		if (IEEE80211_ADDR_EQ(p->bssid, bssid))
446 			break;
447 	if (p != NULL) {
448 		*pp = p->next;
449 		if (wds_vap_destroy(p->ifname) >= 0)
450 			syslog(LOG_INFO, "[%s] wds vap %s destroyed",
451 			    ether_sprintf(bssid), p->ifname);
452 		free(p);
453 	}
454 }
455 
456 static int
457 wds_vap_create(const char *parent, struct wds *p)
458 {
459 	struct ieee80211_clone_params cp;
460 	struct ifreq ifr;
461 	int s, status;
462 
463 	memset(&cp, 0, sizeof(cp));
464 	strncpy(cp.icp_parent, parent, IFNAMSIZ);
465 	cp.icp_opmode = IEEE80211_M_WDS;
466 	IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid);
467 
468 	memset(&ifr, 0, sizeof(ifr));
469 	strncpy(ifr.ifr_name, "wlan", IFNAMSIZ);
470 	ifr.ifr_data = (void *) &cp;
471 
472 	status = -1;
473 	s = socket(AF_INET, SOCK_DGRAM, 0);
474 	if (s >= 0) {
475 		if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) {
476 			strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ);
477 			status = 0;
478 		} else {
479 			syslog(LOG_ERR, "SIOCIFCREATE2("
480 			    "mode %u flags 0x%x parent %s bssid %s): %m",
481 			    cp.icp_opmode, cp.icp_flags, parent,
482 			    ether_sprintf(cp.icp_bssid));
483 		}
484 		close(s);
485 	} else
486 		syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
487 	return status;
488 }
489 
490 static int
491 wds_vap_destroy(const char *ifname)
492 {
493 	struct ieee80211req ifr;
494 	int s, status;
495 
496 	s = socket(AF_INET, SOCK_DGRAM, 0);
497 	if (s < 0) {
498 		syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
499 		return -1;
500 	}
501 	memset(&ifr, 0, sizeof(ifr));
502 	strncpy(ifr.i_name, ifname, IFNAMSIZ);
503 	if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) {
504 		syslog(LOG_ERR, "ioctl(SIOCIFDESTROY): %m");
505 		status = -1;
506 	} else
507 		status = 0;
508 	close(s);
509 	return status;
510 }
511