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 [-efjtv] [-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 int log_stderr = 0; 112 113 logmask = LOG_UPTO(LOG_INFO); 114 while ((c = getopt(argc, argv, "efjP:s:tv")) != -1) 115 switch (c) { 116 case 'e': 117 log_stderr = LOG_PERROR; 118 break; 119 case 'f': 120 bg = 0; 121 break; 122 case 'j': 123 discover_on_join = 1; 124 break; 125 case 'P': 126 pidfile = optarg; 127 break; 128 case 's': 129 script = optarg; 130 break; 131 case 't': 132 logmask = LOG_UPTO(LOG_ERR); 133 break; 134 case 'v': 135 logmask = LOG_UPTO(LOG_DEBUG); 136 break; 137 case '?': 138 usage(progname); 139 /*NOTREACHED*/ 140 } 141 argc -= optind, argv += optind; 142 if (argc == 0) { 143 fprintf(stderr, "%s: no ifnet's specified to monitor\n", 144 progname); 145 usage(progname); 146 } 147 ifnets = argv; 148 nifnets = argc; 149 150 s = socket(PF_ROUTE, SOCK_RAW, 0); 151 if (s < 0) 152 err(EX_OSERR, "socket"); 153 /* 154 * Scan for inherited state. 155 */ 156 scanforvaps(s); 157 158 /* XXX what directory to work in? */ 159 if (bg && daemon(0, 0) < 0) 160 err(EX_OSERR, "daemon"); 161 162 openlog("wlanwds", log_stderr | LOG_PID | LOG_CONS, LOG_DAEMON); 163 setlogmask(logmask); 164 165 for (;;) { 166 ssize_t n = read(s, msg, sizeof(msg)); 167 handle_rtmsg((struct rt_msghdr *)msg, n); 168 } 169 return 0; 170 } 171 172 static const char * 173 ether_sprintf(const uint8_t mac[IEEE80211_ADDR_LEN]) 174 { 175 static char buf[32]; 176 177 snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", 178 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 179 return buf; 180 } 181 182 /* 183 * Fetch a vap's parent ifnet name. 184 */ 185 static int 186 getparent(const char *ifname, char parent[IFNAMSIZ+1]) 187 { 188 char oid[256]; 189 size_t parentlen; 190 191 /* fetch parent interface name */ 192 snprintf(oid, sizeof(oid), "net.wlan.%s.%%parent", ifname+4); 193 parentlen = IFNAMSIZ; 194 if (sysctlbyname(oid, parent, &parentlen, NULL, 0) < 0) 195 return -1; 196 parent[parentlen] = '\0'; 197 return 0; 198 } 199 200 /* 201 * Check if the specified ifnet is one we're supposed to monitor. 202 * The ifnet is assumed to be a vap; we find it's parent and check 203 * it against the set of ifnet's specified on the command line. 204 */ 205 static int 206 checkifnet(const char *ifname, int complain) 207 { 208 char parent[256]; 209 int i; 210 211 if (getparent(ifname, parent) < 0) { 212 if (complain) 213 syslog(LOG_ERR, 214 "%s: no pointer to parent interface: %m", ifname); 215 return 0; 216 } 217 218 for (i = 0; i < nifnets; i++) 219 if (strcasecmp(ifnets[i], "any") == 0 || 220 strcmp(ifnets[i], parent) == 0) 221 return 1; 222 syslog(LOG_DEBUG, "%s: parent %s not being monitored", ifname, parent); 223 return 0; 224 } 225 226 /* 227 * Return 1 if the specified ifnet is a WDS vap. 228 */ 229 static int 230 iswdsvap(int s, const char *ifname) 231 { 232 struct ifmediareq ifmr; 233 234 memset(&ifmr, 0, sizeof(ifmr)); 235 strncpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name)); 236 if (ioctl(s, SIOCGIFMEDIA, (caddr_t)&ifmr) < 0) 237 err(-1, "%s: cannot get media", ifname); 238 return (ifmr.ifm_current & IFM_IEEE80211_WDS) != 0; 239 } 240 241 /* 242 * Fetch the bssid for an ifnet. The caller is assumed 243 * to have already verified this is possible. 244 */ 245 static void 246 getbssid(int s, const char *ifname, uint8_t bssid[IEEE80211_ADDR_LEN]) 247 { 248 struct ieee80211req ireq; 249 250 memset(&ireq, 0, sizeof(ireq)); 251 strncpy(ireq.i_name, ifname, sizeof(ireq.i_name)); 252 ireq.i_type = IEEE80211_IOC_BSSID; 253 ireq.i_data = bssid; 254 ireq.i_len = IEEE80211_ADDR_LEN; 255 if (ioctl(s, SIOCG80211, &ireq) < 0) 256 err(-1, "%s: cannot fetch bssid", ifname); 257 } 258 259 /* 260 * Scan the system for WDS vaps associated with the ifnet's we're 261 * supposed to monitor. Any vaps are added to our internal table 262 * so we can find them (and destroy them) on station leave. 263 */ 264 static void 265 scanforvaps(int s) 266 { 267 char ifname[IFNAMSIZ+1]; 268 uint8_t bssid[IEEE80211_ADDR_LEN]; 269 int i; 270 271 /* XXX brutal; should just walk sysctl tree */ 272 for (i = 0; i < 128; i++) { 273 snprintf(ifname, sizeof(ifname), "wlan%d", i); 274 if (checkifnet(ifname, 0) && iswdsvap(s, ifname)) { 275 struct wds *p = malloc(sizeof(struct wds)); 276 if (p == NULL) 277 err(-1, "%s: malloc failed", __func__); 278 strlcpy(p->ifname, ifname, IFNAMSIZ); 279 getbssid(s, ifname, p->bssid); 280 p->next = wds; 281 wds = p; 282 283 syslog(LOG_INFO, "[%s] discover wds vap %s", 284 ether_sprintf(bssid), ifname); 285 } 286 } 287 } 288 289 /* 290 * Process a routing socket message. We handle messages related 291 * to dynamic WDS: 292 * o on WDS discovery (rx of a 4-address frame with DWDS enabled) 293 * we create a WDS vap for the specified mac address 294 * o on station leave we destroy any associated WDS vap 295 * o on ifnet destroy we update state if this is manual destroy of 296 * a WDS vap in our table 297 * o if the -j option is supplied on the command line we create 298 * WDS vaps on station join/rejoin, this is useful for some setups 299 * where a WDS vap is required for 4-address traffic to flow 300 */ 301 static void 302 handle_rtmsg(struct rt_msghdr *rtm, ssize_t msglen) 303 { 304 struct if_announcemsghdr *ifan; 305 306 if (rtm->rtm_version != RTM_VERSION) { 307 syslog(LOG_ERR, "routing message version %d not understood", 308 rtm->rtm_version); 309 return; 310 } 311 switch (rtm->rtm_type) { 312 case RTM_IFANNOUNCE: 313 ifan = (struct if_announcemsghdr *)rtm; 314 switch (ifan->ifan_what) { 315 case IFAN_ARRIVAL: 316 syslog(LOG_DEBUG, 317 "RTM_IFANNOUNCE: if# %d, what: arrival", 318 ifan->ifan_index); 319 break; 320 case IFAN_DEPARTURE: 321 syslog(LOG_DEBUG, 322 "RTM_IFANNOUNCE: if# %d, what: departure", 323 ifan->ifan_index); 324 /* NB: ok to call w/ unmonitored ifnets */ 325 wds_destroy(ifan->ifan_name); 326 break; 327 } 328 break; 329 case RTM_IEEE80211: 330 #define V(type) ((struct type *)(&ifan[1])) 331 ifan = (struct if_announcemsghdr *)rtm; 332 switch (ifan->ifan_what) { 333 case RTM_IEEE80211_DISASSOC: 334 if (!discover_on_join) 335 break; 336 /* fall thru... */ 337 case RTM_IEEE80211_LEAVE: 338 if (!checkifnet(ifan->ifan_name, 1)) 339 break; 340 syslog(LOG_INFO, "[%s] station leave", 341 ether_sprintf(V(ieee80211_leave_event)->iev_addr)); 342 wds_leave(V(ieee80211_leave_event)->iev_addr); 343 break; 344 case RTM_IEEE80211_JOIN: 345 case RTM_IEEE80211_REJOIN: 346 case RTM_IEEE80211_ASSOC: 347 case RTM_IEEE80211_REASSOC: 348 if (!discover_on_join) 349 break; 350 /* fall thru... */ 351 case RTM_IEEE80211_WDS: 352 syslog(LOG_INFO, "[%s] wds discovery", 353 ether_sprintf(V(ieee80211_wds_event)->iev_addr)); 354 if (!checkifnet(ifan->ifan_name, 1)) 355 break; 356 wds_discovery(ifan->ifan_name, 357 V(ieee80211_wds_event)->iev_addr); 358 break; 359 } 360 break; 361 #undef V 362 } 363 } 364 365 /* 366 * Handle WDS discovery; create a WDS vap for the specified bssid. 367 * If a vap already exists then do nothing (can happen when a flood 368 * of 4-address frames causes multiple events to be queued before 369 * we create a vap). 370 */ 371 static void 372 wds_discovery(const char *ifname, const uint8_t bssid[IEEE80211_ADDR_LEN]) 373 { 374 struct wds *p; 375 char parent[256]; 376 char cmd[1024]; 377 int status; 378 379 for (p = wds; p != NULL; p = p->next) 380 if (IEEE80211_ADDR_EQ(p->bssid, bssid)) { 381 syslog(LOG_INFO, "[%s] wds vap already created (%s)", 382 ether_sprintf(bssid), ifname); 383 return; 384 } 385 if (getparent(ifname, parent) < 0) { 386 syslog(LOG_ERR, "%s: no pointer to parent interface: %m", 387 ifname); 388 return; 389 } 390 391 p = malloc(sizeof(struct wds)); 392 if (p == NULL) { 393 syslog(LOG_ERR, "%s: malloc failed: %m", __func__); 394 return; 395 } 396 IEEE80211_ADDR_COPY(p->bssid, bssid); 397 if (wds_vap_create(parent, p) < 0) { 398 free(p); 399 return; 400 } 401 /* 402 * Add to table and launch setup script. 403 */ 404 p->next = wds; 405 wds = p; 406 syslog(LOG_INFO, "[%s] create wds vap %s", ether_sprintf(bssid), 407 p->ifname); 408 if (script != NULL) { 409 snprintf(cmd, sizeof(cmd), "%s %s", script, p->ifname); 410 status = system(cmd); 411 if (status) 412 syslog(LOG_ERR, "vap setup script %s exited with " 413 "status %d", script, status); 414 } 415 } 416 417 /* 418 * Destroy a WDS vap (if known). 419 */ 420 static void 421 wds_destroy(const char *ifname) 422 { 423 struct wds *p, **pp; 424 425 for (pp = &wds; (p = *pp) != NULL; pp = &p->next) 426 if (strncmp(p->ifname, ifname, IFNAMSIZ) == 0) 427 break; 428 if (p != NULL) { 429 *pp = p->next; 430 /* NB: vap already destroyed */ 431 free(p); 432 return; 433 } 434 } 435 436 /* 437 * Handle a station leave event; destroy any associated WDS vap. 438 */ 439 static void 440 wds_leave(const uint8_t bssid[IEEE80211_ADDR_LEN]) 441 { 442 struct wds *p, **pp; 443 444 for (pp = &wds; (p = *pp) != NULL; pp = &p->next) 445 if (IEEE80211_ADDR_EQ(p->bssid, bssid)) 446 break; 447 if (p != NULL) { 448 *pp = p->next; 449 if (wds_vap_destroy(p->ifname) >= 0) 450 syslog(LOG_INFO, "[%s] wds vap %s destroyed", 451 ether_sprintf(bssid), p->ifname); 452 free(p); 453 } 454 } 455 456 static int 457 wds_vap_create(const char *parent, struct wds *p) 458 { 459 struct ieee80211_clone_params cp; 460 struct ifreq ifr; 461 int s, status; 462 463 memset(&cp, 0, sizeof(cp)); 464 strncpy(cp.icp_parent, parent, IFNAMSIZ); 465 cp.icp_opmode = IEEE80211_M_WDS; 466 IEEE80211_ADDR_COPY(cp.icp_bssid, p->bssid); 467 468 memset(&ifr, 0, sizeof(ifr)); 469 strncpy(ifr.ifr_name, "wlan", IFNAMSIZ); 470 ifr.ifr_data = (void *) &cp; 471 472 status = -1; 473 s = socket(AF_INET, SOCK_DGRAM, 0); 474 if (s >= 0) { 475 if (ioctl(s, SIOCIFCREATE2, &ifr) >= 0) { 476 strlcpy(p->ifname, ifr.ifr_name, IFNAMSIZ); 477 status = 0; 478 } else { 479 syslog(LOG_ERR, "SIOCIFCREATE2(" 480 "mode %u flags 0x%x parent %s bssid %s): %m", 481 cp.icp_opmode, cp.icp_flags, parent, 482 ether_sprintf(cp.icp_bssid)); 483 } 484 close(s); 485 } else 486 syslog(LOG_ERR, "socket(SOCK_DRAGM): %m"); 487 return status; 488 } 489 490 static int 491 wds_vap_destroy(const char *ifname) 492 { 493 struct ieee80211req ifr; 494 int s, status; 495 496 s = socket(AF_INET, SOCK_DGRAM, 0); 497 if (s < 0) { 498 syslog(LOG_ERR, "socket(SOCK_DRAGM): %m"); 499 return -1; 500 } 501 memset(&ifr, 0, sizeof(ifr)); 502 strncpy(ifr.i_name, ifname, IFNAMSIZ); 503 if (ioctl(s, SIOCIFDESTROY, &ifr) < 0) { 504 syslog(LOG_ERR, "ioctl(SIOCIFDESTROY): %m"); 505 status = -1; 506 } else 507 status = 0; 508 close(s); 509 return status; 510 } 511