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