2 * Copyright (c) 2006-2009 Sam Leffler, Errno Consulting
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
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
38 * o destroy wds vap's when station leaves
40 #include <sys/param.h>
42 #include <sys/socket.h>
43 #include <sys/ioctl.h>
44 #include <sys/sysctl.h>
45 #include <sys/types.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 <netatalk/at.h>
54 #include "net80211/ieee80211_ioctl.h"
55 #include "net80211/ieee80211_freebsd.h"
56 #include <arpa/inet.h>
72 #define IEEE80211_ADDR_EQ(a1,a2) (memcmp(a1,a2,IEEE80211_ADDR_LEN) == 0)
73 #define IEEE80211_ADDR_COPY(dst,src) memcpy(dst,src,IEEE80211_ADDR_LEN)
77 uint8_t bssid[IEEE80211_ADDR_LEN]; /* bssid of associated sta */
78 char ifname[IFNAMSIZ]; /* vap interface name */
80 static struct wds *wds;
82 static const char *script = NULL;
84 static int nifnets = 0;
85 static int verbose = 0;
86 static int discover_on_join = 0;
88 static void scanforvaps(int s);
89 static void handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen);
90 static void wds_discovery(const char *ifname,
91 const uint8_t bssid[IEEE80211_ADDR_LEN]);
92 static void wds_destroy(const char *ifname);
93 static void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]);
94 static int wds_vap_create(const char *ifname, struct wds *);
95 static int wds_vap_destroy(const char *ifname);
98 usage(const char *progname)
100 fprintf(stderr, "usage: %s [-fjtv] [-P pidfile] [-s <set_scriptname>] [ifnet0 ... | any]\n",
106 main(int argc, char *argv[])
108 const char *progname = argv[0];
109 const char *pidfile = NULL;
110 int s, c, logmask, bg = 1;
113 logmask = LOG_UPTO(LOG_INFO);
114 while ((c = getopt(argc, argv, "fjP:s:tv")) != -1)
120 discover_on_join = 1;
129 logmask = LOG_UPTO(LOG_ERR);
132 logmask = LOG_UPTO(LOG_DEBUG);
138 argc -= optind, argv += optind;
140 fprintf(stderr, "%s: no ifnet's specified to monitor\n",
147 s = socket(PF_ROUTE, SOCK_RAW, 0);
149 err(EX_OSERR, "socket");
151 * Scan for inherited state.
155 /* XXX what directory to work in? */
156 if (bg && daemon(0, 0) < 0)
157 err(EX_OSERR, "daemon");
159 openlog("wlanwds", LOG_PID | LOG_CONS, LOG_DAEMON);
163 ssize_t n = read(s, msg, sizeof(msg));
164 handle_rtmsg((struct rt_msghdr *)msg, n);
170 ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN])
174 snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
175 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
180 * Fetch a vap's parent ifnet name.
183 getparent(const char *ifname, char parent[IFNAMSIZ+1])
188 /* fetch parent interface name */
189 snprintf(oid, sizeof(oid), "net.wlan.%s.%%parent", ifname+4);
190 parentlen = IFNAMSIZ;
191 if (sysctlbyname(oid, parent, &parentlen, NULL, 0) < 0)
193 parent[parentlen] = '\0';
198 * Check if the specified ifnet is one we're supposed to monitor.
199 * The ifnet is assumed to be a vap; we find it's parent and check
200 * it against the set of ifnet's specified on the command line.
203 checkifnet(const char *ifname, int complain)
208 if (getparent(ifname, parent) < 0) {
211 "%s: no pointer to parent interface: %m", ifname);
215 for (i = 0; i < nifnets; i++)
216 if (strcasecmp(ifnets[i], "any") == 0 ||
217 strcmp(ifnets[i], parent) == 0)
219 syslog(LOG_DEBUG, "%s: parent %s not being monitored", ifname, parent);
224 * Return 1 if the specified ifnet is a WDS vap.
227 iswdsvap(int s, const char *ifname)
229 struct ifmediareq ifmr;
231 memset(&ifmr, 0, sizeof(ifmr));
232 strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
233 if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0)
234 err(-1, "%s: cannot get media", ifname);
235 return (ifmr.ifm_current & IFM_IEEE80211_WDS) != 0;
239 * Fetch the bssid for an ifnet. The caller is assumed
240 * to have already verified this is possible.
243 getbssid(int s, const char *ifname, char bssid[IEEE80211_ADDR_LEN])
245 struct ieee80211req ireq;
247 memset(&ireq, 0, sizeof(ireq));
248 strncpy(ireq.i_name, ifname, sizeof(ireq.i_name));
249 ireq.i_type = IEEE80211_IOC_BSSID;
251 ireq.i_len = IEEE80211_ADDR_LEN;
252 if (ioctl(s, SIOCG80211, &ireq) < 0)
253 err(-1, "%s: cannot fetch bssid", ifname);
257 * Scan the system for WDS vaps associated with the ifnet's we're
258 * supposed to monitor. Any vaps are added to our internal table
259 * so we can find them (and destroy them) on station leave.
264 char ifname[IFNAMSIZ+1];
265 char bssid[IEEE80211_ADDR_LEN];
268 /* XXX brutal; should just walk sysctl tree */
269 for (i = 0; i < 128; i++) {
270 snprintf(ifname, sizeof(ifname), "wlan%d", i);
271 if (checkifnet(ifname, 0) && iswdsvap(s, ifname)) {
272 struct wds *p = malloc(sizeof(struct wds));
274 err(-1, "%s: malloc failed", __func__);
275 strlcpy(p->ifname, ifname, IFNAMSIZ);
276 getbssid(s, ifname, p->bssid);
280 syslog(LOG_INFO, "[%s] discover wds vap %s",
281 ether_sprintf(bssid), ifname);
287 * Process a routing socket message. We handle messages related
289 * o on WDS discovery (rx of a 4-address frame with DWDS enabled)
290 * we create a WDS vap for the specified mac address
291 * o on station leave we destroy any associated WDS vap
292 * o on ifnet destroy we update state if this is manual destroy of
293 * a WDS vap in our table
294 * o if the -j option is supplied on the command line we create
295 * WDS vaps on station join/rejoin, this is useful for some setups
296 * where a WDS vap is required for 4-address traffic to flow
299 handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen)
301 struct if_announcemsghdr *ifan;
303 if (rtm->rtm_version != RTM_VERSION) {
304 syslog(LOG_ERR, "routing message version %d not understood",
308 switch (rtm->rtm_type) {
310 ifan = (struct if_announcemsghdr *)rtm;
311 switch (ifan->ifan_what) {
314 "RTM_IFANNOUNCE: if# %d, what: arrival",
319 "RTM_IFANNOUNCE: if# %d, what: departure",
321 /* NB: ok to call w/ unmonitored ifnets */
322 wds_destroy(ifan->ifan_name);
327 #define V(type) ((struct type *)(&ifan[1]))
328 ifan = (struct if_announcemsghdr *)rtm;
329 switch (ifan->ifan_what) {
330 case RTM_IEEE80211_DISASSOC:
331 if (!discover_on_join)
334 case RTM_IEEE80211_LEAVE:
335 if (!checkifnet(ifan->ifan_name, 1))
337 syslog(LOG_INFO, "[%s] station leave",
338 ether_sprintf(V(ieee80211_leave_event)->iev_addr));
339 wds_leave(V(ieee80211_leave_event)->iev_addr);
341 case RTM_IEEE80211_JOIN:
342 case RTM_IEEE80211_REJOIN:
343 case RTM_IEEE80211_ASSOC:
344 case RTM_IEEE80211_REASSOC:
345 if (!discover_on_join)
348 case RTM_IEEE80211_WDS:
349 syslog(LOG_INFO, "[%s] wds discovery",
350 ether_sprintf(V(ieee80211_wds_event)->iev_addr));
351 if (!checkifnet(ifan->ifan_name, 1))
353 wds_discovery(ifan->ifan_name,
354 V(ieee80211_wds_event)->iev_addr);
363 * Handle WDS discovery; create a WDS vap for the specified bssid.
364 * If a vap already exists then do nothing (can happen when a flood
365 * of 4-address frames causes multiple events to be queued before
369 wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN])
376 for (p = wds; p != NULL; p = p->next)
377 if (IEEE80211_ADDR_EQ(p->bssid, bssid)) {
378 syslog(LOG_INFO, "[%s] wds vap already created (%s)",
379 ether_sprintf(bssid), ifname);
382 if (getparent(ifname, parent) < 0) {
383 syslog(LOG_ERR, "%s: no pointer to parent interface: %m",
388 p = malloc(sizeof(struct wds));
390 syslog(LOG_ERR, "%s: malloc failed: %m", __func__);
393 IEEE80211_ADDR_COPY(p->bssid, bssid);
394 if (wds_vap_create(parent, p) < 0) {
399 * Add to table and launch setup script.
403 syslog(LOG_INFO, "[%s] create wds vap %s", ether_sprintf(bssid),
405 if (script != NULL) {
406 snprintf(cmd, sizeof(cmd), "%s %s", script, p->ifname);
407 status = system(cmd);
409 syslog(LOG_ERR, "vap setup script %s exited with "
410 "status %d", script, status);
415 * Destroy a WDS vap (if known).
418 wds_destroy(const char *ifname)
422 for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
423 if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0)
427 /* NB: vap already destroyed */
434 * Handle a station leave event; destroy any associated WDS vap.
437 wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN])
441 for (pp = &wds; (p = *pp) != NULL; pp = &p->next)
442 if (IEEE80211_ADDR_EQ(p->bssid, bssid))
446 if (wds_vap_destroy(p->ifname) >= 0)
447 syslog(LOG_INFO, "[%s] wds vap %s destroyed",
448 ether_sprintf(bssid), p->ifname);
454 wds_vap_create(const char *parent, struct wds *p)
456 struct ieee80211_clone_params cp;
460 memset(&cp, 0, sizeof(cp));
461 strncpy(cp.icp_parent, parent, IFNAMSIZ);
462 cp.icp_opmode = IEEE80211_M_WDS;
463 IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid);
465 memset(&ifr, 0, sizeof(ifr));
466 strncpy(ifr.ifr_name, "wlan", IFNAMSIZ);
467 ifr.ifr_data = (void *) &cp;
470 s = socket(AF_INET, SOCK_DGRAM, 0);
472 if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) {
473 strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ);
476 syslog(LOG_ERR, "SIOCIFCREATE2("
477 "mode %u flags 0x%x parent %s bssid %s): %m",
478 cp.icp_opmode, cp.icp_flags, parent,
479 ether_sprintf(cp.icp_bssid));
483 syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
488 wds_vap_destroy(const char *ifname)
490 struct ieee80211req ifr;
493 s = socket(AF_INET, SOCK_DGRAM, 0);
495 syslog(LOG_ERR, "socket(SOCK_DRAGM): %m");
498 memset(&ifr, 0, sizeof(ifr));
499 strncpy(ifr.i_name, ifname, IFNAMSIZ);
500 if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) {
501 syslog(LOG_ERR, "ioctl(SIOCIFDESTROY): %m");