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