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