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