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