wlanwds.c revision 178360
1/*-
2 * Copyright (c) 2006-2007 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: head/tools/tools/net80211/wlanwds/wlanwds.c 178360 2008-04-20 20:43:13Z sam $
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 * Note we query only internal state which means if we don't see
41 * a vap created we won't handle leave/delete properly.  Also there
42 * are several fixed pathnames/strings.  Some require fixing
43 * kernel support (e.g. sysctl to find parent device of a vap).
44 *
45 * Code liberaly swiped from wlanwatch; probably should nuke printfs.
46 */
47#include <sys/param.h>
48#include <sys/file.h>
49#include <sys/socket.h>
50#include <sys/ioctl.h>
51#include <sys/sysctl.h>
52#include <sys/types.h>
53
54#include <net/if.h>
55#include "net/if_media.h"
56#include <net/route.h>
57#include <net/if_dl.h>
58#include <netinet/in.h>
59#include <netinet/if_ether.h>
60#include <netatalk/at.h>
61#include "net80211/ieee80211_ioctl.h"
62#include "net80211/ieee80211_freebsd.h"
63#include <arpa/inet.h>
64#include <netdb.h>
65
66#include <ctype.h>
67#include <err.h>
68#include <errno.h>
69#include <paths.h>
70#include <stdio.h>
71#include <stdlib.h>
72#include <string.h>
73#include <sysexits.h>
74#include <unistd.h>
75#include <ifaddrs.h>
76
77#define	IEEE80211_ADDR_EQ(a1,a2)	(memcmp(a1,a2,IEEE80211_ADDR_LEN) == 0)
78#define	IEEE80211_ADDR_COPY(dst,src)	memcpy(dst,src,IEEE80211_ADDR_LEN)
79
80struct wds {
81	struct wds *next;
82	uint8_t	bssid[IEEE80211_ADDR_LEN];	/* bssid of associated sta */
83	char	ifname[IFNAMSIZ];		/* vap interface name */
84};
85static struct wds *wds;
86
87static	const char *bridge = "bridge0";
88static	const char *parent = "mv0";		/* XXX no sysctl to find this */
89static	const char *script = "/usr/local/bin/wdsup";
90static	int verbose = 0;
91
92static	void handle_rtmsg(struct rt_msghdr *rtm, int msglen);
93static	void wds_discovery(const char *ifname,
94		const uint8_t bssid[IEEE80211_ADDR_LEN]);
95static	void wds_destroy(const char *ifname);
96static	void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]);
97static	int wds_vap_create(const char *ifname, struct wds *);
98static	int wds_vap_destroy(const char *ifname);
99
100int
101main(int argc, char *argv[])
102{
103	int n, s, c;
104	char msg[2048];
105
106	while ((c = getopt(argc, argv, "b:p:s:vn")) != -1)
107		switch (c) {
108		case 'b':
109			bridge = optarg;
110			break;
111		case 'p':
112			parent = optarg;
113			break;
114		case 's':
115			script = optarg;
116			break;
117		case 'v':
118			verbose = 1;
119			break;
120		case '?':
121			errx(1, "usage: %s [-b <bridgename>] [-p <parentname>] [-s <set_scriptname>]\n"
122				" [-v (for verbose)]\n", argv[0]);
123			/*NOTREACHED*/
124		}
125
126	s = socket(PF_ROUTE, SOCK_RAW, 0);
127	if (s < 0)
128		err(EX_OSERR, "socket");
129	for(;;) {
130		n = read(s, msg, 2048);
131		handle_rtmsg((struct rt_msghdr *)msg, n);
132	}
133	return 0;
134}
135
136static const char *
137ether_sprintf(const uint8_t mac[6])
138{
139	static char buf[32];
140
141	snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
142		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
143	return buf;
144}
145
146static void
147handle_rtmsg(struct rt_msghdr *rtm, int msglen)
148{
149	struct if_announcemsghdr *ifan;
150	time_t now = time(NULL);
151	char *cnow = ctime(&now);
152
153	if (rtm->rtm_version != RTM_VERSION) {
154		(void) printf("routing message version %d not understood\n",
155		    rtm->rtm_version);
156		return;
157	}
158	switch (rtm->rtm_type) {
159	case RTM_IFANNOUNCE:
160		ifan = (struct if_announcemsghdr *)rtm;
161		if (!verbose)
162			break;
163		printf("%.19s RTM_IFANNOUNCE: if# %d, what: ",
164			cnow, ifan->ifan_index);
165		switch (ifan->ifan_what) {
166		case IFAN_ARRIVAL:
167			printf("arrival");
168			break;
169		case IFAN_DEPARTURE:
170			printf("departure");
171			wds_destroy(ifan->ifan_name);
172			break;
173		default:
174			printf("#%d", ifan->ifan_what);
175			break;
176		}
177		printf("\n");
178		break;
179	case RTM_IEEE80211:
180#define	V(type)	((struct type *)(&ifan[1]))
181		ifan = (struct if_announcemsghdr *)rtm;
182		switch (ifan->ifan_what) {
183		case RTM_IEEE80211_LEAVE:
184			if (verbose)
185				printf("%.19s %s station leave", cnow,
186				    ether_sprintf(V(ieee80211_leave_event)->iev_addr));
187			wds_leave(V(ieee80211_leave_event)->iev_addr);
188			if (verbose)
189				printf("\n");
190			break;
191		case RTM_IEEE80211_WDS:
192			if (verbose)
193				printf("%.19s %s wds discovery", cnow,
194				    ether_sprintf(V(ieee80211_wds_event)->iev_addr));
195			/* XXX wlan0 */
196			wds_discovery("wlan0", V(ieee80211_wds_event)->iev_addr);
197			if (verbose)
198				printf("\n");
199			break;
200		case RTM_IEEE80211_ASSOC:
201		case RTM_IEEE80211_REASSOC:
202		case RTM_IEEE80211_DISASSOC:
203		case RTM_IEEE80211_JOIN:
204		case RTM_IEEE80211_REJOIN:
205		case RTM_IEEE80211_SCAN:
206		case RTM_IEEE80211_REPLAY:
207		case RTM_IEEE80211_MICHAEL:
208			break;
209		default:
210			if (verbose)
211				printf("%.19s RTM_IEEE80211: if# %d, what: #%d\n", cnow,
212					ifan->ifan_index, ifan->ifan_what);
213			break;
214		}
215		break;
216#undef V
217	}
218}
219
220static void
221wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN])
222{
223	struct wds *p;
224
225	for (p = wds; p != NULL; p = p->next)
226		if (IEEE80211_ADDR_EQ(p->bssid, bssid)) {
227			if (verbose)
228				printf(" (already created)");
229			return;
230		}
231	p = malloc(sizeof(struct wds));
232	if (p == NULL) {
233		warn("%s: malloc", __func__);
234		return;
235	}
236	IEEE80211_ADDR_COPY(p->bssid, bssid);
237	/* XXX mv0: no sysctl to find parent device */
238	if (wds_vap_create(parent, p) >= 0) {
239		char cmd[1024];
240		int status;
241
242		/*
243		 * Add to table.
244		 */
245		p->next = wds;
246		wds = p;
247		if (verbose)
248			printf(" (create %s)", p->ifname);
249		/*
250		 * XXX launch script to setup bridge, etc.
251		 */
252		snprintf(cmd, sizeof(cmd), "%s %s %s",
253			script, p->ifname, bridge);
254		status = system(cmd);
255		if (status)
256			warnx("vap setup script %s exited with status %d\n",
257				script, status);
258	} else
259		free(p);
260}
261
262static void
263wds_destroy(const char *ifname)
264{
265	struct wds *p, **pp;
266
267	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
268		if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0)
269			break;
270	/* XXX check for device directly */
271	if (p == NULL)		/* not ours/known */
272		return;
273	*pp = p->next;
274	if (wds_vap_destroy(p->ifname) >= 0)
275		if (verbose)
276			printf(" (wds vap destroyed)");
277	free(p);
278}
279
280static void
281wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN])
282{
283	struct wds *p, **pp;
284
285	for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
286		if (IEEE80211_ADDR_EQ(p->bssid, bssid))
287			break;
288	/* XXX fall back to check device */
289	if (p == NULL)		/* not ours/known */
290		return;
291	*pp = p->next;
292	if (wds_vap_destroy(p->ifname) >= 0)
293		printf(" (wds vap destroyed)");
294	free(p);
295}
296
297static int
298wds_vap_create(const char *parent, struct wds *p)
299{
300	struct ieee80211_clone_params cp;
301	struct ifreq ifr;
302	int s, status;
303
304	memset(&cp, 0, sizeof(cp));
305	strncpy(cp.icp_parent, parent, IFNAMSIZ);
306	cp.icp_opmode = IEEE80211_M_WDS;
307	IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid);
308
309	memset(&ifr, 0, sizeof(ifr));
310	strncpy(ifr.ifr_name, "wlan", IFNAMSIZ);
311	ifr.ifr_data = (void *) &cp;
312
313	status = -1;
314	s = socket(AF_INET, SOCK_DGRAM, 0);
315	if (s >= 0) {
316		if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) {
317			strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ);
318			status = 0;
319		} else {
320			warn("SIOCIFCREATE2("
321			    "mode %u flags 0x%x parent %s bssid %s)",
322			    cp.icp_opmode, cp.icp_flags, parent,
323			    ether_sprintf(cp.icp_bssid));
324		}
325		close(s);
326	} else
327		warn("socket(SOCK_DRAGM)");
328	return status;
329}
330
331static int
332wds_vap_destroy(const char *ifname)
333{
334	struct ieee80211req ifr;
335	int s, status;
336
337	s = socket(AF_INET, SOCK_DGRAM, 0);
338	if (s < 0) {
339		warn("socket(SOCK_DRAGM)");
340		return -1;
341	}
342	memset(&ifr, 0, sizeof(ifr));
343	strncpy(ifr.i_name, ifname, IFNAMSIZ);
344	if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) {
345		warn("ioctl(SIOCIFDESTROY)");
346		status = -1;
347	} else
348		status = 0;
349	close(s);
350	return status;
351}
352