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 30 /* 31 * Test app to demonstrate how to handle dynamic WDS links: 32 * o monitor 802.11 events for wds discovery events 33 * o create wds vap's in response to wds discovery events 34 * and launch a script to handle adding the vap to the 35 * bridge, etc. 36 * o destroy wds vap's when station leaves 37 */ 38 #include <sys/param.h> 39 #include <sys/file.h> 40 #include <sys/socket.h> 41 #include <sys/ioctl.h> 42 #include <sys/sysctl.h> 43 #include <sys/types.h> 44 45 #include <net/if.h> 46 #include "net/if_media.h" 47 #include <net/route.h> 48 #include <net/if_dl.h> 49 #include <netinet/in.h> 50 #include <netinet/if_ether.h> 51 #include "net80211/ieee80211_ioctl.h" 52 #include "net80211/ieee80211_freebsd.h" 53 #include <arpa/inet.h> 54 #include <netdb.h> 55 56 #include <net/if.h> 57 #include <net/if_types.h> 58 59 #include <ctype.h> 60 #include <err.h> 61 #include <errno.h> 62 #include <paths.h> 63 #include <stdarg.h> 64 #include <stdio.h> 65 #include <stdlib.h> 66 #include <string.h> 67 #include <sysexits.h> 68 #include <syslog.h> 69 #include <unistd.h> 70 #include <ifaddrs.h> 71 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) 74 75 struct wds { 76 struct wds *next; 77 uint8_t bssid[IEEE80211_ADDR_LEN]; /* bssid of associated sta */ 78 char ifname[IFNAMSIZ]; /* vap interface name */ 79 }; 80 static struct wds *wds; 81 82 static const char *script = NULL; 83 static char **ifnets; 84 static int nifnets = 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, uint8_t macaddr[ETHER_ADDR_LEN], 94 struct wds *); 95 static int wds_vap_destroy(const char *ifname); 96 97 static void 98 usage(const char *progname) 99 { 100 fprintf(stderr, "usage: %s [-efjtv] [-P pidfile] [-s <set_scriptname>] [ifnet0 ... | any]\n", 101 progname); 102 exit(-1); 103 } 104 105 int 106 main(int argc, char *argv[]) 107 { 108 const char *progname = argv[0]; 109 const char *pidfile = NULL; 110 int s, c, logmask, bg = 1; 111 char msg[2048]; 112 int log_stderr = 0; 113 114 logmask = LOG_UPTO(LOG_INFO); 115 while ((c = getopt(argc, argv, "efjP:s:tv")) != -1) 116 switch (c) { 117 case 'e': 118 log_stderr = LOG_PERROR; 119 break; 120 case 'f': 121 bg = 0; 122 break; 123 case 'j': 124 discover_on_join = 1; 125 break; 126 case 'P': 127 pidfile = optarg; 128 break; 129 case 's': 130 script = optarg; 131 break; 132 case 't': 133 logmask = LOG_UPTO(LOG_ERR); 134 break; 135 case 'v': 136 logmask = LOG_UPTO(LOG_DEBUG); 137 break; 138 case '?': 139 usage(progname); 140 /*NOTREACHED*/ 141 } 142 argc -= optind, argv += optind; 143 if (argc == 0) { 144 fprintf(stderr, "%s: no ifnet's specified to monitor\n", 145 progname); 146 usage(progname); 147 } 148 ifnets = argv; 149 nifnets = argc; 150 151 s = socket(PF_ROUTE, SOCK_RAW, 0); 152 if (s < 0) 153 err(EX_OSERR, "socket"); 154 /* 155 * Scan for inherited state. 156 */ 157 scanforvaps(s); 158 159 /* XXX what directory to work in? */ 160 if (bg && daemon(0, 0) < 0) 161 err(EX_OSERR, "daemon"); 162 163 openlog("wlanwds", log_stderr | LOG_PID | LOG_CONS, LOG_DAEMON); 164 setlogmask(logmask); 165 166 for (;;) { 167 ssize_t n = read(s, msg, sizeof(msg)); 168 handle_rtmsg((struct rt_msghdr *)msg, n); 169 } 170 return 0; 171 } 172 173 static const char * 174 ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN]) 175 { 176 static char buf[32]; 177 178 snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", 179 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 180 return buf; 181 } 182 183 /* 184 * Fetch a vap's parent ifnet name. 185 */ 186 static int 187 getparent(const char *ifname, char parent[IFNAMSIZ+1]) 188 { 189 char oid[256]; 190 size_t parentlen; 191 192 /* fetch parent interface name */ 193 snprintf(oid, sizeof(oid), "net.wlan.%s.%%parent", ifname+4); 194 parentlen = IFNAMSIZ; 195 if (sysctlbyname(oid, parent, &parentlen, NULL, 0) < 0) 196 return -1; 197 parent[parentlen] = '\0'; 198 return 0; 199 } 200 201 /* 202 * Check if the specified ifnet is one we're supposed to monitor. 203 * The ifnet is assumed to be a vap; we find it's parent and check 204 * it against the set of ifnet's specified on the command line. 205 * 206 * TODO: extend this to also optionally allow the specific DWDS 207 * VAP to be monitored, instead of assuming all VAPs on a parent 208 * physical interface are being monitored by this instance of 209 * wlanwds. 210 */ 211 static int 212 checkifnet(const char *ifname, int complain) 213 { 214 char parent[256]; 215 int i; 216 217 if (getparent(ifname, parent) < 0) { 218 if (complain) 219 syslog(LOG_ERR, 220 "%s: no pointer to parent interface: %m", ifname); 221 return 0; 222 } 223 224 for (i = 0; i < nifnets; i++) 225 if (strcasecmp(ifnets[i], "any") == 0 || 226 strcmp(ifnets[i], parent) == 0) 227 return 1; 228 syslog(LOG_DEBUG, "%s: parent %s not being monitored", ifname, parent); 229 return 0; 230 } 231 232 /* 233 * Return 1 if the specified ifnet is a WDS vap. 234 */ 235 static int 236 iswdsvap(int s, const char *ifname) 237 { 238 struct ifmediareq ifmr; 239 240 memset(&ifmr, 0, sizeof(ifmr)); 241 strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name)); 242 if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) 243 err(-1, "%s: cannot get media", ifname); 244 return (ifmr.ifm_current & IFM_IEEE80211_WDS) != 0; 245 } 246 247 /* 248 * Fetch the bssid for an ifnet. The caller is assumed 249 * to have already verified this is possible. 250 */ 251 static void 252 getbssid(int s, const char *ifname, uint8_t bssid[IEEE80211_ADDR_LEN]) 253 { 254 struct ieee80211req ireq; 255 256 memset(&ireq, 0, sizeof(ireq)); 257 strncpy(ireq.i_name, ifname, sizeof(ireq.i_name)); 258 ireq.i_type = IEEE80211_IOC_BSSID; 259 ireq.i_data = bssid; 260 ireq.i_len = IEEE80211_ADDR_LEN; 261 if (ioctl(s, SIOCG80211, &ireq) < 0) 262 err(-1, "%s: cannot fetch bssid", ifname); 263 } 264 265 /* 266 * Fetch the mac address configured for a given ifnet. 267 * (Note - the current link level address, NOT hwaddr.) 268 * 269 * This is currently, sigh, O(n) because there's no current kernel 270 * API that will do it for a single interface. 271 * 272 * Return 0 if successful, -1 if failure. 273 */ 274 static int 275 getlladdr(const char *ifname, uint8_t macaddr[ETHER_ADDR_LEN]) 276 { 277 struct ifaddrs *ifap, *ifa; 278 struct sockaddr_dl *sdl; 279 280 if (getifaddrs(&ifap) < 0) { 281 warn("%s: getifaddrs", __func__); 282 return (-1); 283 } 284 285 /* Look for a matching interface */ 286 for (ifa = ifap; ifa != NULL; ifa++) { 287 if (strcmp(ifname, ifa->ifa_name) != 0) 288 continue; 289 290 /* Found it - check if there's an ifa_addr */ 291 if (ifa->ifa_addr == NULL) { 292 syslog(LOG_CRIT, "%s: ifname %s; ifa_addr is NULL\n", 293 __func__, ifname); 294 goto err; 295 } 296 297 /* Check address family */ 298 sdl = (struct sockaddr_dl *) ifa->ifa_addr; 299 if (sdl->sdl_type != IFT_ETHER) { 300 syslog(LOG_CRIT, "%s: %s: unknown aftype (%d)\n", 301 __func__, 302 ifname, 303 sdl->sdl_type); 304 goto err; 305 } 306 if (sdl->sdl_alen != ETHER_ADDR_LEN) { 307 syslog(LOG_CRIT, "%s: %s: aflen too short (%d)\n", 308 __func__, 309 ifname, 310 sdl->sdl_alen); 311 goto err; 312 } 313 314 /* Ok, found it */ 315 memcpy(macaddr, (void *) LLADDR(sdl), ETHER_ADDR_LEN); 316 goto ok; 317 } 318 syslog(LOG_CRIT, "%s: couldn't find ifname %s\n", __func__, ifname); 319 /* FALLTHROUGH */ 320 err: 321 freeifaddrs(ifap); 322 return (-1); 323 324 ok: 325 freeifaddrs(ifap); 326 return (0); 327 } 328 329 /* 330 * Scan the system for WDS vaps associated with the ifnet's we're 331 * supposed to monitor. Any vaps are added to our internal table 332 * so we can find them (and destroy them) on station leave. 333 */ 334 static void 335 scanforvaps(int s) 336 { 337 char ifname[IFNAMSIZ+1]; 338 uint8_t bssid[IEEE80211_ADDR_LEN]; 339 int i; 340 341 /* XXX brutal; should just walk sysctl tree */ 342 for (i = 0; i < 128; i++) { 343 snprintf(ifname, sizeof(ifname), "wlan%d", i); 344 if (checkifnet(ifname, 0) && iswdsvap(s, ifname)) { 345 struct wds *p = malloc(sizeof(struct wds)); 346 if (p == NULL) 347 err(-1, "%s: malloc failed", __func__); 348 strlcpy(p->ifname, ifname, IFNAMSIZ); 349 getbssid(s, ifname, p->bssid); 350 p->next = wds; 351 wds = p; 352 353 syslog(LOG_INFO, "[%s] discover wds vap %s", 354 ether_sprintf(bssid), ifname); 355 } 356 } 357 } 358 359 /* 360 * Process a routing socket message. We handle messages related 361 * to dynamic WDS: 362 * o on WDS discovery (rx of a 4-address frame with DWDS enabled) 363 * we create a WDS vap for the specified mac address 364 * o on station leave we destroy any associated WDS vap 365 * o on ifnet destroy we update state if this is manual destroy of 366 * a WDS vap in our table 367 * o if the -j option is supplied on the command line we create 368 * WDS vaps on station join/rejoin, this is useful for some setups 369 * where a WDS vap is required for 4-address traffic to flow 370 */ 371 static void 372 handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen) 373 { 374 struct if_announcemsghdr *ifan; 375 376 (void) msglen; /* UNUSED */ 377 378 if (rtm->rtm_version != RTM_VERSION) { 379 syslog(LOG_ERR, "routing message version %d not understood", 380 rtm->rtm_version); 381 return; 382 } 383 switch (rtm->rtm_type) { 384 case RTM_IFANNOUNCE: 385 ifan = (struct if_announcemsghdr *)rtm; 386 switch (ifan->ifan_what) { 387 case IFAN_ARRIVAL: 388 syslog(LOG_DEBUG, 389 "RTM_IFANNOUNCE: if# %d, what: arrival", 390 ifan->ifan_index); 391 break; 392 case IFAN_DEPARTURE: 393 syslog(LOG_DEBUG, 394 "RTM_IFANNOUNCE: if# %d, what: departure", 395 ifan->ifan_index); 396 /* NB: ok to call w/ unmonitored ifnets */ 397 wds_destroy(ifan->ifan_name); 398 break; 399 } 400 break; 401 case RTM_IEEE80211: 402 #define V(type) ((struct type *)(&ifan[1])) 403 ifan = (struct if_announcemsghdr *)rtm; 404 switch (ifan->ifan_what) { 405 case RTM_IEEE80211_DISASSOC: 406 if (!discover_on_join) 407 break; 408 /* fall thru... */ 409 case RTM_IEEE80211_LEAVE: 410 if (!checkifnet(ifan->ifan_name, 1)) 411 break; 412 syslog(LOG_INFO, "[%s] station leave", 413 ether_sprintf(V(ieee80211_leave_event)->iev_addr)); 414 wds_leave(V(ieee80211_leave_event)->iev_addr); 415 break; 416 case RTM_IEEE80211_JOIN: 417 case RTM_IEEE80211_REJOIN: 418 case RTM_IEEE80211_ASSOC: 419 case RTM_IEEE80211_REASSOC: 420 if (!discover_on_join) 421 break; 422 /* fall thru... */ 423 case RTM_IEEE80211_WDS: 424 syslog(LOG_INFO, "[%s] wds discovery", 425 ether_sprintf(V(ieee80211_wds_event)->iev_addr)); 426 if (!checkifnet(ifan->ifan_name, 1)) 427 break; 428 wds_discovery(ifan->ifan_name, 429 V(ieee80211_wds_event)->iev_addr); 430 break; 431 } 432 break; 433 #undef V 434 } 435 } 436 437 /* 438 * Handle WDS discovery; create a WDS vap for the specified bssid. 439 * If a vap already exists then do nothing (can happen when a flood 440 * of 4-address frames causes multiple events to be queued before 441 * we create a vap). 442 */ 443 static void 444 wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN]) 445 { 446 struct wds *p; 447 char parent[256]; 448 char cmd[1024]; 449 uint8_t macaddr[ETHER_ADDR_LEN]; 450 int status; 451 452 for (p = wds; p != NULL; p = p->next) 453 if (IEEE80211_ADDR_EQ(p->bssid, bssid)) { 454 syslog(LOG_INFO, "[%s] wds vap already created (%s)", 455 ether_sprintf(bssid), ifname); 456 return; 457 } 458 if (getparent(ifname, parent) < 0) { 459 syslog(LOG_ERR, "%s: no pointer to parent interface: %m", 460 ifname); 461 return; 462 } 463 464 if (getlladdr(ifname, macaddr) < 0) { 465 syslog(LOG_ERR, "%s: couldn't get lladdr for parent interface: %m", 466 ifname); 467 return; 468 } 469 470 p = malloc(sizeof(struct wds)); 471 if (p == NULL) { 472 syslog(LOG_ERR, "%s: malloc failed: %m", __func__); 473 return; 474 } 475 IEEE80211_ADDR_COPY(p->bssid, bssid); 476 if (wds_vap_create(parent, macaddr, p) < 0) { 477 free(p); 478 return; 479 } 480 /* 481 * Add to table and launch setup script. 482 */ 483 p->next = wds; 484 wds = p; 485 syslog(LOG_INFO, "[%s] create wds vap %s, parent %s (%s)", 486 ether_sprintf(bssid), 487 p->ifname, 488 ifname, 489 parent); 490 if (script != NULL) { 491 snprintf(cmd, sizeof(cmd), "%s %s", script, p->ifname); 492 status = system(cmd); 493 if (status) 494 syslog(LOG_ERR, "vap setup script %s exited with " 495 "status %d", script, status); 496 } 497 } 498 499 /* 500 * Destroy a WDS vap (if known). 501 */ 502 static void 503 wds_destroy(const char *ifname) 504 { 505 struct wds *p, **pp; 506 507 for (pp = &wds; (p = *pp) != NULL; pp = &p->next) 508 if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0) 509 break; 510 if (p != NULL) { 511 *pp = p->next; 512 /* NB: vap already destroyed */ 513 free(p); 514 return; 515 } 516 } 517 518 /* 519 * Handle a station leave event; destroy any associated WDS vap. 520 */ 521 static void 522 wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]) 523 { 524 struct wds *p, **pp; 525 526 for (pp = &wds; (p = *pp) != NULL; pp = &p->next) 527 if (IEEE80211_ADDR_EQ(p->bssid, bssid)) 528 break; 529 if (p != NULL) { 530 *pp = p->next; 531 if (wds_vap_destroy(p->ifname) >= 0) 532 syslog(LOG_INFO, "[%s] wds vap %s destroyed", 533 ether_sprintf(bssid), p->ifname); 534 free(p); 535 } 536 } 537 538 static int 539 wds_vap_create(const char *parent, uint8_t macaddr[ETHER_ADDR_LEN], 540 struct wds *p) 541 { 542 struct ieee80211_clone_params cp; 543 struct ifreq ifr; 544 int s, status; 545 char bssid_str[32], macaddr_str[32]; 546 547 memset(&cp, 0, sizeof(cp)); 548 549 /* Parent interface */ 550 strncpy(cp.icp_parent, parent, IFNAMSIZ); 551 552 /* WDS interface */ 553 cp.icp_opmode = IEEE80211_M_WDS; 554 555 /* BSSID for the current node */ 556 IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid); 557 558 /* 559 * Set the MAC address to match the actual interface 560 * that we received the discovery event from. 561 * That way we can run WDS on any VAP rather than 562 * only the first VAP and then correctly set the 563 * MAC address. 564 */ 565 cp.icp_flags |= IEEE80211_CLONE_MACADDR; 566 IEEE80211_ADDR_COPY(cp.icp_macaddr, macaddr); 567 568 memset(&ifr, 0, sizeof(ifr)); 569 strncpy(ifr.ifr_name, "wlan", IFNAMSIZ); 570 ifr.ifr_data = (void *) &cp; 571 572 status = -1; 573 s = socket(AF_INET, SOCK_DGRAM, 0); 574 if (s >= 0) { 575 if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) { 576 strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ); 577 status = 0; 578 } else { 579 syslog(LOG_ERR, "SIOCIFCREATE2(" 580 "mode %u flags 0x%x parent %s bssid %s macaddr %s): %m", 581 cp.icp_opmode, cp.icp_flags, parent, 582 ether_ntoa_r((void *) cp.icp_bssid, bssid_str), 583 ether_ntoa_r((void *) cp.icp_macaddr, macaddr_str)); 584 } 585 close(s); 586 } else 587 syslog(LOG_ERR, "socket(SOCK_DRAGM): %m"); 588 return status; 589 } 590 591 static int 592 wds_vap_destroy(const char *ifname) 593 { 594 struct ieee80211req ifr; 595 int s, status; 596 597 s = socket(AF_INET, SOCK_DGRAM, 0); 598 if (s < 0) { 599 syslog(LOG_ERR, "socket(SOCK_DRAGM): %m"); 600 return -1; 601 } 602 memset(&ifr, 0, sizeof(ifr)); 603 strncpy(ifr.i_name, ifname, IFNAMSIZ); 604 if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) { 605 syslog(LOG_ERR, "ioctl(SIOCIFDESTROY): %m"); 606 status = -1; 607 } else 608 status = 0; 609 close(s); 610 return status; 611 } 612