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, MERCHANTABILITY
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: src/tools/tools/net80211/wlanwds/wlanwds.c,v 1.3 2009/04/18 16:14:03 sam Exp $
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 "netproto/802_11/ieee80211_ioctl.h"
54 #include "netproto/802_11/ieee80211_dragonfly.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
74 struct wds {
75 struct wds *next;
76 uint8_t bssid[IEEE80211_ADDR_LEN]; /* bssid of associated sta */
77 char ifname[IFNAMSIZ]; /* vap interface name */
78 };
79 static struct wds *wds;
80
81 static const char *script = NULL;
82 static char **ifnets;
83 static int nifnets = 0;
84 static int verbose = 0;
85 static int discover_on_join = 0;
86
87 static void scanforvaps(int s);
88 static void handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen);
89 static void wds_discovery(const char *ifname,
90 const uint8_t bssid[IEEE80211_ADDR_LEN]);
91 static void wds_destroy(const char *ifname);
92 static void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]);
93 static int wds_vap_create(const char *ifname, struct wds *);
94 static int wds_vap_destroy(const char *ifname);
95
96 static void
usage(const char * progname)97 usage(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
104 int
main(int argc,char * argv[])105 main(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
168 static const char *
ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN])169 ether_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 */
181 static int
getparent(const char * ifname,char parent[IFNAMSIZ+1])182 getparent(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 */
201 static int
checkifnet(const char * ifname,int complain)202 checkifnet(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 */
225 static int
iswdsvap(int s,const char * ifname)226 iswdsvap(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 */
241 static void
getbssid(int s,const char * ifname,char bssid[IEEE80211_ADDR_LEN])242 getbssid(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 */
260 static void
scanforvaps(int s)261 scanforvaps(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 */
297 static void
handle_rtmsg(struct rt_msghdr * rtm,ssize_t msglen)298 handle_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 */
367 static void
wds_discovery(const char * ifname,const uint8_t bssid[IEEE80211_ADDR_LEN])368 wds_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 */
416 static void
wds_destroy(const char * ifname)417 wds_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 */
435 static void
wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN])436 wds_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
452 static int
wds_vap_create(const char * parent,struct wds * p)453 wds_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
486 static int
wds_vap_destroy(const char * ifname)487 wds_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