xref: /freebsd/tools/tools/net80211/wlanwds/wlanwds.c (revision 2be1a816b9ff69588e55be0a84cbe2a31efc0f2f)
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$
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 
80 struct wds {
81 	struct wds *next;
82 	uint8_t	bssid[IEEE80211_ADDR_LEN];	/* bssid of associated sta */
83 	char	ifname[IFNAMSIZ];		/* vap interface name */
84 };
85 static struct wds *wds;
86 
87 static	const char *bridge = "bridge0";
88 static	const char *parent = "mv0";		/* XXX no sysctl to find this */
89 static	const char *script = "/usr/local/bin/wdsup";
90 static	int verbose = 0;
91 
92 static	void handle_rtmsg(struct rt_msghdr *rtm, int msglen);
93 static	void wds_discovery(const char *ifname,
94 		const uint8_t bssid[IEEE80211_ADDR_LEN]);
95 static	void wds_destroy(const char *ifname);
96 static	void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]);
97 static	int wds_vap_create(const char *ifname, struct wds *);
98 static	int wds_vap_destroy(const char *ifname);
99 
100 int
101 main(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 
136 static const char *
137 ether_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 
146 static void
147 handle_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 
220 static void
221 wds_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 
262 static void
263 wds_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 
280 static void
281 wds_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 
297 static int
298 wds_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 
331 static int
332 wds_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