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$ 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 "net80211/ieee80211_ioctl.h" 54 #include "net80211/ieee80211_freebsd.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 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 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 * 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 182 getparent(const char *ifname, char parent[IFNAMSIZ+1]) 183 { 184 char oid[256]; 185 size_t 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 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 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 242 getbssid(int s, const char *ifname, uint8_t 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 261 scanforvaps(int s) 262 { 263 char ifname[IFNAMSIZ+1]; 264 uint8_t 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 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 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 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 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 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 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