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
77struct wds {
78	struct wds *next;
79	uint8_t	bssid[IEEE80211_ADDR_LEN];	/* bssid of associated sta */
80	char	ifname[IFNAMSIZ];		/* vap interface name */
81};
82static struct wds *wds;
83
84static	const char *script = NULL;
85static	char **ifnets;
86static	int nifnets = 0;
87static	int verbose = 0;
88static	int discover_on_join = 0;
89
90static	void scanforvaps(int s);
91static	void handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen);
92static	void wds_discovery(const char *ifname,
93		const uint8_t bssid[IEEE80211_ADDR_LEN]);
94static	void wds_destroy(const char *ifname);
95static	void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]);
96static	int wds_vap_create(const char *ifname, uint8_t macaddr[ETHER_ADDR_LEN],
97	    struct wds *);
98static	int wds_vap_destroy(const char *ifname);
99
100static void
101usage(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
108int
109main(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
176static const char *
177ether_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 */
189static int
190getparent(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 */
214static int
215checkifnet(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 */
238static int
239iswdsvap(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 */
254static void
255getbssid(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 */
277static int
278getlladdr(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 */
323err:
324	freeifaddrs(ifap);
325	return (-1);
326
327ok:
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 */
337static void
338scanforvaps(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 */
374static void
375handle_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 */
444static void
445wds_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 */
503static void
504wds_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 */
522static void
523wds_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
539static int
540wds_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
592static int
593wds_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