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