/*- * Copyright (c) 2006-2009 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer, * without modification. * 2. Redistributions in binary form must reproduce at minimum a disclaimer * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any * redistribution must be conditioned upon including a substantially * similar Disclaimer requirement for further binary redistribution. * * NO WARRANTY * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGES. * * $FreeBSD: src/tools/tools/net80211/wlanwds/wlanwds.c,v 1.3 2009/04/18 16:14:03 sam Exp $ */ /* * Test app to demonstrate how to handle dynamic WDS links: * o monitor 802.11 events for wds discovery events * o create wds vap's in response to wds discovery events * and launch a script to handle adding the vap to the * bridge, etc. * o destroy wds vap's when station leaves */ #include #include #include #include #include #include #include #include "net/if_media.h" #include #include #include #include #include "netproto/802_11/ieee80211_ioctl.h" #include "netproto/802_11/ieee80211_dragonfly.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IEEE80211_ADDR_EQ(a1,a2) (memcmp(a1,a2,IEEE80211_ADDR_LEN) == 0) #define IEEE80211_ADDR_COPY(dst,src) memcpy(dst,src,IEEE80211_ADDR_LEN) struct wds { struct wds *next; uint8_t bssid[IEEE80211_ADDR_LEN]; /* bssid of associated sta */ char ifname[IFNAMSIZ]; /* vap interface name */ }; static struct wds *wds; static const char *script = NULL; static char **ifnets; static int nifnets = 0; static int verbose = 0; static int discover_on_join = 0; static void scanforvaps(int s); static void handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen); static void wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN]); static void wds_destroy(const char *ifname); static void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]); static int wds_vap_create(const char *ifname, struct wds *); static int wds_vap_destroy(const char *ifname); static void usage(const char *progname) { fprintf(stderr, "usage: %s [-fjtv] [-P pidfile] [-s ] [ifnet0 ... | any]\n", progname); exit(-1); } int main(int argc, char *argv[]) { const char *progname = argv[0]; const char *pidfile = NULL; int s, c, logmask, bg = 1; char msg[2048]; logmask = LOG_UPTO(LOG_INFO); while ((c = getopt(argc, argv, "fjP:s:tv")) != -1) switch (c) { case 'f': bg = 0; break; case 'j': discover_on_join = 1; break; case 'P': pidfile = optarg; break; case 's': script = optarg; break; case 't': logmask = LOG_UPTO(LOG_ERR); break; case 'v': logmask = LOG_UPTO(LOG_DEBUG); break; case '?': usage(progname); /*NOTREACHED*/ } argc -= optind, argv += optind; if (argc == 0) { fprintf(stderr, "%s: no ifnet's specified to monitor\n", progname); usage(progname); } ifnets = argv; nifnets = argc; s = socket(PF_ROUTE, SOCK_RAW, 0); if (s < 0) err(EX_OSERR, "socket"); /* * Scan for inherited state. */ scanforvaps(s); /* XXX what directory to work in? */ if (bg && daemon(0, 0) < 0) err(EX_OSERR, "daemon"); openlog("wlanwds", LOG_PID | LOG_CONS, LOG_DAEMON); setlogmask(logmask); for (;;) { ssize_t n = read(s, msg, sizeof(msg)); handle_rtmsg((struct rt_msghdr *)msg, n); } return 0; } static const char * ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN]) { static char buf[32]; snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return buf; } /* * Fetch a vap's parent ifnet name. */ static int getparent(const char *ifname, char parent[IFNAMSIZ+1]) { char oid[256]; int parentlen; /* fetch parent interface name */ snprintf(oid, sizeof(oid), "net.wlan.%s.%%parent", ifname+4); parentlen = IFNAMSIZ; if (sysctlbyname(oid, parent, &parentlen, NULL, 0) < 0) return -1; parent[parentlen] = '\0'; return 0; } /* * Check if the specified ifnet is one we're supposed to monitor. * The ifnet is assumed to be a vap; we find it's parent and check * it against the set of ifnet's specified on the command line. */ static int checkifnet(const char *ifname, int complain) { char parent[256]; int i; if (getparent(ifname, parent) < 0) { if (complain) syslog(LOG_ERR, "%s: no pointer to parent interface: %m", ifname); return 0; } for (i = 0; i < nifnets; i++) if (strcasecmp(ifnets[i], "any") == 0 || strcmp(ifnets[i], parent) == 0) return 1; syslog(LOG_DEBUG, "%s: parent %s not being monitored", ifname, parent); return 0; } /* * Return 1 if the specified ifnet is a WDS vap. */ static int iswdsvap(int s, const char *ifname) { struct ifmediareq ifmr; memset(&ifmr, 0, sizeof(ifmr)); strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name)); if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) err(-1, "%s: cannot get media", ifname); return (ifmr.ifm_current & IFM_IEEE80211_WDS) != 0; } /* * Fetch the bssid for an ifnet. The caller is assumed * to have already verified this is possible. */ static void getbssid(int s, const char *ifname, char bssid[IEEE80211_ADDR_LEN]) { struct ieee80211req ireq; memset(&ireq, 0, sizeof(ireq)); strncpy(ireq.i_name, ifname, sizeof(ireq.i_name)); ireq.i_type = IEEE80211_IOC_BSSID; ireq.i_data = bssid; ireq.i_len = IEEE80211_ADDR_LEN; if (ioctl(s, SIOCG80211, &ireq) < 0) err(-1, "%s: cannot fetch bssid", ifname); } /* * Scan the system for WDS vaps associated with the ifnet's we're * supposed to monitor. Any vaps are added to our internal table * so we can find them (and destroy them) on station leave. */ static void scanforvaps(int s) { char ifname[IFNAMSIZ+1]; char bssid[IEEE80211_ADDR_LEN]; int i; /* XXX brutal; should just walk sysctl tree */ for (i = 0; i < 128; i++) { snprintf(ifname, sizeof(ifname), "wlan%d", i); if (checkifnet(ifname, 0) && iswdsvap(s, ifname)) { struct wds *p = malloc(sizeof(struct wds)); if (p == NULL) err(-1, "%s: malloc failed", __func__); strlcpy(p->ifname, ifname, IFNAMSIZ); getbssid(s, ifname, p->bssid); p->next = wds; wds = p; syslog(LOG_INFO, "[%s] discover wds vap %s", ether_sprintf(bssid), ifname); } } } /* * Process a routing socket message. We handle messages related * to dynamic WDS: * o on WDS discovery (rx of a 4-address frame with DWDS enabled) * we create a WDS vap for the specified mac address * o on station leave we destroy any associated WDS vap * o on ifnet destroy we update state if this is manual destroy of * a WDS vap in our table * o if the -j option is supplied on the command line we create * WDS vaps on station join/rejoin, this is useful for some setups * where a WDS vap is required for 4-address traffic to flow */ static void handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen) { struct if_announcemsghdr *ifan; if (rtm->rtm_version != RTM_VERSION) { syslog(LOG_ERR, "routing message version %d not understood", rtm->rtm_version); return; } switch (rtm->rtm_type) { case RTM_IFANNOUNCE: ifan = (struct if_announcemsghdr *)rtm; switch (ifan->ifan_what) { case IFAN_ARRIVAL: syslog(LOG_DEBUG, "RTM_IFANNOUNCE: if# %d, what: arrival", ifan->ifan_index); break; case IFAN_DEPARTURE: syslog(LOG_DEBUG, "RTM_IFANNOUNCE: if# %d, what: departure", ifan->ifan_index); /* NB: ok to call w/ unmonitored ifnets */ wds_destroy(ifan->ifan_name); break; } break; case RTM_IEEE80211: #define V(type) ((struct type *)(&ifan[1])) ifan = (struct if_announcemsghdr *)rtm; switch (ifan->ifan_what) { case RTM_IEEE80211_DISASSOC: if (!discover_on_join) break; /* fall thru... */ case RTM_IEEE80211_LEAVE: if (!checkifnet(ifan->ifan_name, 1)) break; syslog(LOG_INFO, "[%s] station leave", ether_sprintf(V(ieee80211_leave_event)->iev_addr)); wds_leave(V(ieee80211_leave_event)->iev_addr); break; case RTM_IEEE80211_JOIN: case RTM_IEEE80211_REJOIN: case RTM_IEEE80211_ASSOC: case RTM_IEEE80211_REASSOC: if (!discover_on_join) break; /* fall thru... */ case RTM_IEEE80211_WDS: syslog(LOG_INFO, "[%s] wds discovery", ether_sprintf(V(ieee80211_wds_event)->iev_addr)); if (!checkifnet(ifan->ifan_name, 1)) break; wds_discovery(ifan->ifan_name, V(ieee80211_wds_event)->iev_addr); break; } break; #undef V } } /* * Handle WDS discovery; create a WDS vap for the specified bssid. * If a vap already exists then do nothing (can happen when a flood * of 4-address frames causes multiple events to be queued before * we create a vap). */ static void wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN]) { struct wds *p; char parent[256]; char cmd[1024]; int status; for (p = wds; p != NULL; p = p->next) if (IEEE80211_ADDR_EQ(p->bssid, bssid)) { syslog(LOG_INFO, "[%s] wds vap already created (%s)", ether_sprintf(bssid), ifname); return; } if (getparent(ifname, parent) < 0) { syslog(LOG_ERR, "%s: no pointer to parent interface: %m", ifname); return; } p = malloc(sizeof(struct wds)); if (p == NULL) { syslog(LOG_ERR, "%s: malloc failed: %m", __func__); return; } IEEE80211_ADDR_COPY(p->bssid, bssid); if (wds_vap_create(parent, p) < 0) { free(p); return; } /* * Add to table and launch setup script. */ p->next = wds; wds = p; syslog(LOG_INFO, "[%s] create wds vap %s", ether_sprintf(bssid), p->ifname); if (script != NULL) { snprintf(cmd, sizeof(cmd), "%s %s", script, p->ifname); status = system(cmd); if (status) syslog(LOG_ERR, "vap setup script %s exited with " "status %d", script, status); } } /* * Destroy a WDS vap (if known). */ static void wds_destroy(const char *ifname) { struct wds *p, **pp; for (pp = &wds; (p = *pp) != NULL; pp = &p->next) if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0) break; if (p != NULL) { *pp = p->next; /* NB: vap already destroyed */ free(p); return; } } /* * Handle a station leave event; destroy any associated WDS vap. */ static void wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]) { struct wds *p, **pp; for (pp = &wds; (p = *pp) != NULL; pp = &p->next) if (IEEE80211_ADDR_EQ(p->bssid, bssid)) break; if (p != NULL) { *pp = p->next; if (wds_vap_destroy(p->ifname) >= 0) syslog(LOG_INFO, "[%s] wds vap %s destroyed", ether_sprintf(bssid), p->ifname); free(p); } } static int wds_vap_create(const char *parent, struct wds *p) { struct ieee80211_clone_params cp; struct ifreq ifr; int s, status; memset(&cp, 0, sizeof(cp)); strncpy(cp.icp_parent, parent, IFNAMSIZ); cp.icp_opmode = IEEE80211_M_WDS; IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid); memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, "wlan", IFNAMSIZ); ifr.ifr_data = (void *) &cp; status = -1; s = socket(AF_INET, SOCK_DGRAM, 0); if (s >= 0) { if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) { strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ); status = 0; } else { syslog(LOG_ERR, "SIOCIFCREATE2(" "mode %u flags 0x%x parent %s bssid %s): %m", cp.icp_opmode, cp.icp_flags, parent, ether_sprintf(cp.icp_bssid)); } close(s); } else syslog(LOG_ERR, "socket(SOCK_DRAGM): %m"); return status; } static int wds_vap_destroy(const char *ifname) { struct ieee80211req ifr; int s, status; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { syslog(LOG_ERR, "socket(SOCK_DRAGM): %m"); return -1; } memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.i_name, ifname, IFNAMSIZ); if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) { syslog(LOG_ERR, "ioctl(SIOCIFDESTROY): %m"); status = -1; } else status = 0; close(s); return status; }