| Commit | Line | Data |
|---|---|---|
| 32176cfd RP |
1 | /*- |
| 2 | * Copyright (c) 2007-2008 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 | * 2. Redistributions in binary form must reproduce the above copyright | |
| 11 | * notice, this list of conditions and the following disclaimer in the | |
| 12 | * documentation and/or other materials provided with the distribution. | |
| 13 | * | |
| 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
| 15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
| 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
| 17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
| 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
| 19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| 20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| 21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
| 23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 24 | * | |
| 25 | * $FreeBSD: head/sys/net80211/ieee80211_sta.c 203422 2010-02-03 10:07:43Z rpaulo $ | |
| 32176cfd RP |
26 | */ |
| 27 | ||
| 28 | /* | |
| 29 | * IEEE 802.11 Station mode support. | |
| 30 | */ | |
| 31 | #include "opt_inet.h" | |
| 32 | #include "opt_wlan.h" | |
| 33 | ||
| 34 | #include <sys/param.h> | |
| 35 | #include <sys/systm.h> | |
| 36 | #include <sys/mbuf.h> | |
| 37 | #include <sys/malloc.h> | |
| 38 | #include <sys/kernel.h> | |
| 39 | ||
| 40 | #include <sys/socket.h> | |
| 41 | #include <sys/sockio.h> | |
| 42 | #include <sys/endian.h> | |
| 43 | #include <sys/errno.h> | |
| 44 | #include <sys/proc.h> | |
| 45 | #include <sys/sysctl.h> | |
| 46 | ||
| 47 | #include <net/if.h> | |
| 48 | #include <net/if_media.h> | |
| 49 | #include <net/if_llc.h> | |
| 50 | #include <net/ethernet.h> | |
| 51 | #include <net/route.h> | |
| 52 | ||
| 53 | #include <net/bpf.h> | |
| 54 | ||
| 55 | #include <netproto/802_11/ieee80211_var.h> | |
| 56 | #include <netproto/802_11/ieee80211_sta.h> | |
| 57 | #include <netproto/802_11/ieee80211_input.h> | |
| 58 | #ifdef IEEE80211_SUPPORT_SUPERG | |
| 59 | #include <netproto/802_11/ieee80211_superg.h> | |
| 60 | #endif | |
| 61 | ||
| 62 | #define IEEE80211_RATE2MBS(r) (((r) & IEEE80211_RATE_VAL) / 2) | |
| 63 | ||
| 64 | static void sta_vattach(struct ieee80211vap *); | |
| 65 | static void sta_beacon_miss(struct ieee80211vap *); | |
| 66 | static int sta_newstate(struct ieee80211vap *, enum ieee80211_state, int); | |
| 67 | static int sta_input(struct ieee80211_node *, struct mbuf *, int, int); | |
| 68 | static void sta_recv_mgmt(struct ieee80211_node *, struct mbuf *, | |
| 69 | int subtype, int rssi, int nf); | |
| 70 | static void sta_recv_ctl(struct ieee80211_node *, struct mbuf *, int subtype); | |
| 71 | ||
| 72 | void | |
| 73 | ieee80211_sta_attach(struct ieee80211com *ic) | |
| 74 | { | |
| 75 | ic->ic_vattach[IEEE80211_M_STA] = sta_vattach; | |
| 76 | } | |
| 77 | ||
| 78 | void | |
| 79 | ieee80211_sta_detach(struct ieee80211com *ic) | |
| 80 | { | |
| 81 | } | |
| 82 | ||
| 83 | static void | |
| 84 | sta_vdetach(struct ieee80211vap *vap) | |
| 85 | { | |
| 86 | } | |
| 87 | ||
| 88 | static void | |
| 89 | sta_vattach(struct ieee80211vap *vap) | |
| 90 | { | |
| 91 | vap->iv_newstate = sta_newstate; | |
| 92 | vap->iv_input = sta_input; | |
| 93 | vap->iv_recv_mgmt = sta_recv_mgmt; | |
| 94 | vap->iv_recv_ctl = sta_recv_ctl; | |
| 95 | vap->iv_opdetach = sta_vdetach; | |
| 96 | vap->iv_bmiss = sta_beacon_miss; | |
| 97 | } | |
| 98 | ||
| 99 | /* | |
| 100 | * Handle a beacon miss event. The common code filters out | |
| 101 | * spurious events that can happen when scanning and/or before | |
| 102 | * reaching RUN state. | |
| 103 | */ | |
| 104 | static void | |
| 105 | sta_beacon_miss(struct ieee80211vap *vap) | |
| 106 | { | |
| 107 | struct ieee80211com *ic = vap->iv_ic; | |
| 108 | ||
| 109 | KASSERT((ic->ic_flags & IEEE80211_F_SCAN) == 0, ("scanning")); | |
| 110 | KASSERT(vap->iv_state >= IEEE80211_S_RUN, | |
| 111 | ("wrong state %s", ieee80211_state_name[vap->iv_state])); | |
| 112 | ||
| 113 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG, | |
| 114 | "beacon miss, mode %s state %s\n", | |
| 115 | ieee80211_opmode_name[vap->iv_opmode], | |
| 116 | ieee80211_state_name[vap->iv_state]); | |
| 117 | ||
| 118 | if (vap->iv_state == IEEE80211_S_CSA) { | |
| 119 | /* | |
| 120 | * A Channel Switch is pending; assume we missed the | |
| 121 | * beacon that would've completed the process and just | |
| 122 | * force the switch. If we made a mistake we'll not | |
| 123 | * find the AP on the new channel and fall back to a | |
| 124 | * normal scan. | |
| 125 | */ | |
| 126 | ieee80211_csa_completeswitch(ic); | |
| 127 | return; | |
| 128 | } | |
| 129 | if (++vap->iv_bmiss_count < vap->iv_bmiss_max) { | |
| 130 | /* | |
| 131 | * Send a directed probe req before falling back to a | |
| 132 | * scan; if we receive a response ic_bmiss_count will | |
| 133 | * be reset. Some cards mistakenly report beacon miss | |
| 134 | * so this avoids the expensive scan if the ap is | |
| 135 | * still there. | |
| 136 | */ | |
| 137 | ieee80211_send_probereq(vap->iv_bss, vap->iv_myaddr, | |
| 138 | vap->iv_bss->ni_bssid, vap->iv_bss->ni_bssid, | |
| 139 | vap->iv_bss->ni_essid, vap->iv_bss->ni_esslen); | |
| 140 | return; | |
| 141 | } | |
| 142 | vap->iv_bmiss_count = 0; | |
| 143 | vap->iv_stats.is_beacon_miss++; | |
| 144 | if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { | |
| 145 | #ifdef IEEE80211_SUPPORT_SUPERG | |
| 146 | struct ieee80211com *ic = vap->iv_ic; | |
| 147 | ||
| 148 | /* | |
| 149 | * If we receive a beacon miss interrupt when using | |
| 150 | * dynamic turbo, attempt to switch modes before | |
| 151 | * reassociating. | |
| 152 | */ | |
| 153 | if (IEEE80211_ATH_CAP(vap, vap->iv_bss, IEEE80211_NODE_TURBOP)) | |
| 154 | ieee80211_dturbo_switch(vap, | |
| 155 | ic->ic_bsschan->ic_flags ^ IEEE80211_CHAN_TURBO); | |
| 156 | #endif | |
| 157 | /* | |
| 158 | * Try to reassociate before scanning for a new ap. | |
| 159 | */ | |
| 160 | ieee80211_new_state(vap, IEEE80211_S_ASSOC, 1); | |
| 161 | } else { | |
| 162 | /* | |
| 163 | * Somebody else is controlling state changes (e.g. | |
| 164 | * a user-mode app) don't do anything that would | |
| 165 | * confuse them; just drop into scan mode so they'll | |
| 166 | * notified of the state change and given control. | |
| 167 | */ | |
| 168 | ieee80211_new_state(vap, IEEE80211_S_SCAN, 0); | |
| 169 | } | |
| 170 | } | |
| 171 | ||
| 172 | /* | |
| 173 | * Handle deauth with reason. We retry only for | |
| 174 | * the cases where we might succeed. Otherwise | |
| 175 | * we downgrade the ap and scan. | |
| 176 | */ | |
| 177 | static void | |
| 178 | sta_authretry(struct ieee80211vap *vap, struct ieee80211_node *ni, int reason) | |
| 179 | { | |
| 180 | switch (reason) { | |
| 181 | case IEEE80211_STATUS_SUCCESS: /* NB: MLME assoc */ | |
| 182 | case IEEE80211_STATUS_TIMEOUT: | |
| 183 | case IEEE80211_REASON_ASSOC_EXPIRE: | |
| 184 | case IEEE80211_REASON_NOT_AUTHED: | |
| 185 | case IEEE80211_REASON_NOT_ASSOCED: | |
| 186 | case IEEE80211_REASON_ASSOC_LEAVE: | |
| 187 | case IEEE80211_REASON_ASSOC_NOT_AUTHED: | |
| 188 | IEEE80211_SEND_MGMT(ni, IEEE80211_FC0_SUBTYPE_AUTH, 1); | |
| 189 | break; | |
| 190 | default: | |
| 191 | ieee80211_scan_assoc_fail(vap, vap->iv_bss->ni_macaddr, reason); | |
| 192 | if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) | |
| 193 | ieee80211_check_scan_current(vap); | |
| 194 | break; | |
| 195 | } | |
| 196 | } | |
| 197 | ||
| 198 | /* | |
| 199 | * IEEE80211_M_STA vap state machine handler. | |
| 200 | * This routine handles the main states in the 802.11 protocol. | |
| 201 | */ | |
| 202 | static int | |
| 203 | sta_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) | |
| 204 | { | |
| 205 | struct ieee80211com *ic = vap->iv_ic; | |
| 206 | struct ieee80211_node *ni; | |
| 207 | enum ieee80211_state ostate; | |
| 208 | ||
| 32176cfd RP |
209 | ostate = vap->iv_state; |
| 210 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (%d)\n", | |
| 211 | __func__, ieee80211_state_name[ostate], | |
| 212 | ieee80211_state_name[nstate], arg); | |
| 213 | vap->iv_state = nstate; /* state transition */ | |
| 214 | callout_stop(&vap->iv_mgtsend); /* XXX callout_drain */ | |
| 215 | if (ostate != IEEE80211_S_SCAN) | |
| 216 | ieee80211_cancel_scan(vap); /* background scan */ | |
| 217 | ni = vap->iv_bss; /* NB: no reference held */ | |
| 218 | if (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS) | |
| 219 | callout_stop(&vap->iv_swbmiss); | |
| 220 | switch (nstate) { | |
| 221 | case IEEE80211_S_INIT: | |
| 222 | switch (ostate) { | |
| 223 | case IEEE80211_S_SLEEP: | |
| 224 | /* XXX wakeup */ | |
| 225 | case IEEE80211_S_RUN: | |
| 226 | IEEE80211_SEND_MGMT(ni, | |
| 227 | IEEE80211_FC0_SUBTYPE_DISASSOC, | |
| 228 | IEEE80211_REASON_ASSOC_LEAVE); | |
| 229 | ieee80211_sta_leave(ni); | |
| 230 | break; | |
| 231 | case IEEE80211_S_ASSOC: | |
| 232 | IEEE80211_SEND_MGMT(ni, | |
| 233 | IEEE80211_FC0_SUBTYPE_DEAUTH, | |
| 234 | IEEE80211_REASON_AUTH_LEAVE); | |
| 235 | break; | |
| 236 | case IEEE80211_S_SCAN: | |
| 237 | ieee80211_cancel_scan(vap); | |
| 238 | break; | |
| 239 | default: | |
| 240 | goto invalid; | |
| 241 | } | |
| 242 | if (ostate != IEEE80211_S_INIT) { | |
| 243 | /* NB: optimize INIT -> INIT case */ | |
| 244 | ieee80211_reset_bss(vap); | |
| 245 | } | |
| 246 | if (vap->iv_auth->ia_detach != NULL) | |
| 247 | vap->iv_auth->ia_detach(vap); | |
| 248 | break; | |
| 249 | case IEEE80211_S_SCAN: | |
| 250 | switch (ostate) { | |
| 251 | case IEEE80211_S_INIT: | |
| 252 | /* | |
| 253 | * Initiate a scan. We can come here as a result | |
| 254 | * of an IEEE80211_IOC_SCAN_REQ too in which case | |
| 255 | * the vap will be marked with IEEE80211_FEXT_SCANREQ | |
| 256 | * and the scan request parameters will be present | |
| 257 | * in iv_scanreq. Otherwise we do the default. | |
| 258 | */ | |
| 259 | if (vap->iv_flags_ext & IEEE80211_FEXT_SCANREQ) { | |
| 260 | ieee80211_check_scan(vap, | |
| 261 | vap->iv_scanreq_flags, | |
| 262 | vap->iv_scanreq_duration, | |
| 263 | vap->iv_scanreq_mindwell, | |
| 264 | vap->iv_scanreq_maxdwell, | |
| 265 | vap->iv_scanreq_nssid, vap->iv_scanreq_ssid); | |
| 266 | vap->iv_flags_ext &= ~IEEE80211_FEXT_SCANREQ; | |
| 267 | } else | |
| 268 | ieee80211_check_scan_current(vap); | |
| 269 | break; | |
| 270 | case IEEE80211_S_SCAN: | |
| 271 | case IEEE80211_S_AUTH: | |
| 272 | case IEEE80211_S_ASSOC: | |
| 273 | /* | |
| 274 | * These can happen either because of a timeout | |
| 275 | * on an assoc/auth response or because of a | |
| 276 | * change in state that requires a reset. For | |
| 277 | * the former we're called with a non-zero arg | |
| 278 | * that is the cause for the failure; pass this | |
| 279 | * to the scan code so it can update state. | |
| 280 | * Otherwise trigger a new scan unless we're in | |
| 281 | * manual roaming mode in which case an application | |
| 282 | * must issue an explicit scan request. | |
| 283 | */ | |
| 284 | if (arg != 0) | |
| 285 | ieee80211_scan_assoc_fail(vap, | |
| 286 | vap->iv_bss->ni_macaddr, arg); | |
| 287 | if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) | |
| 288 | ieee80211_check_scan_current(vap); | |
| 289 | break; | |
| 290 | case IEEE80211_S_RUN: /* beacon miss */ | |
| 291 | /* | |
| 292 | * Beacon miss. Notify user space and if not | |
| 293 | * under control of a user application (roaming | |
| 294 | * manual) kick off a scan to re-connect. | |
| 295 | */ | |
| 296 | ieee80211_sta_leave(ni); | |
| 297 | if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) | |
| 298 | ieee80211_check_scan_current(vap); | |
| 299 | break; | |
| 300 | default: | |
| 301 | goto invalid; | |
| 302 | } | |
| 303 | break; | |
| 304 | case IEEE80211_S_AUTH: | |
| 305 | switch (ostate) { | |
| 306 | case IEEE80211_S_INIT: | |
| 307 | case IEEE80211_S_SCAN: | |
| 308 | IEEE80211_SEND_MGMT(ni, | |
| 309 | IEEE80211_FC0_SUBTYPE_AUTH, 1); | |
| 310 | break; | |
| 311 | case IEEE80211_S_AUTH: | |
| 312 | case IEEE80211_S_ASSOC: | |
| 313 | switch (arg & 0xff) { | |
| 314 | case IEEE80211_FC0_SUBTYPE_AUTH: | |
| 315 | /* ??? */ | |
| 316 | IEEE80211_SEND_MGMT(ni, | |
| 317 | IEEE80211_FC0_SUBTYPE_AUTH, 2); | |
| 318 | break; | |
| 319 | case IEEE80211_FC0_SUBTYPE_DEAUTH: | |
| 320 | sta_authretry(vap, ni, arg>>8); | |
| 321 | break; | |
| 322 | } | |
| 323 | break; | |
| 324 | case IEEE80211_S_RUN: | |
| 325 | switch (arg & 0xff) { | |
| 326 | case IEEE80211_FC0_SUBTYPE_AUTH: | |
| 327 | IEEE80211_SEND_MGMT(ni, | |
| 328 | IEEE80211_FC0_SUBTYPE_AUTH, 2); | |
| 329 | vap->iv_state = ostate; /* stay RUN */ | |
| 330 | break; | |
| 331 | case IEEE80211_FC0_SUBTYPE_DEAUTH: | |
| 332 | ieee80211_sta_leave(ni); | |
| 333 | if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { | |
| 334 | /* try to reauth */ | |
| 335 | IEEE80211_SEND_MGMT(ni, | |
| 336 | IEEE80211_FC0_SUBTYPE_AUTH, 1); | |
| 337 | } | |
| 338 | break; | |
| 339 | } | |
| 340 | break; | |
| 341 | default: | |
| 342 | goto invalid; | |
| 343 | } | |
| 344 | break; | |
| 345 | case IEEE80211_S_ASSOC: | |
| 346 | switch (ostate) { | |
| 347 | case IEEE80211_S_AUTH: | |
| 348 | case IEEE80211_S_ASSOC: | |
| 349 | IEEE80211_SEND_MGMT(ni, | |
| 350 | IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0); | |
| 351 | break; | |
| 352 | case IEEE80211_S_SLEEP: /* cannot happen */ | |
| 353 | case IEEE80211_S_RUN: | |
| 354 | ieee80211_sta_leave(ni); | |
| 355 | if (vap->iv_roaming == IEEE80211_ROAMING_AUTO) { | |
| 356 | IEEE80211_SEND_MGMT(ni, arg ? | |
| 357 | IEEE80211_FC0_SUBTYPE_REASSOC_REQ : | |
| 358 | IEEE80211_FC0_SUBTYPE_ASSOC_REQ, 0); | |
| 359 | } | |
| 360 | break; | |
| 361 | default: | |
| 362 | goto invalid; | |
| 363 | } | |
| 364 | break; | |
| 365 | case IEEE80211_S_RUN: | |
| 366 | if (vap->iv_flags & IEEE80211_F_WPA) { | |
| 367 | /* XXX validate prerequisites */ | |
| 368 | } | |
| 369 | switch (ostate) { | |
| 370 | case IEEE80211_S_RUN: | |
| 371 | case IEEE80211_S_CSA: | |
| 372 | break; | |
| 373 | case IEEE80211_S_AUTH: /* when join is done in fw */ | |
| 374 | case IEEE80211_S_ASSOC: | |
| 375 | #ifdef IEEE80211_DEBUG | |
| 376 | if (ieee80211_msg_debug(vap)) { | |
| 6168f72e | 377 | ieee80211_note(vap, "%s with %6D ssid ", |
| 32176cfd RP |
378 | (vap->iv_opmode == IEEE80211_M_STA ? |
| 379 | "associated" : "synchronized"), | |
| 6168f72e | 380 | ni->ni_bssid, ":"); |
| 32176cfd RP |
381 | ieee80211_print_essid(vap->iv_bss->ni_essid, |
| 382 | ni->ni_esslen); | |
| 383 | /* XXX MCS/HT */ | |
| 6168f72e | 384 | kprintf(" channel %d start %uMb\n", |
| 32176cfd RP |
385 | ieee80211_chan2ieee(ic, ic->ic_curchan), |
| 386 | IEEE80211_RATE2MBS(ni->ni_txrate)); | |
| 387 | } | |
| 388 | #endif | |
| 389 | ieee80211_scan_assoc_success(vap, ni->ni_macaddr); | |
| 390 | ieee80211_notify_node_join(ni, | |
| 391 | arg == IEEE80211_FC0_SUBTYPE_ASSOC_RESP); | |
| 392 | break; | |
| 393 | case IEEE80211_S_SLEEP: | |
| 394 | ieee80211_sta_pwrsave(vap, 0); | |
| 395 | break; | |
| 396 | default: | |
| 397 | goto invalid; | |
| 398 | } | |
| 399 | ieee80211_sync_curchan(ic); | |
| 400 | if (ostate != IEEE80211_S_RUN && | |
| 401 | (vap->iv_flags_ext & IEEE80211_FEXT_SWBMISS)) { | |
| 402 | /* | |
| 403 | * Start s/w beacon miss timer for devices w/o | |
| 404 | * hardware support. We fudge a bit here since | |
| 405 | * we're doing this in software. | |
| 406 | */ | |
| 407 | vap->iv_swbmiss_period = IEEE80211_TU_TO_TICKS( | |
| 408 | 2 * vap->iv_bmissthreshold * ni->ni_intval); | |
| 409 | vap->iv_swbmiss_count = 0; | |
| 410 | callout_reset(&vap->iv_swbmiss, vap->iv_swbmiss_period, | |
| 47156d48 | 411 | ieee80211_swbmiss_callout, vap); |
| 32176cfd RP |
412 | } |
| 413 | /* | |
| 414 | * When 802.1x is not in use mark the port authorized | |
| 415 | * at this point so traffic can flow. | |
| 416 | */ | |
| 417 | if (ni->ni_authmode != IEEE80211_AUTH_8021X) | |
| 418 | ieee80211_node_authorize(ni); | |
| 419 | /* | |
| 420 | * Fake association when joining an existing bss. | |
| 421 | */ | |
| 422 | if (ic->ic_newassoc != NULL) | |
| 423 | ic->ic_newassoc(vap->iv_bss, ostate != IEEE80211_S_RUN); | |
| 424 | break; | |
| 425 | case IEEE80211_S_CSA: | |
| 426 | if (ostate != IEEE80211_S_RUN) | |
| 427 | goto invalid; | |
| 428 | break; | |
| 429 | case IEEE80211_S_SLEEP: | |
| 430 | ieee80211_sta_pwrsave(vap, 1); | |
| 431 | break; | |
| 432 | default: | |
| 433 | invalid: | |
| 434 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, | |
| 435 | "%s: unexpected state transition %s -> %s\n", __func__, | |
| 436 | ieee80211_state_name[ostate], ieee80211_state_name[nstate]); | |
| 437 | break; | |
| 438 | } | |
| 439 | return 0; | |
| 440 | } | |
| 441 | ||
| 442 | /* | |
| 443 | * Return non-zero if the frame is an echo of a multicast | |
| 444 | * frame sent by ourself. The dir is known to be DSTODS. | |
| 445 | */ | |
| 446 | static __inline int | |
| 447 | isdstods_mcastecho(struct ieee80211vap *vap, const struct ieee80211_frame *wh) | |
| 448 | { | |
| 449 | #define QWH4(wh) ((const struct ieee80211_qosframe_addr4 *)wh) | |
| 450 | #define WH4(wh) ((const struct ieee80211_frame_addr4 *)wh) | |
| 451 | const uint8_t *sa; | |
| 452 | ||
| 453 | KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode")); | |
| 454 | ||
| 455 | if (!IEEE80211_IS_MULTICAST(wh->i_addr3)) | |
| 456 | return 0; | |
| 457 | sa = IEEE80211_QOS_HAS_SEQ(wh) ? QWH4(wh)->i_addr4 : WH4(wh)->i_addr4; | |
| 458 | return IEEE80211_ADDR_EQ(sa, vap->iv_myaddr); | |
| 459 | #undef WH4 | |
| 460 | #undef QWH4 | |
| 461 | } | |
| 462 | ||
| 463 | /* | |
| 464 | * Return non-zero if the frame is an echo of a multicast | |
| 465 | * frame sent by ourself. The dir is known to be FROMDS. | |
| 466 | */ | |
| 467 | static __inline int | |
| 468 | isfromds_mcastecho(struct ieee80211vap *vap, const struct ieee80211_frame *wh) | |
| 469 | { | |
| 470 | KASSERT(vap->iv_opmode == IEEE80211_M_STA, ("wrong mode")); | |
| 471 | ||
| 472 | if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) | |
| 473 | return 0; | |
| 474 | return IEEE80211_ADDR_EQ(wh->i_addr3, vap->iv_myaddr); | |
| 475 | } | |
| 476 | ||
| 477 | /* | |
| 478 | * Decide if a received management frame should be | |
| 479 | * printed when debugging is enabled. This filters some | |
| 480 | * of the less interesting frames that come frequently | |
| 481 | * (e.g. beacons). | |
| 482 | */ | |
| 483 | static __inline int | |
| 484 | doprint(struct ieee80211vap *vap, int subtype) | |
| 485 | { | |
| 486 | switch (subtype) { | |
| 487 | case IEEE80211_FC0_SUBTYPE_BEACON: | |
| 488 | return (vap->iv_ic->ic_flags & IEEE80211_F_SCAN); | |
| 489 | case IEEE80211_FC0_SUBTYPE_PROBE_REQ: | |
| 490 | return 0; | |
| 491 | } | |
| 492 | return 1; | |
| 493 | } | |
| 494 | ||
| 495 | /* | |
| 496 | * Process a received frame. The node associated with the sender | |
| 497 | * should be supplied. If nothing was found in the node table then | |
| 498 | * the caller is assumed to supply a reference to iv_bss instead. | |
| 499 | * The RSSI and a timestamp are also supplied. The RSSI data is used | |
| 500 | * during AP scanning to select a AP to associate with; it can have | |
| 501 | * any units so long as values have consistent units and higher values | |
| 502 | * mean ``better signal''. The receive timestamp is currently not used | |
| 503 | * by the 802.11 layer. | |
| 504 | */ | |
| 505 | static int | |
| 506 | sta_input(struct ieee80211_node *ni, struct mbuf *m, int rssi, int nf) | |
| 507 | { | |
| 508 | #define SEQ_LEQ(a,b) ((int)((a)-(b)) <= 0) | |
| 509 | #define HAS_SEQ(type) ((type & 0x4) == 0) | |
| 510 | struct ieee80211vap *vap = ni->ni_vap; | |
| 511 | struct ieee80211com *ic = ni->ni_ic; | |
| 512 | struct ifnet *ifp = vap->iv_ifp; | |
| 513 | struct ieee80211_frame *wh; | |
| 514 | struct ieee80211_key *key; | |
| 515 | struct ether_header *eh; | |
| 516 | int hdrspace, need_tap = 1; /* mbuf need to be tapped. */ | |
| 517 | uint8_t dir, type, subtype, qos; | |
| 518 | uint8_t *bssid; | |
| 519 | uint16_t rxseq; | |
| 520 | ||
| 521 | if (m->m_flags & M_AMPDU_MPDU) { | |
| 522 | /* | |
| 523 | * Fastpath for A-MPDU reorder q resubmission. Frames | |
| 524 | * w/ M_AMPDU_MPDU marked have already passed through | |
| 525 | * here but were received out of order and been held on | |
| 526 | * the reorder queue. When resubmitted they are marked | |
| 527 | * with the M_AMPDU_MPDU flag and we can bypass most of | |
| 528 | * the normal processing. | |
| 529 | */ | |
| 530 | wh = mtod(m, struct ieee80211_frame *); | |
| 531 | type = IEEE80211_FC0_TYPE_DATA; | |
| 532 | dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; | |
| 533 | subtype = IEEE80211_FC0_SUBTYPE_QOS; | |
| 534 | hdrspace = ieee80211_hdrspace(ic, wh); /* XXX optimize? */ | |
| 535 | goto resubmit_ampdu; | |
| 536 | } | |
| 537 | ||
| 538 | KASSERT(ni != NULL, ("null node")); | |
| 539 | ni->ni_inact = ni->ni_inact_reload; | |
| 540 | ||
| 541 | type = -1; /* undefined */ | |
| 542 | ||
| 543 | if (m->m_pkthdr.len < sizeof(struct ieee80211_frame_min)) { | |
| 544 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, | |
| 545 | ni->ni_macaddr, NULL, | |
| 546 | "too short (1): len %u", m->m_pkthdr.len); | |
| 547 | vap->iv_stats.is_rx_tooshort++; | |
| 548 | goto out; | |
| 549 | } | |
| 550 | /* | |
| 551 | * Bit of a cheat here, we use a pointer for a 3-address | |
| 552 | * frame format but don't reference fields past outside | |
| 553 | * ieee80211_frame_min w/o first validating the data is | |
| 554 | * present. | |
| 555 | */ | |
| 556 | wh = mtod(m, struct ieee80211_frame *); | |
| 557 | ||
| 558 | if ((wh->i_fc[0] & IEEE80211_FC0_VERSION_MASK) != | |
| 559 | IEEE80211_FC0_VERSION_0) { | |
| 560 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, | |
| 561 | ni->ni_macaddr, NULL, "wrong version, fc %02x:%02x", | |
| 562 | wh->i_fc[0], wh->i_fc[1]); | |
| 563 | vap->iv_stats.is_rx_badversion++; | |
| 564 | goto err; | |
| 565 | } | |
| 566 | ||
| 567 | dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; | |
| 568 | type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; | |
| 569 | subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; | |
| 570 | if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) { | |
| 571 | bssid = wh->i_addr2; | |
| 572 | if (!IEEE80211_ADDR_EQ(bssid, ni->ni_bssid)) { | |
| 573 | /* not interested in */ | |
| 574 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, | |
| 575 | bssid, NULL, "%s", "not to bss"); | |
| 576 | vap->iv_stats.is_rx_wrongbss++; | |
| 577 | goto out; | |
| 578 | } | |
| 579 | IEEE80211_RSSI_LPF(ni->ni_avgrssi, rssi); | |
| 580 | ni->ni_noise = nf; | |
| 36e4ebd1 | 581 | if (HAS_SEQ(type) && !IEEE80211_IS_MULTICAST(wh->i_addr1)) { |
| 32176cfd RP |
582 | uint8_t tid = ieee80211_gettid(wh); |
| 583 | if (IEEE80211_QOS_HAS_SEQ(wh) && | |
| 584 | TID_TO_WME_AC(tid) >= WME_AC_VI) | |
| 585 | ic->ic_wme.wme_hipri_traffic++; | |
| 586 | rxseq = le16toh(*(uint16_t *)wh->i_seq); | |
| 587 | if ((ni->ni_flags & IEEE80211_NODE_HT) == 0 && | |
| 588 | (wh->i_fc[1] & IEEE80211_FC1_RETRY) && | |
| 589 | SEQ_LEQ(rxseq, ni->ni_rxseqs[tid])) { | |
| 590 | /* duplicate, discard */ | |
| 591 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, | |
| 592 | bssid, "duplicate", | |
| 593 | "seqno <%u,%u> fragno <%u,%u> tid %u", | |
| 594 | rxseq >> IEEE80211_SEQ_SEQ_SHIFT, | |
| 595 | ni->ni_rxseqs[tid] >> | |
| 596 | IEEE80211_SEQ_SEQ_SHIFT, | |
| 597 | rxseq & IEEE80211_SEQ_FRAG_MASK, | |
| 598 | ni->ni_rxseqs[tid] & | |
| 599 | IEEE80211_SEQ_FRAG_MASK, | |
| 600 | tid); | |
| 601 | vap->iv_stats.is_rx_dup++; | |
| 602 | IEEE80211_NODE_STAT(ni, rx_dup); | |
| 603 | goto out; | |
| 604 | } | |
| 605 | ni->ni_rxseqs[tid] = rxseq; | |
| 606 | } | |
| 607 | } | |
| 608 | ||
| 609 | switch (type) { | |
| 610 | case IEEE80211_FC0_TYPE_DATA: | |
| 611 | hdrspace = ieee80211_hdrspace(ic, wh); | |
| 612 | if (m->m_len < hdrspace && | |
| 613 | (m = m_pullup(m, hdrspace)) == NULL) { | |
| 614 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, | |
| 615 | ni->ni_macaddr, NULL, | |
| 616 | "data too short: expecting %u", hdrspace); | |
| 617 | vap->iv_stats.is_rx_tooshort++; | |
| 618 | goto out; /* XXX */ | |
| 619 | } | |
| 620 | /* | |
| 621 | * Handle A-MPDU re-ordering. If the frame is to be | |
| 622 | * processed directly then ieee80211_ampdu_reorder | |
| 623 | * will return 0; otherwise it has consumed the mbuf | |
| 624 | * and we should do nothing more with it. | |
| 625 | */ | |
| 626 | if ((m->m_flags & M_AMPDU) && | |
| 627 | (dir == IEEE80211_FC1_DIR_FROMDS || | |
| 628 | dir == IEEE80211_FC1_DIR_DSTODS) && | |
| 629 | ieee80211_ampdu_reorder(ni, m) != 0) { | |
| 630 | m = NULL; | |
| 631 | goto out; | |
| 632 | } | |
| 633 | resubmit_ampdu: | |
| 634 | if (dir == IEEE80211_FC1_DIR_FROMDS) { | |
| 635 | if ((ifp->if_flags & IFF_SIMPLEX) && | |
| 636 | isfromds_mcastecho(vap, wh)) { | |
| 637 | /* | |
| 638 | * In IEEE802.11 network, multicast | |
| 639 | * packets sent from "me" are broadcast | |
| 640 | * from the AP; silently discard for | |
| 641 | * SIMPLEX interface. | |
| 642 | */ | |
| 643 | IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, | |
| 644 | wh, "data", "%s", "multicast echo"); | |
| 645 | vap->iv_stats.is_rx_mcastecho++; | |
| 646 | goto out; | |
| 647 | } | |
| 648 | if ((vap->iv_flags & IEEE80211_F_DWDS) && | |
| 649 | IEEE80211_IS_MULTICAST(wh->i_addr1)) { | |
| 650 | /* | |
| 651 | * DWDS sta's must drop 3-address mcast frames | |
| 652 | * as they will be sent separately as a 4-addr | |
| 653 | * frame. Accepting the 3-addr frame will | |
| 654 | * confuse the bridge into thinking the sending | |
| 655 | * sta is located at the end of WDS link. | |
| 656 | */ | |
| 657 | IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, | |
| 658 | "3-address data", "%s", "DWDS enabled"); | |
| 659 | vap->iv_stats.is_rx_mcastecho++; | |
| 660 | goto out; | |
| 661 | } | |
| 662 | } else if (dir == IEEE80211_FC1_DIR_DSTODS) { | |
| 663 | if ((vap->iv_flags & IEEE80211_F_DWDS) == 0) { | |
| 664 | IEEE80211_DISCARD(vap, | |
| 665 | IEEE80211_MSG_INPUT, wh, "4-address data", | |
| 666 | "%s", "DWDS not enabled"); | |
| 667 | vap->iv_stats.is_rx_wrongdir++; | |
| 668 | goto out; | |
| 669 | } | |
| 670 | if ((ifp->if_flags & IFF_SIMPLEX) && | |
| 671 | isdstods_mcastecho(vap, wh)) { | |
| 672 | /* | |
| 673 | * In IEEE802.11 network, multicast | |
| 674 | * packets sent from "me" are broadcast | |
| 675 | * from the AP; silently discard for | |
| 676 | * SIMPLEX interface. | |
| 677 | */ | |
| 678 | IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, | |
| 679 | "4-address data", "%s", "multicast echo"); | |
| 680 | vap->iv_stats.is_rx_mcastecho++; | |
| 681 | goto out; | |
| 682 | } | |
| 683 | } else { | |
| 684 | IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, wh, | |
| 685 | "data", "incorrect dir 0x%x", dir); | |
| 686 | vap->iv_stats.is_rx_wrongdir++; | |
| 687 | goto out; | |
| 688 | } | |
| 689 | ||
| 690 | /* | |
| 691 | * Handle privacy requirements. Note that we | |
| 692 | * must not be preempted from here until after | |
| 693 | * we (potentially) call ieee80211_crypto_demic; | |
| 694 | * otherwise we may violate assumptions in the | |
| 695 | * crypto cipher modules used to do delayed update | |
| 696 | * of replay sequence numbers. | |
| 697 | */ | |
| 698 | if (wh->i_fc[1] & IEEE80211_FC1_WEP) { | |
| 699 | if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { | |
| 700 | /* | |
| 701 | * Discard encrypted frames when privacy is off. | |
| 702 | */ | |
| 703 | IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, | |
| 704 | wh, "WEP", "%s", "PRIVACY off"); | |
| 705 | vap->iv_stats.is_rx_noprivacy++; | |
| 706 | IEEE80211_NODE_STAT(ni, rx_noprivacy); | |
| 707 | goto out; | |
| 708 | } | |
| 709 | key = ieee80211_crypto_decap(ni, m, hdrspace); | |
| 710 | if (key == NULL) { | |
| 711 | /* NB: stats+msgs handled in crypto_decap */ | |
| 712 | IEEE80211_NODE_STAT(ni, rx_wepfail); | |
| 713 | goto out; | |
| 714 | } | |
| 715 | wh = mtod(m, struct ieee80211_frame *); | |
| 716 | wh->i_fc[1] &= ~IEEE80211_FC1_WEP; | |
| 717 | } else { | |
| 718 | /* XXX M_WEP and IEEE80211_F_PRIVACY */ | |
| 719 | key = NULL; | |
| 720 | } | |
| 721 | ||
| 722 | /* | |
| 723 | * Save QoS bits for use below--before we strip the header. | |
| 724 | */ | |
| 725 | if (subtype == IEEE80211_FC0_SUBTYPE_QOS) { | |
| 726 | qos = (dir == IEEE80211_FC1_DIR_DSTODS) ? | |
| 727 | ((struct ieee80211_qosframe_addr4 *)wh)->i_qos[0] : | |
| 728 | ((struct ieee80211_qosframe *)wh)->i_qos[0]; | |
| 729 | } else | |
| 730 | qos = 0; | |
| 731 | ||
| 732 | /* | |
| 733 | * Next up, any fragmentation. | |
| 734 | */ | |
| 735 | if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { | |
| 736 | m = ieee80211_defrag(ni, m, hdrspace); | |
| 737 | if (m == NULL) { | |
| 738 | /* Fragment dropped or frame not complete yet */ | |
| 739 | goto out; | |
| 740 | } | |
| 741 | } | |
| 742 | wh = NULL; /* no longer valid, catch any uses */ | |
| 743 | ||
| 744 | /* | |
| 745 | * Next strip any MSDU crypto bits. | |
| 746 | */ | |
| 747 | if (key != NULL && !ieee80211_crypto_demic(vap, key, m, 0)) { | |
| 748 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, | |
| 749 | ni->ni_macaddr, "data", "%s", "demic error"); | |
| 750 | vap->iv_stats.is_rx_demicfail++; | |
| 751 | IEEE80211_NODE_STAT(ni, rx_demicfail); | |
| 752 | goto out; | |
| 753 | } | |
| 754 | ||
| 755 | /* copy to listener after decrypt */ | |
| 756 | if (ieee80211_radiotap_active_vap(vap)) | |
| 757 | ieee80211_radiotap_rx(vap, m); | |
| 758 | need_tap = 0; | |
| 759 | ||
| 760 | /* | |
| 761 | * Finally, strip the 802.11 header. | |
| 762 | */ | |
| 763 | m = ieee80211_decap(vap, m, hdrspace); | |
| 764 | if (m == NULL) { | |
| 765 | /* XXX mask bit to check for both */ | |
| 766 | /* don't count Null data frames as errors */ | |
| 767 | if (subtype == IEEE80211_FC0_SUBTYPE_NODATA || | |
| 768 | subtype == IEEE80211_FC0_SUBTYPE_QOS_NULL) | |
| 769 | goto out; | |
| 770 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, | |
| 771 | ni->ni_macaddr, "data", "%s", "decap error"); | |
| 772 | vap->iv_stats.is_rx_decap++; | |
| 773 | IEEE80211_NODE_STAT(ni, rx_decap); | |
| 774 | goto err; | |
| 775 | } | |
| 776 | eh = mtod(m, struct ether_header *); | |
| 777 | if (!ieee80211_node_is_authorized(ni)) { | |
| 778 | /* | |
| 779 | * Deny any non-PAE frames received prior to | |
| 780 | * authorization. For open/shared-key | |
| 781 | * authentication the port is mark authorized | |
| 782 | * after authentication completes. For 802.1x | |
| 783 | * the port is not marked authorized by the | |
| 784 | * authenticator until the handshake has completed. | |
| 785 | */ | |
| 786 | if (eh->ether_type != htons(ETHERTYPE_PAE)) { | |
| 787 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT, | |
| 788 | eh->ether_shost, "data", | |
| 789 | "unauthorized port: ether type 0x%x len %u", | |
| 790 | eh->ether_type, m->m_pkthdr.len); | |
| 791 | vap->iv_stats.is_rx_unauth++; | |
| 792 | IEEE80211_NODE_STAT(ni, rx_unauth); | |
| 793 | goto err; | |
| 794 | } | |
| 795 | } else { | |
| 796 | /* | |
| 797 | * When denying unencrypted frames, discard | |
| 798 | * any non-PAE frames received without encryption. | |
| 799 | */ | |
| 800 | if ((vap->iv_flags & IEEE80211_F_DROPUNENC) && | |
| 801 | (key == NULL && (m->m_flags & M_WEP) == 0) && | |
| 802 | eh->ether_type != htons(ETHERTYPE_PAE)) { | |
| 803 | /* | |
| 804 | * Drop unencrypted frames. | |
| 805 | */ | |
| 806 | vap->iv_stats.is_rx_unencrypted++; | |
| 807 | IEEE80211_NODE_STAT(ni, rx_unencrypted); | |
| 808 | goto out; | |
| 809 | } | |
| 810 | } | |
| 811 | /* XXX require HT? */ | |
| 812 | if (qos & IEEE80211_QOS_AMSDU) { | |
| 813 | m = ieee80211_decap_amsdu(ni, m); | |
| 814 | if (m == NULL) | |
| 815 | return IEEE80211_FC0_TYPE_DATA; | |
| 816 | } else { | |
| 817 | #ifdef IEEE80211_SUPPORT_SUPERG | |
| 818 | m = ieee80211_decap_fastframe(vap, ni, m); | |
| 819 | if (m == NULL) | |
| 820 | return IEEE80211_FC0_TYPE_DATA; | |
| 821 | #endif | |
| 822 | } | |
| 823 | ieee80211_deliver_data(vap, ni, m); | |
| 824 | return IEEE80211_FC0_TYPE_DATA; | |
| 825 | ||
| 826 | case IEEE80211_FC0_TYPE_MGT: | |
| 827 | vap->iv_stats.is_rx_mgmt++; | |
| 828 | IEEE80211_NODE_STAT(ni, rx_mgmt); | |
| 829 | if (dir != IEEE80211_FC1_DIR_NODS) { | |
| 830 | IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, | |
| 831 | wh, "data", "incorrect dir 0x%x", dir); | |
| 832 | vap->iv_stats.is_rx_wrongdir++; | |
| 833 | goto err; | |
| 834 | } | |
| 835 | if (m->m_pkthdr.len < sizeof(struct ieee80211_frame)) { | |
| 836 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_ANY, | |
| 837 | ni->ni_macaddr, "mgt", "too short: len %u", | |
| 838 | m->m_pkthdr.len); | |
| 839 | vap->iv_stats.is_rx_tooshort++; | |
| 840 | goto out; | |
| 841 | } | |
| 842 | #ifdef IEEE80211_DEBUG | |
| 843 | if ((ieee80211_msg_debug(vap) && doprint(vap, subtype)) || | |
| 844 | ieee80211_msg_dumppkts(vap)) { | |
| 6168f72e | 845 | if_printf(ifp, "received %s from %6D rssi %d\n", |
| 32176cfd RP |
846 | ieee80211_mgt_subtype_name[subtype >> |
| 847 | IEEE80211_FC0_SUBTYPE_SHIFT], | |
| 6168f72e | 848 | wh->i_addr2, ":", rssi); |
| 32176cfd RP |
849 | } |
| 850 | #endif | |
| 851 | if (wh->i_fc[1] & IEEE80211_FC1_WEP) { | |
| 852 | if (subtype != IEEE80211_FC0_SUBTYPE_AUTH) { | |
| 853 | /* | |
| 854 | * Only shared key auth frames with a challenge | |
| 855 | * should be encrypted, discard all others. | |
| 856 | */ | |
| 857 | IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, | |
| 858 | wh, ieee80211_mgt_subtype_name[subtype >> | |
| 859 | IEEE80211_FC0_SUBTYPE_SHIFT], | |
| 860 | "%s", "WEP set but not permitted"); | |
| 861 | vap->iv_stats.is_rx_mgtdiscard++; /* XXX */ | |
| 862 | goto out; | |
| 863 | } | |
| 864 | if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { | |
| 865 | /* | |
| 866 | * Discard encrypted frames when privacy is off. | |
| 867 | */ | |
| 868 | IEEE80211_DISCARD(vap, IEEE80211_MSG_INPUT, | |
| 869 | wh, "mgt", "%s", "WEP set but PRIVACY off"); | |
| 870 | vap->iv_stats.is_rx_noprivacy++; | |
| 871 | goto out; | |
| 872 | } | |
| 873 | hdrspace = ieee80211_hdrspace(ic, wh); | |
| 874 | key = ieee80211_crypto_decap(ni, m, hdrspace); | |
| 875 | if (key == NULL) { | |
| 876 | /* NB: stats+msgs handled in crypto_decap */ | |
| 877 | goto out; | |
| 878 | } | |
| 879 | wh = mtod(m, struct ieee80211_frame *); | |
| 880 | wh->i_fc[1] &= ~IEEE80211_FC1_WEP; | |
| 881 | } | |
| 882 | vap->iv_recv_mgmt(ni, m, subtype, rssi, nf); | |
| 883 | goto out; | |
| 884 | ||
| 885 | case IEEE80211_FC0_TYPE_CTL: | |
| 886 | vap->iv_stats.is_rx_ctl++; | |
| 887 | IEEE80211_NODE_STAT(ni, rx_ctrl); | |
| 888 | vap->iv_recv_ctl(ni, m, subtype); | |
| 889 | goto out; | |
| 890 | ||
| 891 | default: | |
| 892 | IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, | |
| 893 | wh, NULL, "bad frame type 0x%x", type); | |
| 894 | /* should not come here */ | |
| 895 | break; | |
| 896 | } | |
| 897 | err: | |
| 898 | ifp->if_ierrors++; | |
| 899 | out: | |
| 900 | if (m != NULL) { | |
| 901 | if (need_tap && ieee80211_radiotap_active_vap(vap)) | |
| 902 | ieee80211_radiotap_rx(vap, m); | |
| 903 | m_freem(m); | |
| 904 | } | |
| 905 | return type; | |
| 906 | #undef SEQ_LEQ | |
| 907 | } | |
| 908 | ||
| 909 | static void | |
| 910 | sta_auth_open(struct ieee80211_node *ni, struct ieee80211_frame *wh, | |
| 911 | int rssi, int nf, uint16_t seq, uint16_t status) | |
| 912 | { | |
| 913 | struct ieee80211vap *vap = ni->ni_vap; | |
| 914 | ||
| 915 | if (ni->ni_authmode == IEEE80211_AUTH_SHARED) { | |
| 916 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, | |
| 917 | ni->ni_macaddr, "open auth", | |
| 918 | "bad sta auth mode %u", ni->ni_authmode); | |
| 919 | vap->iv_stats.is_rx_bad_auth++; /* XXX */ | |
| 920 | return; | |
| 921 | } | |
| 922 | if (vap->iv_state != IEEE80211_S_AUTH || | |
| 923 | seq != IEEE80211_AUTH_OPEN_RESPONSE) { | |
| 924 | vap->iv_stats.is_rx_bad_auth++; | |
| 925 | return; | |
| 926 | } | |
| 927 | if (status != 0) { | |
| 928 | IEEE80211_NOTE(vap, IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, | |
| 929 | ni, "open auth failed (reason %d)", status); | |
| 930 | vap->iv_stats.is_rx_auth_fail++; | |
| 931 | vap->iv_stats.is_rx_authfail_code = status; | |
| 932 | ieee80211_new_state(vap, IEEE80211_S_SCAN, | |
| 933 | IEEE80211_SCAN_FAIL_STATUS); | |
| 934 | } else | |
| 935 | ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); | |
| 936 | } | |
| 937 | ||
| 938 | static void | |
| 939 | sta_auth_shared(struct ieee80211_node *ni, struct ieee80211_frame *wh, | |
| 940 | uint8_t *frm, uint8_t *efrm, int rssi, int nf, | |
| 941 | uint16_t seq, uint16_t status) | |
| 942 | { | |
| 943 | struct ieee80211vap *vap = ni->ni_vap; | |
| 944 | uint8_t *challenge; | |
| 32176cfd RP |
945 | |
| 946 | /* | |
| 947 | * NB: this can happen as we allow pre-shared key | |
| 948 | * authentication to be enabled w/o wep being turned | |
| 949 | * on so that configuration of these can be done | |
| 950 | * in any order. It may be better to enforce the | |
| 951 | * ordering in which case this check would just be | |
| 952 | * for sanity/consistency. | |
| 953 | */ | |
| 954 | if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0) { | |
| 955 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, | |
| 956 | ni->ni_macaddr, "shared key auth", | |
| 957 | "%s", " PRIVACY is disabled"); | |
| 32176cfd RP |
958 | goto bad; |
| 959 | } | |
| 960 | /* | |
| 961 | * Pre-shared key authentication is evil; accept | |
| 962 | * it only if explicitly configured (it is supported | |
| 963 | * mainly for compatibility with clients like OS X). | |
| 964 | */ | |
| 965 | if (ni->ni_authmode != IEEE80211_AUTH_AUTO && | |
| 966 | ni->ni_authmode != IEEE80211_AUTH_SHARED) { | |
| 967 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, | |
| 968 | ni->ni_macaddr, "shared key auth", | |
| 969 | "bad sta auth mode %u", ni->ni_authmode); | |
| 970 | vap->iv_stats.is_rx_bad_auth++; /* XXX maybe a unique error? */ | |
| 32176cfd RP |
971 | goto bad; |
| 972 | } | |
| 973 | ||
| 974 | challenge = NULL; | |
| 975 | if (frm + 1 < efrm) { | |
| 976 | if ((frm[1] + 2) > (efrm - frm)) { | |
| 977 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, | |
| 978 | ni->ni_macaddr, "shared key auth", | |
| 979 | "ie %d/%d too long", | |
| 7bfcf376 | 980 | frm[0], (int)((frm[1] + 2) - (efrm - frm))); |
| 32176cfd | 981 | vap->iv_stats.is_rx_bad_auth++; |
| 32176cfd RP |
982 | goto bad; |
| 983 | } | |
| 984 | if (*frm == IEEE80211_ELEMID_CHALLENGE) | |
| 985 | challenge = frm; | |
| 986 | frm += frm[1] + 2; | |
| 987 | } | |
| 988 | switch (seq) { | |
| 989 | case IEEE80211_AUTH_SHARED_CHALLENGE: | |
| 990 | case IEEE80211_AUTH_SHARED_RESPONSE: | |
| 991 | if (challenge == NULL) { | |
| 992 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, | |
| 993 | ni->ni_macaddr, "shared key auth", | |
| 994 | "%s", "no challenge"); | |
| 995 | vap->iv_stats.is_rx_bad_auth++; | |
| 32176cfd RP |
996 | goto bad; |
| 997 | } | |
| 998 | if (challenge[1] != IEEE80211_CHALLENGE_LEN) { | |
| 999 | IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_AUTH, | |
| 1000 | ni->ni_macaddr, "shared key auth", | |
| 1001 | "bad challenge len %d", challenge[1]); | |
| 1002 | vap->iv_stats.is_rx_bad_auth++; | |
| 32176cfd RP |
1003 | goto bad; |
| 1004 | } | |
| 1005 | default: | |
| 1006 | break; | |
| 1007 | } | |
| 1008 | if (vap->iv_state != IEEE80211_S_AUTH) | |
| 1009 | return; | |
| 1010 | switch (seq) { | |
| 1011 | case IEEE80211_AUTH_SHARED_PASS: | |
| 1012 | if (ni->ni_challenge != NULL) { | |
| 1013 | kfree(ni->ni_challenge, M_80211_NODE); | |
| 1014 | ni->ni_challenge = NULL; | |
| 1015 | } | |
| 1016 | if (status != 0) { | |
| 1017 | IEEE80211_NOTE_FRAME(vap, | |
| 1018 | IEEE80211_MSG_DEBUG | IEEE80211_MSG_AUTH, wh, | |
| 1019 | "shared key auth failed (reason %d)", status); | |
| 1020 | vap->iv_stats.is_rx_auth_fail++; | |
| 1021 | vap->iv_stats.is_rx_authfail_code = status; | |
| 1022 | return; | |
| 1023 | } | |
| 1024 | ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); | |
| 1025 | break; | |
| 1026 | case IEEE80211_AUTH_SHARED_CHALLENGE: | |
| 1027 | if (!ieee80211_alloc_challenge(ni)) | |
| 1028 | return; | |
| 1029 | /* XXX could optimize by passing recvd challenge */ | |
| 1030 | memcpy(ni->ni_challenge, &challenge[2], challenge[1]); | |
| 1031 | IEEE80211_SEND_MGMT(ni, | |
| 1032 | IEEE80211_FC0_SUBTYPE_AUTH, seq + 1); | |
| 1033 | break; | |
| 1034 | default: | |
| 1035 | IEEE80211_DISCARD(vap, IEEE80211_MSG_AUTH, | |
| 1036 | wh, "shared key auth", "bad seq %d", seq); | |
| 1037 | vap->iv_stats.is_rx_bad_auth++; | |
| 1038 | return; | |
| 1039 | } | |
| 1040 | return; | |
| 1041 | bad: | |
| 1042 | /* | |
| 1043 | * Kick the state machine. This short-circuits | |
| 1044 | * using the mgt frame timeout to trigger the | |
| 1045 | * state transition. | |
| 1046 | */ | |
| 1047 | if (vap->iv_state == IEEE80211_S_AUTH) | |
| 1048 | ieee80211_new_state(vap, IEEE80211_S_SCAN, | |
| 1049 | IEEE80211_SCAN_FAIL_STATUS); | |
| 1050 | } | |
| 1051 | ||
| 1052 | static int | |
| 1053 | ieee80211_parse_wmeparams(struct ieee80211vap *vap, uint8_t *frm, | |
| 1054 | const struct ieee80211_frame *wh) | |
| 1055 | { | |
| 1056 | #define MS(_v, _f) (((_v) & _f) >> _f##_S) | |
| 1057 | struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme; | |
| 1058 | u_int len = frm[1], qosinfo; | |
| 1059 | int i; | |
| 1060 | ||
| 1061 | if (len < sizeof(struct ieee80211_wme_param)-2) { | |
| 1062 | IEEE80211_DISCARD_IE(vap, | |
| 1063 | IEEE80211_MSG_ELEMID | IEEE80211_MSG_WME, | |
| 1064 | wh, "WME", "too short, len %u", len); | |
| 1065 | return -1; | |
| 1066 | } | |
| 1067 | qosinfo = frm[__offsetof(struct ieee80211_wme_param, param_qosInfo)]; | |
| 1068 | qosinfo &= WME_QOSINFO_COUNT; | |
| 1069 | /* XXX do proper check for wraparound */ | |
| 1070 | if (qosinfo == wme->wme_wmeChanParams.cap_info) | |
| 1071 | return 0; | |
| 1072 | frm += __offsetof(struct ieee80211_wme_param, params_acParams); | |
| 1073 | for (i = 0; i < WME_NUM_AC; i++) { | |
| 1074 | struct wmeParams *wmep = | |
| 1075 | &wme->wme_wmeChanParams.cap_wmeParams[i]; | |
| 1076 | /* NB: ACI not used */ | |
| 1077 | wmep->wmep_acm = MS(frm[0], WME_PARAM_ACM); | |
| 1078 | wmep->wmep_aifsn = MS(frm[0], WME_PARAM_AIFSN); | |
| 1079 | wmep->wmep_logcwmin = MS(frm[1], WME_PARAM_LOGCWMIN); | |
| 1080 | wmep->wmep_logcwmax = MS(frm[1], WME_PARAM_LOGCWMAX); | |
| 1081 | wmep->wmep_txopLimit = LE_READ_2(frm+2); | |
| 1082 | frm += 4; | |
| 1083 | } | |
| 1084 | wme->wme_wmeChanParams.cap_info = qosinfo; | |
| 1085 | return 1; | |
| 1086 | #undef MS | |
| 1087 | } | |
| 1088 | ||
| 1089 | /* | |
| 1090 | * Process 11h Channel Switch Announcement (CSA) ie. If this | |
| 1091 | * is the first CSA then initiate the switch. Otherwise we | |
| 1092 | * track state and trigger completion and/or cancel of the switch. | |
| 1093 | * XXX should be public for IBSS use | |
| 1094 | */ | |
| 1095 | static void | |
| 1096 | ieee80211_parse_csaparams(struct ieee80211vap *vap, uint8_t *frm, | |
| 1097 | const struct ieee80211_frame *wh) | |
| 1098 | { | |
| 1099 | struct ieee80211com *ic = vap->iv_ic; | |
| 1100 | const struct ieee80211_csa_ie *csa = | |
| 1101 | (const struct ieee80211_csa_ie *) frm; | |
| 1102 | ||
| 1103 | KASSERT(vap->iv_state >= IEEE80211_S_RUN, | |
| 1104 | ("state %s", ieee80211_state_name[vap->iv_state])); | |
| 1105 | ||
| 1106 | if (csa->csa_mode > 1) { | |
| 1107 | IEEE80211_DISCARD_IE(vap, | |
| 1108 | IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, | |
| 1109 | wh, "CSA", "invalid mode %u", csa->csa_mode); | |
| 1110 | return; | |
| 1111 | } | |
| 32176cfd RP |
1112 | if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) { |
| 1113 | /* | |
| 1114 | * Convert the channel number to a channel reference. We | |
| 1115 | * try first to preserve turbo attribute of the current | |
| 1116 | * channel then fallback. Note this will not work if the | |
| 1117 | * CSA specifies a channel that requires a band switch (e.g. | |
| 1118 | * 11a => 11g). This is intentional as 11h is defined only | |
| 1119 | * for 5GHz/11a and because the switch does not involve a | |
| 1120 | * reassociation, protocol state (capabilities, negotated | |
| 1121 | * rates, etc) may/will be wrong. | |
| 1122 | */ | |
| 1123 | struct ieee80211_channel *c = | |
| 1124 | ieee80211_find_channel_byieee(ic, csa->csa_newchan, | |
| 1125 | (ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALLTURBO)); | |
| 1126 | if (c == NULL) { | |
| 1127 | c = ieee80211_find_channel_byieee(ic, | |
| 1128 | csa->csa_newchan, | |
| 1129 | (ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALL)); | |
| 1130 | if (c == NULL) { | |
| 1131 | IEEE80211_DISCARD_IE(vap, | |
| 1132 | IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, | |
| 1133 | wh, "CSA", "invalid channel %u", | |
| 1134 | csa->csa_newchan); | |
| 1135 | goto done; | |
| 1136 | } | |
| 1137 | } | |
| 1138 | #if IEEE80211_CSA_COUNT_MIN > 0 | |
| 1139 | if (csa->csa_count < IEEE80211_CSA_COUNT_MIN) { | |
| 1140 | /* | |
| 1141 | * Require at least IEEE80211_CSA_COUNT_MIN count to | |
| 1142 | * reduce the risk of being redirected by a fabricated | |
| 1143 | * CSA. If a valid CSA is dropped we'll still get a | |
| 1144 | * beacon miss when the AP leaves the channel so we'll | |
| 1145 | * eventually follow to the new channel. | |
| 1146 | * | |
| 1147 | * NOTE: this violates the 11h spec that states that | |
| 1148 | * count may be any value and if 0 then a switch | |
| 1149 | * should happen asap. | |
| 1150 | */ | |
| 1151 | IEEE80211_DISCARD_IE(vap, | |
| 1152 | IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH, | |
| 1153 | wh, "CSA", "count %u too small, must be >= %u", | |
| 1154 | csa->csa_count, IEEE80211_CSA_COUNT_MIN); | |
| 1155 | goto done; | |
| 1156 | } | |
| 1157 | #endif | |
| 1158 | ieee80211_csa_startswitch(ic, c, csa->csa_mode, csa->csa_count); | |
| 1159 | } else { | |
| 1160 | /* | |
| 1161 | * Validate this ie against the initial CSA. We require | |
| 1162 | * mode and channel not change and the count must be | |
| 1163 | * monotonically decreasing. This may be pointless and | |
| 1164 | * canceling the switch as a result may be too paranoid but | |
| 1165 | * in the worst case if we drop out of CSA because of this | |
| 1166 | * and the AP does move then we'll just end up taking a | |
| 1167 | * beacon miss and scan to find the AP. | |
| 1168 | * | |
| 1169 | * XXX may want <= on count as we also process ProbeResp | |
| 1170 | * frames and those may come in w/ the same count as the | |
| 1171 | * previous beacon; but doing so leaves us open to a stuck | |
| 1172 | * count until we add a dead-man timer | |
| 1173 | */ | |
| 1174 | if (!(csa->csa_count < ic->ic_csa_count && | |
| 1175 | csa->csa_mode == ic->ic_csa_mode && | |
| 1176 | csa->csa_newchan == ieee80211_chan2ieee(ic, ic->ic_csa_newchan))) { | |
| 1177 | IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_DOTH, wh, | |
| 1178 | "CSA ie mismatch, initial ie <%d,%d,%d>, " | |
| 1179 | "this ie <%d,%d,%d>", ic->ic_csa_mode, | |
| 7bfcf376 | 1180 | ic->ic_csa_newchan->ic_ieee, ic->ic_csa_count, |
| 32176cfd RP |
1181 | csa->csa_mode, csa->csa_newchan, csa->csa_count); |
| 1182 | ieee80211_csa_cancelswitch(ic); | |
| 1183 | } else { | |
| 1184 | if (csa->csa_count <= 1) | |
| 1185 | ieee80211_csa_completeswitch(ic); | |
| 1186 | else | |
| 1187 | ic->ic_csa_count = csa->csa_count; | |
| 1188 | } | |
| 1189 | } | |
| 1190 | done: | |
| 26c6f223 | 1191 | ; |
| 32176cfd RP |
1192 | } |
| 1193 | ||
| 1194 | /* | |
| 1195 | * Return non-zero if a background scan may be continued: | |
| 1196 | * o bg scan is active | |
| 1197 | * o no channel switch is pending | |
| 1198 | * o there has not been any traffic recently | |
| 1199 | * | |
| 1200 | * Note we do not check if there is an administrative enable; | |
| 1201 | * this is only done to start the scan. We assume that any | |
| 1202 | * change in state will be accompanied by a request to cancel | |
| 1203 | * active scans which will otherwise cause this test to fail. | |
| 1204 | */ | |
| 1205 | static __inline int | |
| 1206 | contbgscan(struct ieee80211vap *vap) | |
| 1207 | { | |
| 1208 | struct ieee80211com *ic = vap->iv_ic; | |
| 1209 | ||
| 1210 | return ((ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) && | |
| 1211 | (ic->ic_flags & IEEE80211_F_CSAPENDING) == 0 && | |
| 1212 | vap->iv_state == IEEE80211_S_RUN && /* XXX? */ | |
| 1213 | time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)); | |
| 1214 | } | |
| 1215 | ||
| 1216 | /* | |
| 1217 | * Return non-zero if a backgrond scan may be started: | |
| 1218 | * o bg scanning is administratively enabled | |
| 1219 | * o no channel switch is pending | |
| 1220 | * o we are not boosted on a dynamic turbo channel | |
| 1221 | * o there has not been a scan recently | |
| 1222 | * o there has not been any traffic recently | |
| 1223 | */ | |
| 1224 | static __inline int | |
| 1225 | startbgscan(struct ieee80211vap *vap) | |
| 1226 | { | |
| 1227 | struct ieee80211com *ic = vap->iv_ic; | |
| 1228 | ||
| 1229 | return ((vap->iv_flags & IEEE80211_F_BGSCAN) && | |
| 1230 | (ic->ic_flags & IEEE80211_F_CSAPENDING) == 0 && | |
| 1231 | #ifdef IEEE80211_SUPPORT_SUPERG | |
| 1232 | !IEEE80211_IS_CHAN_DTURBO(ic->ic_curchan) && | |
| 1233 | #endif | |
| 1234 | time_after(ticks, ic->ic_lastscan + vap->iv_bgscanintvl) && | |
| 1235 | time_after(ticks, ic->ic_lastdata + vap->iv_bgscanidle)); | |
| 1236 | } | |
| 1237 | ||
| 1238 | static void | |
| 1239 | sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, | |
| 1240 | int subtype, int rssi, int nf) | |
| 1241 | { | |
| 1242 | #define ISPROBE(_st) ((_st) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) | |
| 1243 | #define ISREASSOC(_st) ((_st) == IEEE80211_FC0_SUBTYPE_REASSOC_RESP) | |
| 1244 | struct ieee80211vap *vap = ni->ni_vap; | |
| 1245 | struct ieee80211com *ic = ni->ni_ic; | |
| 1246 | struct ieee80211_frame *wh; | |
| 1247 | uint8_t *frm, *efrm; | |
| 1248 | uint8_t *rates, *xrates, *wme, *htcap, *htinfo; | |
| 1249 | uint8_t rate; | |
| 1250 | ||
| 1251 | wh = mtod(m0, struct ieee80211_frame *); | |
| 1252 | frm = (uint8_t *)&wh[1]; | |
| 1253 | efrm = mtod(m0, uint8_t *) + m0->m_len; | |
| 1254 | switch (subtype) { | |
| 1255 | case IEEE80211_FC0_SUBTYPE_PROBE_RESP: | |
| 1256 | case IEEE80211_FC0_SUBTYPE_BEACON: { | |
| 1257 | struct ieee80211_scanparams scan; | |
| 1258 | /* | |
| 1259 | * We process beacon/probe response frames: | |
| 1260 | * o when scanning, or | |
| 1261 | * o station mode when associated (to collect state | |
| 1262 | * updates such as 802.11g slot time) | |
| 1263 | * Frames otherwise received are discarded. | |
| 1264 | */ | |
| 1265 | if (!((ic->ic_flags & IEEE80211_F_SCAN) || ni->ni_associd)) { | |
| 1266 | vap->iv_stats.is_rx_mgtdiscard++; | |
| 1267 | return; | |
| 1268 | } | |
| 1269 | /* XXX probe response in sta mode when !scanning? */ | |
| 1270 | if (ieee80211_parse_beacon(ni, m0, &scan) != 0) | |
| 1271 | return; | |
| 1272 | /* | |
| 1273 | * Count frame now that we know it's to be processed. | |
| 1274 | */ | |
| 1275 | if (subtype == IEEE80211_FC0_SUBTYPE_BEACON) { | |
| 1276 | vap->iv_stats.is_rx_beacon++; /* XXX remove */ | |
| 1277 | IEEE80211_NODE_STAT(ni, rx_beacons); | |
| 1278 | } else | |
| 1279 | IEEE80211_NODE_STAT(ni, rx_proberesp); | |
| 1280 | /* | |
| 1281 | * When operating in station mode, check for state updates. | |
| 1282 | * Be careful to ignore beacons received while doing a | |
| 1283 | * background scan. We consider only 11g/WMM stuff right now. | |
| 1284 | */ | |
| 1285 | if (ni->ni_associd != 0 && | |
| 1286 | ((ic->ic_flags & IEEE80211_F_SCAN) == 0 || | |
| 1287 | IEEE80211_ADDR_EQ(wh->i_addr2, ni->ni_bssid))) { | |
| 1288 | /* record tsf of last beacon */ | |
| 1289 | memcpy(ni->ni_tstamp.data, scan.tstamp, | |
| 1290 | sizeof(ni->ni_tstamp)); | |
| 1291 | /* count beacon frame for s/w bmiss handling */ | |
| 1292 | vap->iv_swbmiss_count++; | |
| 1293 | vap->iv_bmiss_count = 0; | |
| 1294 | if (ni->ni_erp != scan.erp) { | |
| 1295 | IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, | |
| 1296 | wh->i_addr2, | |
| 1297 | "erp change: was 0x%x, now 0x%x", | |
| 1298 | ni->ni_erp, scan.erp); | |
| 1299 | if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && | |
| 1300 | (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) | |
| 1301 | ic->ic_flags |= IEEE80211_F_USEPROT; | |
| 1302 | else | |
| 1303 | ic->ic_flags &= ~IEEE80211_F_USEPROT; | |
| 1304 | ni->ni_erp = scan.erp; | |
| 1305 | /* XXX statistic */ | |
| 1306 | /* XXX driver notification */ | |
| 1307 | } | |
| 1308 | if ((ni->ni_capinfo ^ scan.capinfo) & IEEE80211_CAPINFO_SHORT_SLOTTIME) { | |
| 1309 | IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, | |
| 1310 | wh->i_addr2, | |
| 1311 | "capabilities change: was 0x%x, now 0x%x", | |
| 1312 | ni->ni_capinfo, scan.capinfo); | |
| 1313 | /* | |
| 1314 | * NB: we assume short preamble doesn't | |
| 1315 | * change dynamically | |
| 1316 | */ | |
| 1317 | ieee80211_set_shortslottime(ic, | |
| 1318 | IEEE80211_IS_CHAN_A(ic->ic_bsschan) || | |
| 1319 | (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); | |
| 1320 | ni->ni_capinfo = (ni->ni_capinfo &~ IEEE80211_CAPINFO_SHORT_SLOTTIME) | |
| 1321 | | (scan.capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME); | |
| 1322 | /* XXX statistic */ | |
| 1323 | } | |
| 1324 | if (scan.wme != NULL && | |
| 1325 | (ni->ni_flags & IEEE80211_NODE_QOS) && | |
| 1326 | ieee80211_parse_wmeparams(vap, scan.wme, wh) > 0) | |
| 1327 | ieee80211_wme_updateparams(vap); | |
| 1328 | #ifdef IEEE80211_SUPPORT_SUPERG | |
| 1329 | if (scan.ath != NULL) | |
| 1330 | ieee80211_parse_athparams(ni, scan.ath, wh); | |
| 1331 | #endif | |
| 1332 | if (scan.htcap != NULL && scan.htinfo != NULL && | |
| 1333 | (vap->iv_flags_ht & IEEE80211_FHT_HT)) { | |
| 1334 | ieee80211_ht_updateparams(ni, | |
| 1335 | scan.htcap, scan.htinfo); | |
| 1336 | /* XXX state changes? */ | |
| 1337 | } | |
| 1338 | if (scan.tim != NULL) { | |
| 1339 | struct ieee80211_tim_ie *tim = | |
| 1340 | (struct ieee80211_tim_ie *) scan.tim; | |
| 1341 | #if 0 | |
| 1342 | int aid = IEEE80211_AID(ni->ni_associd); | |
| 1343 | int ix = aid / NBBY; | |
| 1344 | int min = tim->tim_bitctl &~ 1; | |
| 1345 | int max = tim->tim_len + min - 4; | |
| 1346 | if ((tim->tim_bitctl&1) || | |
| 1347 | (min <= ix && ix <= max && | |
| 1348 | isset(tim->tim_bitmap - min, aid))) { | |
| 1349 | /* | |
| 1350 | * XXX Do not let bg scan kick off | |
| 1351 | * we are expecting data. | |
| 1352 | */ | |
| 1353 | ic->ic_lastdata = ticks; | |
| 1354 | ieee80211_sta_pwrsave(vap, 0); | |
| 1355 | } | |
| 1356 | #endif | |
| 1357 | ni->ni_dtim_count = tim->tim_count; | |
| 1358 | ni->ni_dtim_period = tim->tim_period; | |
| 1359 | } | |
| 1360 | if (scan.csa != NULL && | |
| 1361 | (vap->iv_flags & IEEE80211_F_DOTH)) | |
| 1362 | ieee80211_parse_csaparams(vap, scan.csa, wh); | |
| 1363 | else if (ic->ic_flags & IEEE80211_F_CSAPENDING) { | |
| 1364 | /* | |
| 1365 | * No CSA ie or 11h disabled, but a channel | |
| 1366 | * switch is pending; drop out so we aren't | |
| 1367 | * stuck in CSA state. If the AP really is | |
| 1368 | * moving we'll get a beacon miss and scan. | |
| 1369 | */ | |
| 32176cfd | 1370 | ieee80211_csa_cancelswitch(ic); |
| 32176cfd RP |
1371 | } |
| 1372 | /* | |
| 1373 | * If scanning, pass the info to the scan module. | |
| 1374 | * Otherwise, check if it's the right time to do | |
| 1375 | * a background scan. Background scanning must | |
| 1376 | * be enabled and we must not be operating in the | |
| 1377 | * turbo phase of dynamic turbo mode. Then, | |
| 1378 | * it's been a while since the last background | |
| 1379 | * scan and if no data frames have come through | |
| 1380 | * recently, kick off a scan. Note that this | |
| 1381 | * is the mechanism by which a background scan | |
| 1382 | * is started _and_ continued each time we | |
| 1383 | * return on-channel to receive a beacon from | |
| 1384 | * our ap. | |
| 1385 | */ | |
| 1386 | if (ic->ic_flags & IEEE80211_F_SCAN) { | |
| 1387 | ieee80211_add_scan(vap, &scan, wh, | |
| 1388 | subtype, rssi, nf); | |
| 1389 | } else if (contbgscan(vap)) { | |
| 1390 | ieee80211_bg_scan(vap, 0); | |
| 1391 | } else if (startbgscan(vap)) { | |
| 1392 | vap->iv_stats.is_scan_bg++; | |
| 1393 | #if 0 | |
| 1394 | /* wakeup if we are sleeing */ | |
| 1395 | ieee80211_set_pwrsave(vap, 0); | |
| 1396 | #endif | |
| 1397 | ieee80211_bg_scan(vap, 0); | |
| 1398 | } | |
| 1399 | return; | |
| 1400 | } | |
| 1401 | /* | |
| 1402 | * If scanning, just pass information to the scan module. | |
| 1403 | */ | |
| 1404 | if (ic->ic_flags & IEEE80211_F_SCAN) { | |
| 1405 | if (ic->ic_flags_ext & IEEE80211_FEXT_PROBECHAN) { | |
| 1406 | /* | |
| 1407 | * Actively scanning a channel marked passive; | |
| 1408 | * send a probe request now that we know there | |
| 1409 | * is 802.11 traffic present. | |
| 1410 | * | |
| 1411 | * XXX check if the beacon we recv'd gives | |
| 1412 | * us what we need and suppress the probe req | |
| 1413 | */ | |
| 1414 | ieee80211_probe_curchan(vap, 1); | |
| 1415 | ic->ic_flags_ext &= ~IEEE80211_FEXT_PROBECHAN; | |
| 1416 | } | |
| 1417 | ieee80211_add_scan(vap, &scan, wh, subtype, rssi, nf); | |
| 1418 | return; | |
| 1419 | } | |
| 1420 | break; | |
| 1421 | } | |
| 1422 | ||
| 1423 | case IEEE80211_FC0_SUBTYPE_AUTH: { | |
| 1424 | uint16_t algo, seq, status; | |
| 1425 | /* | |
| 1426 | * auth frame format | |
| 1427 | * [2] algorithm | |
| 1428 | * [2] sequence | |
| 1429 | * [2] status | |
| 1430 | * [tlv*] challenge | |
| 1431 | */ | |
| 1432 | IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); | |
| 1433 | algo = le16toh(*(uint16_t *)frm); | |
| 1434 | seq = le16toh(*(uint16_t *)(frm + 2)); | |
| 1435 | status = le16toh(*(uint16_t *)(frm + 4)); | |
| 1436 | IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_AUTH, wh->i_addr2, | |
| 1437 | "recv auth frame with algorithm %d seq %d", algo, seq); | |
| 1438 | ||
| 1439 | if (vap->iv_flags & IEEE80211_F_COUNTERM) { | |
| 1440 | IEEE80211_DISCARD(vap, | |
| 1441 | IEEE80211_MSG_AUTH | IEEE80211_MSG_CRYPTO, | |
| 1442 | wh, "auth", "%s", "TKIP countermeasures enabled"); | |
| 1443 | vap->iv_stats.is_rx_auth_countermeasures++; | |
| 1444 | if (vap->iv_opmode == IEEE80211_M_HOSTAP) { | |
| 1445 | ieee80211_send_error(ni, wh->i_addr2, | |
| 1446 | IEEE80211_FC0_SUBTYPE_AUTH, | |
| 1447 | IEEE80211_REASON_MIC_FAILURE); | |
| 1448 | } | |
| 1449 | return; | |
| 1450 | } | |
| 1451 | if (algo == IEEE80211_AUTH_ALG_SHARED) | |
| 1452 | sta_auth_shared(ni, wh, frm + 6, efrm, rssi, nf, | |
| 1453 | seq, status); | |
| 1454 | else if (algo == IEEE80211_AUTH_ALG_OPEN) | |
| 1455 | sta_auth_open(ni, wh, rssi, nf, seq, status); | |
| 1456 | else { | |
| 1457 | IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, | |
| 1458 | wh, "auth", "unsupported alg %d", algo); | |
| 1459 | vap->iv_stats.is_rx_auth_unsupported++; | |
| 1460 | return; | |
| 1461 | } | |
| 1462 | break; | |
| 1463 | } | |
| 1464 | ||
| 1465 | case IEEE80211_FC0_SUBTYPE_ASSOC_RESP: | |
| 1466 | case IEEE80211_FC0_SUBTYPE_REASSOC_RESP: { | |
| 1467 | uint16_t capinfo, associd; | |
| 1468 | uint16_t status; | |
| 1469 | ||
| 1470 | if (vap->iv_state != IEEE80211_S_ASSOC) { | |
| 1471 | vap->iv_stats.is_rx_mgtdiscard++; | |
| 1472 | return; | |
| 1473 | } | |
| 1474 | ||
| 1475 | /* | |
| 1476 | * asresp frame format | |
| 1477 | * [2] capability information | |
| 1478 | * [2] status | |
| 1479 | * [2] association ID | |
| 1480 | * [tlv] supported rates | |
| 1481 | * [tlv] extended supported rates | |
| 1482 | * [tlv] WME | |
| 1483 | * [tlv] HT capabilities | |
| 1484 | * [tlv] HT info | |
| 1485 | */ | |
| 1486 | IEEE80211_VERIFY_LENGTH(efrm - frm, 6, return); | |
| 1487 | ni = vap->iv_bss; | |
| 1488 | capinfo = le16toh(*(uint16_t *)frm); | |
| 1489 | frm += 2; | |
| 1490 | status = le16toh(*(uint16_t *)frm); | |
| 1491 | frm += 2; | |
| 1492 | if (status != 0) { | |
| 1493 | IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, | |
| 1494 | wh->i_addr2, "%sassoc failed (reason %d)", | |
| 1495 | ISREASSOC(subtype) ? "re" : "", status); | |
| 1496 | vap->iv_stats.is_rx_auth_fail++; /* XXX */ | |
| 1497 | return; | |
| 1498 | } | |
| 1499 | associd = le16toh(*(uint16_t *)frm); | |
| 1500 | frm += 2; | |
| 1501 | ||
| 1502 | rates = xrates = wme = htcap = htinfo = NULL; | |
| 1503 | while (efrm - frm > 1) { | |
| 1504 | IEEE80211_VERIFY_LENGTH(efrm - frm, frm[1] + 2, return); | |
| 1505 | switch (*frm) { | |
| 1506 | case IEEE80211_ELEMID_RATES: | |
| 1507 | rates = frm; | |
| 1508 | break; | |
| 1509 | case IEEE80211_ELEMID_XRATES: | |
| 1510 | xrates = frm; | |
| 1511 | break; | |
| 1512 | case IEEE80211_ELEMID_HTCAP: | |
| 1513 | htcap = frm; | |
| 1514 | break; | |
| 1515 | case IEEE80211_ELEMID_HTINFO: | |
| 1516 | htinfo = frm; | |
| 1517 | break; | |
| 1518 | case IEEE80211_ELEMID_VENDOR: | |
| 1519 | if (iswmeoui(frm)) | |
| 1520 | wme = frm; | |
| 1521 | else if (vap->iv_flags_ht & IEEE80211_FHT_HTCOMPAT) { | |
| 1522 | /* | |
| 1523 | * Accept pre-draft HT ie's if the | |
| 1524 | * standard ones have not been seen. | |
| 1525 | */ | |
| 1526 | if (ishtcapoui(frm)) { | |
| 1527 | if (htcap == NULL) | |
| 1528 | htcap = frm; | |
| 1529 | } else if (ishtinfooui(frm)) { | |
| 1530 | if (htinfo == NULL) | |
| 1531 | htcap = frm; | |
| 1532 | } | |
| 1533 | } | |
| 1534 | /* XXX Atheros OUI support */ | |
| 1535 | break; | |
| 1536 | } | |
| 1537 | frm += frm[1] + 2; | |
| 1538 | } | |
| 1539 | ||
| 1540 | IEEE80211_VERIFY_ELEMENT(rates, IEEE80211_RATE_MAXSIZE, return); | |
| 1541 | if (xrates != NULL) | |
| 1542 | IEEE80211_VERIFY_ELEMENT(xrates, | |
| 1543 | IEEE80211_RATE_MAXSIZE - rates[1], return); | |
| 1544 | rate = ieee80211_setup_rates(ni, rates, xrates, | |
| 1545 | IEEE80211_F_JOIN | | |
| 1546 | IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE | | |
| 1547 | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); | |
| 1548 | if (rate & IEEE80211_RATE_BASIC) { | |
| 1549 | IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_ASSOC, | |
| 1550 | wh->i_addr2, | |
| 1551 | "%sassoc failed (rate set mismatch)", | |
| 1552 | ISREASSOC(subtype) ? "re" : ""); | |
| 1553 | vap->iv_stats.is_rx_assoc_norate++; | |
| 1554 | ieee80211_new_state(vap, IEEE80211_S_SCAN, | |
| 1555 | IEEE80211_SCAN_FAIL_STATUS); | |
| 1556 | return; | |
| 1557 | } | |
| 1558 | ||
| 1559 | ni->ni_capinfo = capinfo; | |
| 1560 | ni->ni_associd = associd; | |
| 1561 | if (ni->ni_jointime == 0) | |
| 1562 | ni->ni_jointime = time_second; | |
| 1563 | if (wme != NULL && | |
| 1564 | ieee80211_parse_wmeparams(vap, wme, wh) >= 0) { | |
| 1565 | ni->ni_flags |= IEEE80211_NODE_QOS; | |
| 1566 | ieee80211_wme_updateparams(vap); | |
| 1567 | } else | |
| 1568 | ni->ni_flags &= ~IEEE80211_NODE_QOS; | |
| 1569 | /* | |
| 1570 | * Setup HT state according to the negotiation. | |
| 1571 | * | |
| 1572 | * NB: shouldn't need to check if HT use is enabled but some | |
| 1573 | * ap's send back HT ie's even when we don't indicate we | |
| 1574 | * are HT capable in our AssocReq. | |
| 1575 | */ | |
| 1576 | if (htcap != NULL && htinfo != NULL && | |
| 1577 | (vap->iv_flags_ht & IEEE80211_FHT_HT)) { | |
| 1578 | ieee80211_ht_node_init(ni); | |
| 1579 | ieee80211_ht_updateparams(ni, htcap, htinfo); | |
| 1580 | ieee80211_setup_htrates(ni, htcap, | |
| 1581 | IEEE80211_F_JOIN | IEEE80211_F_DOBRS); | |
| 1582 | ieee80211_setup_basic_htrates(ni, htinfo); | |
| 1583 | ieee80211_node_setuptxparms(ni); | |
| 1584 | } else { | |
| 1585 | #ifdef IEEE80211_SUPPORT_SUPERG | |
| 1586 | if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_ATH)) | |
| 1587 | ieee80211_ff_node_init(ni); | |
| 1588 | #endif | |
| 1589 | } | |
| 1590 | /* | |
| 1591 | * Configure state now that we are associated. | |
| 1592 | * | |
| 1593 | * XXX may need different/additional driver callbacks? | |
| 1594 | */ | |
| 1595 | if (IEEE80211_IS_CHAN_A(ic->ic_curchan) || | |
| 1596 | (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)) { | |
| 1597 | ic->ic_flags |= IEEE80211_F_SHPREAMBLE; | |
| 1598 | ic->ic_flags &= ~IEEE80211_F_USEBARKER; | |
| 1599 | } else { | |
| 1600 | ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE; | |
| 1601 | ic->ic_flags |= IEEE80211_F_USEBARKER; | |
| 1602 | } | |
| 1603 | ieee80211_set_shortslottime(ic, | |
| 1604 | IEEE80211_IS_CHAN_A(ic->ic_curchan) || | |
| 1605 | (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)); | |
| 1606 | /* | |
| 1607 | * Honor ERP protection. | |
| 1608 | * | |
| 1609 | * NB: ni_erp should zero for non-11g operation. | |
| 1610 | */ | |
| 1611 | if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan) && | |
| 1612 | (ni->ni_erp & IEEE80211_ERP_USE_PROTECTION)) | |
| 1613 | ic->ic_flags |= IEEE80211_F_USEPROT; | |
| 1614 | else | |
| 1615 | ic->ic_flags &= ~IEEE80211_F_USEPROT; | |
| 1616 | IEEE80211_NOTE_MAC(vap, | |
| 1617 | IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, wh->i_addr2, | |
| 1618 | "%sassoc success at aid %d: %s preamble, %s slot time%s%s%s%s%s%s%s%s", | |
| 1619 | ISREASSOC(subtype) ? "re" : "", | |
| 1620 | IEEE80211_NODE_AID(ni), | |
| 1621 | ic->ic_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", | |
| 1622 | ic->ic_flags&IEEE80211_F_SHSLOT ? "short" : "long", | |
| 1623 | ic->ic_flags&IEEE80211_F_USEPROT ? ", protection" : "", | |
| 1624 | ni->ni_flags & IEEE80211_NODE_QOS ? ", QoS" : "", | |
| 1625 | ni->ni_flags & IEEE80211_NODE_HT ? | |
| 1626 | (ni->ni_chw == 40 ? ", HT40" : ", HT20") : "", | |
| 1627 | ni->ni_flags & IEEE80211_NODE_AMPDU ? " (+AMPDU)" : "", | |
| 1628 | ni->ni_flags & IEEE80211_NODE_MIMO_RTS ? " (+SMPS-DYN)" : | |
| 1629 | ni->ni_flags & IEEE80211_NODE_MIMO_PS ? " (+SMPS)" : "", | |
| 1630 | ni->ni_flags & IEEE80211_NODE_RIFS ? " (+RIFS)" : "", | |
| 1631 | IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF) ? | |
| 1632 | ", fast-frames" : "", | |
| 1633 | IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_TURBOP) ? | |
| 1634 | ", turbo" : "" | |
| 1635 | ); | |
| 1636 | ieee80211_new_state(vap, IEEE80211_S_RUN, subtype); | |
| 1637 | break; | |
| 1638 | } | |
| 1639 | ||
| 1640 | case IEEE80211_FC0_SUBTYPE_DEAUTH: { | |
| 1641 | uint16_t reason; | |
| 1642 | ||
| 1643 | if (vap->iv_state == IEEE80211_S_SCAN) { | |
| 1644 | vap->iv_stats.is_rx_mgtdiscard++; | |
| 1645 | return; | |
| 1646 | } | |
| 1647 | if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { | |
| 1648 | /* NB: can happen when in promiscuous mode */ | |
| 1649 | vap->iv_stats.is_rx_mgtdiscard++; | |
| 1650 | break; | |
| 1651 | } | |
| 1652 | ||
| 1653 | /* | |
| 1654 | * deauth frame format | |
| 1655 | * [2] reason | |
| 1656 | */ | |
| 1657 | IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); | |
| 1658 | reason = le16toh(*(uint16_t *)frm); | |
| 1659 | ||
| 1660 | vap->iv_stats.is_rx_deauth++; | |
| 1661 | vap->iv_stats.is_rx_deauth_code = reason; | |
| 1662 | IEEE80211_NODE_STAT(ni, rx_deauth); | |
| 1663 | ||
| 1664 | IEEE80211_NOTE(vap, IEEE80211_MSG_AUTH, ni, | |
| 1665 | "recv deauthenticate (reason %d)", reason); | |
| 1666 | ieee80211_new_state(vap, IEEE80211_S_AUTH, | |
| 1667 | (reason << 8) | IEEE80211_FC0_SUBTYPE_DEAUTH); | |
| 1668 | break; | |
| 1669 | } | |
| 1670 | ||
| 1671 | case IEEE80211_FC0_SUBTYPE_DISASSOC: { | |
| 1672 | uint16_t reason; | |
| 1673 | ||
| 1674 | if (vap->iv_state != IEEE80211_S_RUN && | |
| 1675 | vap->iv_state != IEEE80211_S_ASSOC && | |
| 1676 | vap->iv_state != IEEE80211_S_AUTH) { | |
| 1677 | vap->iv_stats.is_rx_mgtdiscard++; | |
| 1678 | return; | |
| 1679 | } | |
| 1680 | if (!IEEE80211_ADDR_EQ(wh->i_addr1, vap->iv_myaddr)) { | |
| 1681 | /* NB: can happen when in promiscuous mode */ | |
| 1682 | vap->iv_stats.is_rx_mgtdiscard++; | |
| 1683 | break; | |
| 1684 | } | |
| 1685 | ||
| 1686 | /* | |
| 1687 | * disassoc frame format | |
| 1688 | * [2] reason | |
| 1689 | */ | |
| 1690 | IEEE80211_VERIFY_LENGTH(efrm - frm, 2, return); | |
| 1691 | reason = le16toh(*(uint16_t *)frm); | |
| 1692 | ||
| 1693 | vap->iv_stats.is_rx_disassoc++; | |
| 1694 | vap->iv_stats.is_rx_disassoc_code = reason; | |
| 1695 | IEEE80211_NODE_STAT(ni, rx_disassoc); | |
| 1696 | ||
| 1697 | IEEE80211_NOTE(vap, IEEE80211_MSG_ASSOC, ni, | |
| 1698 | "recv disassociate (reason %d)", reason); | |
| 1699 | ieee80211_new_state(vap, IEEE80211_S_ASSOC, 0); | |
| 1700 | break; | |
| 1701 | } | |
| 1702 | ||
| 1703 | case IEEE80211_FC0_SUBTYPE_ACTION: | |
| 1704 | if (vap->iv_state == IEEE80211_S_RUN) { | |
| 1705 | if (ieee80211_parse_action(ni, m0) == 0) | |
| 1706 | ic->ic_recv_action(ni, wh, frm, efrm); | |
| 1707 | } else | |
| 1708 | vap->iv_stats.is_rx_mgtdiscard++; | |
| 1709 | break; | |
| 1710 | ||
| 1711 | case IEEE80211_FC0_SUBTYPE_PROBE_REQ: | |
| 1712 | case IEEE80211_FC0_SUBTYPE_ASSOC_REQ: | |
| 1713 | case IEEE80211_FC0_SUBTYPE_REASSOC_REQ: | |
| 1714 | vap->iv_stats.is_rx_mgtdiscard++; | |
| 1715 | return; | |
| 1716 | default: | |
| 1717 | IEEE80211_DISCARD(vap, IEEE80211_MSG_ANY, | |
| 1718 | wh, "mgt", "subtype 0x%x not handled", subtype); | |
| 1719 | vap->iv_stats.is_rx_badsubtype++; | |
| 1720 | break; | |
| 1721 | } | |
| 1722 | #undef ISREASSOC | |
| 1723 | #undef ISPROBE | |
| 1724 | } | |
| 1725 | ||
| 1726 | static void | |
| 1727 | sta_recv_ctl(struct ieee80211_node *ni, struct mbuf *m0, int subtype) | |
| 1728 | { | |
| 1729 | } |