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