diff options
author | sam <sam@FreeBSD.org> | 2004-12-08 17:26:47 +0000 |
---|---|---|
committer | sam <sam@FreeBSD.org> | 2004-12-08 17:26:47 +0000 |
commit | 2843bf259ec9ca475410e4e9e53e7cd47cd55333 (patch) | |
tree | 704c955cb68020dd8237664b417e3ee6141f028b /sys/net80211/ieee80211_node.c | |
parent | d9ec783dcb4653bc5f6993ab5f154827f5a58922 (diff) | |
download | FreeBSD-src-2843bf259ec9ca475410e4e9e53e7cd47cd55333.zip FreeBSD-src-2843bf259ec9ca475410e4e9e53e7cd47cd55333.tar.gz |
Update 802.11 support; too much new functionality to fully describe
here but it includes completed 802.11g, WPA, 802.11i, 802.1x, WME/WMM,
AP-side power-save, crypto plugin framework, authenticator plugin framework,
and access control plugin frameowrk.
Diffstat (limited to 'sys/net80211/ieee80211_node.c')
-rw-r--r-- | sys/net80211/ieee80211_node.c | 1746 |
1 files changed, 1475 insertions, 271 deletions
diff --git a/sys/net80211/ieee80211_node.c b/sys/net80211/ieee80211_node.c index 5d4f0f5..708e7fa 100644 --- a/sys/net80211/ieee80211_node.c +++ b/sys/net80211/ieee80211_node.c @@ -1,6 +1,6 @@ /*- * Copyright (c) 2001 Atsushi Onoe - * Copyright (c) 2002, 2003 Sam Leffler, Errno Consulting + * Copyright (c) 2002-2004 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,120 +33,249 @@ #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); -#include "opt_inet.h" - #include <sys/param.h> #include <sys/systm.h> #include <sys/mbuf.h> #include <sys/malloc.h> #include <sys/kernel.h> + #include <sys/socket.h> -#include <sys/sockio.h> -#include <sys/endian.h> -#include <sys/errno.h> -#include <sys/bus.h> -#include <sys/proc.h> -#include <sys/sysctl.h> - -#include <machine/atomic.h> #include <net/if.h> -#include <net/if_dl.h> #include <net/if_media.h> -#include <net/if_arp.h> #include <net/ethernet.h> -#include <net/if_llc.h> #include <net80211/ieee80211_var.h> #include <net/bpf.h> -#ifdef INET -#include <netinet/in.h> -#include <netinet/if_ether.h> -#endif +static struct ieee80211_node *node_alloc(struct ieee80211_node_table *); +static void node_cleanup(struct ieee80211_node *); +static void node_free(struct ieee80211_node *); +static u_int8_t node_getrssi(const struct ieee80211_node *); + +static void ieee80211_setup_node(struct ieee80211_node_table *, + struct ieee80211_node *, const u_int8_t *); +static void _ieee80211_free_node(struct ieee80211_node *); +static void ieee80211_free_allnodes(struct ieee80211_node_table *); -static struct ieee80211_node *ieee80211_node_alloc(struct ieee80211com *); -static void ieee80211_node_free(struct ieee80211com *, struct ieee80211_node *); -static void ieee80211_node_copy(struct ieee80211com *, - struct ieee80211_node *, const struct ieee80211_node *); -static u_int8_t ieee80211_node_getrssi(struct ieee80211com *, - struct ieee80211_node *); +static void ieee80211_timeout_scan_candidates(struct ieee80211_node_table *); +static void ieee80211_timeout_stations(struct ieee80211_node_table *); -static void ieee80211_setup_node(struct ieee80211com *ic, - struct ieee80211_node *ni, u_int8_t *macaddr); -static void _ieee80211_free_node(struct ieee80211com *, - struct ieee80211_node *); +static void ieee80211_set_tim(struct ieee80211com *, + struct ieee80211_node *, int set); + +static void ieee80211_node_table_init(struct ieee80211com *ic, + struct ieee80211_node_table *nt, const char *name, int inact, + void (*timeout)(struct ieee80211_node_table *)); +static struct ieee80211_node_table *ieee80211_node_table_alloc( + struct ieee80211com *ic, const char *name, int inact, + void (*timeout)(struct ieee80211_node_table *)); +static void ieee80211_node_table_cleanup(struct ieee80211_node_table *nt); MALLOC_DEFINE(M_80211_NODE, "80211node", "802.11 node state"); void -ieee80211_node_attach(struct ifnet *ifp) +ieee80211_node_attach(struct ieee80211com *ic) { - struct ieee80211com *ic = (void *)ifp; - /* XXX need unit */ - IEEE80211_NODE_LOCK_INIT(ic, ifp->if_xname); - TAILQ_INIT(&ic->ic_node); - ic->ic_node_alloc = ieee80211_node_alloc; - ic->ic_node_free = ieee80211_node_free; - ic->ic_node_copy = ieee80211_node_copy; - ic->ic_node_getrssi = ieee80211_node_getrssi; - ic->ic_scangen = 1; + ic->ic_sta = NULL; /* defer to when we need it */ + ieee80211_node_table_init(ic, &ic->ic_scan, "scan", + IEEE80211_INACT_SCAN, ieee80211_timeout_scan_candidates); + + ic->ic_node_alloc = node_alloc; + ic->ic_node_free = node_free; + ic->ic_node_cleanup = node_cleanup; + ic->ic_node_getrssi = node_getrssi; + + /* default station inactivity timer setings */ + ic->ic_inact_init = IEEE80211_INACT_INIT; + ic->ic_inact_auth = IEEE80211_INACT_AUTH; + ic->ic_inact_run = IEEE80211_INACT_RUN; + ic->ic_inact_probe = IEEE80211_INACT_PROBE; + + /* XXX defer */ + if (ic->ic_max_aid == 0) + ic->ic_max_aid = IEEE80211_AID_DEF; + else if (ic->ic_max_aid > IEEE80211_AID_MAX) + ic->ic_max_aid = IEEE80211_AID_MAX; + MALLOC(ic->ic_aid_bitmap, u_int32_t *, + howmany(ic->ic_max_aid, 32) * sizeof(u_int32_t), + M_DEVBUF, M_NOWAIT | M_ZERO); + if (ic->ic_aid_bitmap == NULL) { + /* XXX no way to recover */ + printf("%s: no memory for AID bitmap!\n", __func__); + ic->ic_max_aid = 0; + } + + /* XXX defer until using hostap/ibss mode */ + ic->ic_tim_len = howmany(ic->ic_max_aid, 8) * sizeof(u_int8_t); + MALLOC(ic->ic_tim_bitmap, u_int8_t *, ic->ic_tim_len, + M_DEVBUF, M_NOWAIT | M_ZERO); + if (ic->ic_tim_bitmap == NULL) { + /* XXX no way to recover */ + printf("%s: no memory for TIM bitmap!\n", __func__); + } + ic->ic_set_tim = ieee80211_set_tim; /* NB: driver should override */ } void -ieee80211_node_lateattach(struct ifnet *ifp) +ieee80211_node_lateattach(struct ieee80211com *ic) { - struct ieee80211com *ic = (void *)ifp; struct ieee80211_node *ni; + struct ieee80211_rsnparms *rsn; - ni = (*ic->ic_node_alloc)(ic); + ni = ieee80211_alloc_node(&ic->ic_scan, ic->ic_myaddr); KASSERT(ni != NULL, ("unable to setup inital BSS node")); - ni->ni_chan = IEEE80211_CHAN_ANYC; - ic->ic_bss = ni; - ic->ic_txpower = IEEE80211_TXPOWER_MAX; + /* + * Setup "global settings" in the bss node so that + * each new station automatically inherits them. + */ + rsn = &ni->ni_rsn; + /* WEP, TKIP, and AES-CCM are always supported */ + rsn->rsn_ucastcipherset |= 1<<IEEE80211_CIPHER_WEP; + rsn->rsn_ucastcipherset |= 1<<IEEE80211_CIPHER_TKIP; + rsn->rsn_ucastcipherset |= 1<<IEEE80211_CIPHER_AES_CCM; + if (ic->ic_caps & IEEE80211_C_AES) + rsn->rsn_ucastcipherset |= 1<<IEEE80211_CIPHER_AES_OCB; + if (ic->ic_caps & IEEE80211_C_CKIP) + rsn->rsn_ucastcipherset |= 1<<IEEE80211_CIPHER_CKIP; + /* + * Default unicast cipher to WEP for 802.1x use. If + * WPA is enabled the management code will set these + * values to reflect. + */ + rsn->rsn_ucastcipher = IEEE80211_CIPHER_WEP; + rsn->rsn_ucastkeylen = 104 / NBBY; + /* + * WPA says the multicast cipher is the lowest unicast + * cipher supported. But we skip WEP which would + * otherwise be used based on this criteria. + */ + rsn->rsn_mcastcipher = IEEE80211_CIPHER_TKIP; + rsn->rsn_mcastkeylen = 128 / NBBY; + + /* + * We support both WPA-PSK and 802.1x; the one used + * is determined by the authentication mode and the + * setting of the PSK state. + */ + rsn->rsn_keymgmtset = WPA_ASE_8021X_UNSPEC | WPA_ASE_8021X_PSK; + rsn->rsn_keymgmt = WPA_ASE_8021X_PSK; + + ic->ic_bss = ieee80211_ref_node(ni); /* hold reference */ + ic->ic_auth = ieee80211_authenticator_get(ni->ni_authmode); +} + +void +ieee80211_node_detach(struct ieee80211com *ic) +{ + + if (ic->ic_bss != NULL) { + ieee80211_free_node(ic->ic_bss); + ic->ic_bss = NULL; + } + ieee80211_node_table_cleanup(&ic->ic_scan); + if (ic->ic_sta != NULL) { + ieee80211_node_table_free(ic->ic_sta); + ic->ic_sta = NULL; + } + if (ic->ic_aid_bitmap != NULL) { + FREE(ic->ic_aid_bitmap, M_DEVBUF); + ic->ic_aid_bitmap = NULL; + } + if (ic->ic_tim_bitmap != NULL) { + FREE(ic->ic_tim_bitmap, M_DEVBUF); + ic->ic_tim_bitmap = NULL; + } } +/* + * Port authorize/unauthorize interfaces for use by an authenticator. + */ + void -ieee80211_node_detach(struct ifnet *ifp) +ieee80211_node_authorize(struct ieee80211com *ic, struct ieee80211_node *ni) { - struct ieee80211com *ic = (void *)ifp; + ni->ni_flags |= IEEE80211_NODE_AUTH; +} - if (ic->ic_bss != NULL) - (*ic->ic_node_free)(ic, ic->ic_bss); - ieee80211_free_allnodes(ic); - IEEE80211_NODE_LOCK_DESTROY(ic); +void +ieee80211_node_unauthorize(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + ni->ni_flags &= ~IEEE80211_NODE_AUTH; +} + +/* + * Set/change the channel. The rate set is also updated as + * to insure a consistent view by drivers. + */ +static __inline void +ieee80211_set_chan(struct ieee80211com *ic, + struct ieee80211_node *ni, struct ieee80211_channel *chan) +{ + ni->ni_chan = chan; + ni->ni_rates = ic->ic_sup_rates[ieee80211_chan2mode(ic, chan)]; } /* * AP scanning support. */ +#ifdef IEEE80211_DEBUG +static void +dump_chanlist(const u_char chans[]) +{ + const char *sep; + int i; + + sep = " "; + for (i = 0; i < IEEE80211_CHAN_MAX; i++) + if (isset(chans, i)) { + printf("%s%u", sep, i); + sep = ", "; + } +} +#endif /* IEEE80211_DEBUG */ + /* - * Initialize the active channel set based on the set + * Initialize the channel set to scan based on the * of available channels and the current PHY mode. */ static void -ieee80211_reset_scan(struct ifnet *ifp) +ieee80211_reset_scan(struct ieee80211com *ic) { - struct ieee80211com *ic = (void *)ifp; - memcpy(ic->ic_chan_scan, ic->ic_chan_active, - sizeof(ic->ic_chan_active)); + /* XXX ic_des_chan should be handled with ic_chan_active */ + if (ic->ic_des_chan != IEEE80211_CHAN_ANYC) { + memset(ic->ic_chan_scan, 0, sizeof(ic->ic_chan_scan)); + setbit(ic->ic_chan_scan, + ieee80211_chan2ieee(ic, ic->ic_des_chan)); + } else + memcpy(ic->ic_chan_scan, ic->ic_chan_active, + sizeof(ic->ic_chan_active)); /* NB: hack, setup so next_scan starts with the first channel */ if (ic->ic_bss->ni_chan == IEEE80211_CHAN_ANYC) - ic->ic_bss->ni_chan = &ic->ic_channels[IEEE80211_CHAN_MAX]; + ieee80211_set_chan(ic, ic->ic_bss, + &ic->ic_channels[IEEE80211_CHAN_MAX]); +#ifdef IEEE80211_DEBUG + if (ieee80211_msg_scan(ic)) { + printf("%s: scan set:", __func__); + dump_chanlist(ic->ic_chan_scan); + printf(" start chan %u\n", + ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan)); + } +#endif /* IEEE80211_DEBUG */ } /* * Begin an active scan. */ void -ieee80211_begin_scan(struct ifnet *ifp) +ieee80211_begin_scan(struct ieee80211com *ic, int reset) { - struct ieee80211com *ic = (void *)ifp; + ic->ic_scan.nt_scangen++; /* * In all but hostap mode scanning starts off in * an active mode before switching to passive. @@ -156,95 +285,174 @@ ieee80211_begin_scan(struct ifnet *ifp) ic->ic_stats.is_scan_active++; } else ic->ic_stats.is_scan_passive++; - if (ifp->if_flags & IFF_DEBUG) - if_printf(ifp, "begin %s scan\n", - (ic->ic_flags & IEEE80211_F_ASCAN) ? - "active" : "passive"); + IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN, "begin %s scan, scangen %u\n", + (ic->ic_flags & IEEE80211_F_ASCAN) ? "active" : "passive", + ic->ic_scan.nt_scangen); /* - * Clear scan state and flush any previously seen - * AP's. Note that the latter assumes we don't act - * as both an AP and a station, otherwise we'll - * potentially flush state of stations associated - * with us. + * Clear scan state and flush any previously seen AP's. */ - ieee80211_reset_scan(ifp); - ieee80211_free_allnodes(ic); + ieee80211_reset_scan(ic); + if (reset) + ieee80211_free_allnodes(&ic->ic_scan); + + ic->ic_flags |= IEEE80211_F_SCAN; /* Scan the next channel. */ - ieee80211_next_scan(ifp); + ieee80211_next_scan(ic); } /* * Switch to the next channel marked for scanning. */ -void -ieee80211_next_scan(struct ifnet *ifp) +int +ieee80211_next_scan(struct ieee80211com *ic) { - struct ieee80211com *ic = (void *)ifp; struct ieee80211_channel *chan; + /* + * Insure any previous mgt frame timeouts don't fire. + * This assumes the driver does the right thing in + * flushing anything queued in the driver and below. + */ + ic->ic_mgt_timer = 0; + chan = ic->ic_bss->ni_chan; - for (;;) { + do { if (++chan > &ic->ic_channels[IEEE80211_CHAN_MAX]) chan = &ic->ic_channels[0]; if (isset(ic->ic_chan_scan, ieee80211_chan2ieee(ic, chan))) { + clrbit(ic->ic_chan_scan, ieee80211_chan2ieee(ic, chan)); + IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN, + "%s: chan %d->%d\n", __func__, + ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan), + ieee80211_chan2ieee(ic, chan)); + ieee80211_set_chan(ic, ic->ic_bss, chan); +#ifdef notyet + /* XXX driver state change */ /* - * Honor channels marked passive-only - * during an active scan. + * Scan next channel. If doing an active scan + * and the channel is not marked passive-only + * then send a probe request. Otherwise just + * listen for beacons on the channel. */ - if ((ic->ic_flags & IEEE80211_F_ASCAN) == 0 || - (chan->ic_flags & IEEE80211_CHAN_PASSIVE) == 0) - break; - } - if (chan == ic->ic_bss->ni_chan) { - ieee80211_end_scan(ifp); - return; + if ((ic->ic_flags & IEEE80211_F_ASCAN) && + (ni->ni_chan->ic_flags & IEEE80211_CHAN_PASSIVE) == 0) { + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_PROBE_REQ, 0); + } +#else + ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); +#endif + return 1; } - } - clrbit(ic->ic_chan_scan, ieee80211_chan2ieee(ic, chan)); - IEEE80211_DPRINTF(("ieee80211_next_scan: chan %d->%d\n", - ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan), - ieee80211_chan2ieee(ic, chan))); - ic->ic_bss->ni_chan = chan; - ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); + } while (chan != ic->ic_bss->ni_chan); + ieee80211_end_scan(ic); + return 0; } void ieee80211_create_ibss(struct ieee80211com* ic, struct ieee80211_channel *chan) { struct ieee80211_node *ni; - struct ifnet *ifp = &ic->ic_if; + + IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN, + "%s: creating ibss\n", __func__); + + /* + * Create the station/neighbor table. Note that for adhoc + * mode we make the initial inactivity timer longer since + * we create nodes only through discovery and they typically + * are long-lived associations. + */ + if (ic->ic_opmode == IEEE80211_M_HOSTAP) + ic->ic_sta = ieee80211_node_table_alloc(ic, + "station", ic->ic_inact_init, + ieee80211_timeout_stations); + else + ic->ic_sta = ieee80211_node_table_alloc(ic, + "neighbor", ic->ic_inact_run, + ieee80211_timeout_stations); + if (ic->ic_sta == NULL) { + /* + * Should remain in SCAN state and retry. + */ + /* XXX stat+msg */ + return; + } ni = ic->ic_bss; - if (ifp->if_flags & IFF_DEBUG) - if_printf(ifp, "creating ibss\n"); - ic->ic_flags |= IEEE80211_F_SIBSS; - ni->ni_chan = chan; - ni->ni_rates = ic->ic_sup_rates[ieee80211_chan2mode(ic, ni->ni_chan)]; IEEE80211_ADDR_COPY(ni->ni_macaddr, ic->ic_myaddr); IEEE80211_ADDR_COPY(ni->ni_bssid, ic->ic_myaddr); - if (ic->ic_opmode == IEEE80211_M_IBSS) - ni->ni_bssid[0] |= 0x02; /* local bit for IBSS */ ni->ni_esslen = ic->ic_des_esslen; memcpy(ni->ni_essid, ic->ic_des_essid, ni->ni_esslen); ni->ni_rssi = 0; ni->ni_rstamp = 0; - memset(ni->ni_tstamp, 0, sizeof(ni->ni_tstamp)); + ni->ni_tstamp.tsf = 0; ni->ni_intval = ic->ic_lintval; - ni->ni_capinfo = IEEE80211_CAPINFO_IBSS; - if (ic->ic_flags & IEEE80211_F_WEPON) + ni->ni_capinfo = 0; + ni->ni_erp = 0; + if (ic->ic_flags & IEEE80211_F_PRIVACY) ni->ni_capinfo |= IEEE80211_CAPINFO_PRIVACY; if (ic->ic_phytype == IEEE80211_T_FH) { ni->ni_fhdwell = 200; /* XXX */ ni->ni_fhindex = 1; } + if (ic->ic_opmode == IEEE80211_M_IBSS) { + ic->ic_flags |= IEEE80211_F_SIBSS; + ni->ni_capinfo |= IEEE80211_CAPINFO_IBSS; /* XXX */ + ni->ni_bssid[0] |= 0x02; /* local bit for IBSS */ + } + /* + * Fix the channel and related attributes. + */ + ieee80211_set_chan(ic, ni, chan); + ic->ic_curmode = ieee80211_chan2mode(ic, chan); + /* + * Do mode-specific rate setup. + */ + if (ic->ic_curmode == IEEE80211_MODE_11G) { + /* + * Use a mixed 11b/11g rate set. + */ + ieee80211_set11gbasicrates(&ni->ni_rates, IEEE80211_MODE_11G); + } else if (ic->ic_curmode == IEEE80211_MODE_11B) { + /* + * Force pure 11b rate set. + */ + ieee80211_set11gbasicrates(&ni->ni_rates, IEEE80211_MODE_11B); + } + /* + * Set the erp state (mostly the slot time) to deal with + * the auto-select case; this should be redundant if the + * mode is locked. + */ + ieee80211_reset_erp(ic); + ieee80211_wme_initparams(ic); + ieee80211_new_state(ic, IEEE80211_S_RUN, -1); } +void +ieee80211_reset_bss(struct ieee80211com *ic) +{ + struct ieee80211_node *ni, *obss; + + ieee80211_node_table_reset(&ic->ic_scan); + ni = ieee80211_alloc_node(&ic->ic_scan, ic->ic_myaddr); + KASSERT(ni != NULL, ("unable to setup inital BSS node")); + obss = ic->ic_bss; + ic->ic_bss = ieee80211_ref_node(ni); + if (obss != NULL) + ieee80211_free_node(obss); + if (ic->ic_sta != NULL) { + ieee80211_node_table_free(ic->ic_sta); + ic->ic_sta = NULL; + } +} + static int -ieee80211_match_bss(struct ifnet *ifp, struct ieee80211_node *ni) +ieee80211_match_bss(struct ieee80211com *ic, struct ieee80211_node *ni) { - struct ieee80211com *ic = (void *)ifp; u_int8_t rate; int fail; @@ -261,7 +469,7 @@ ieee80211_match_bss(struct ifnet *ifp, struct ieee80211_node *ni) if ((ni->ni_capinfo & IEEE80211_CAPINFO_ESS) == 0) fail |= 0x02; } - if (ic->ic_flags & IEEE80211_F_WEPON) { + if (ic->ic_flags & IEEE80211_F_PRIVACY) { if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) fail |= 0x04; } else { @@ -280,7 +488,7 @@ ieee80211_match_bss(struct ifnet *ifp, struct ieee80211_node *ni) !IEEE80211_ADDR_EQ(ic->ic_des_bssid, ni->ni_bssid)) fail |= 0x20; #ifdef IEEE80211_DEBUG - if (ifp->if_flags & IFF_DEBUG) { + if (ieee80211_msg_scan(ic)) { printf(" %c %s", fail ? '-' : '+', ether_sprintf(ni->ni_macaddr)); printf(" %s%c", ether_sprintf(ni->ni_bssid), @@ -306,49 +514,137 @@ ieee80211_match_bss(struct ifnet *ifp, struct ieee80211_node *ni) return fail; } +static __inline u_int8_t +maxrate(const struct ieee80211_node *ni) +{ + const struct ieee80211_rateset *rs = &ni->ni_rates; + /* NB: assumes rate set is sorted (happens on frame receive) */ + return rs->rs_rates[rs->rs_nrates-1] & IEEE80211_RATE_VAL; +} + +/* + * Compare the capabilities of two nodes and decide which is + * more desirable (return >0 if a is considered better). Note + * that we assume compatibility/usability has already been checked + * so we don't need to (e.g. validate whether privacy is supported). + * Used to select the best scan candidate for association in a BSS. + */ +static int +ieee80211_node_compare(struct ieee80211com *ic, + const struct ieee80211_node *a, + const struct ieee80211_node *b) +{ + u_int8_t maxa, maxb; + u_int8_t rssia, rssib; + + /* privacy support preferred */ + if ((a->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) && + (b->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) + return 1; + if ((a->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0 && + (b->ni_capinfo & IEEE80211_CAPINFO_PRIVACY)) + return -1; + + /* best/max rate preferred if signal level close enough XXX */ + maxa = maxrate(a); + maxb = maxrate(b); + rssia = ic->ic_node_getrssi(a); + rssib = ic->ic_node_getrssi(b); + if (maxa != maxb && abs(rssib - rssia) < 5) + return maxa - maxb; + + /* XXX use freq for channel preference */ + /* for now just prefer 5Ghz band to all other bands */ + if (IEEE80211_IS_CHAN_5GHZ(a->ni_chan) && + !IEEE80211_IS_CHAN_5GHZ(b->ni_chan)) + return 1; + if (!IEEE80211_IS_CHAN_5GHZ(a->ni_chan) && + IEEE80211_IS_CHAN_5GHZ(b->ni_chan)) + return -1; + + /* all things being equal, use signal level */ + return rssia - rssib; +} + /* * Complete a scan of potential channels. */ void -ieee80211_end_scan(struct ifnet *ifp) +ieee80211_end_scan(struct ieee80211com *ic) { - struct ieee80211com *ic = (void *)ifp; struct ieee80211_node *ni, *nextbs, *selbs; - int i, fail; + struct ieee80211_node_table *nt; + + IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN, "end %s scan\n", + (ic->ic_flags & IEEE80211_F_ASCAN) ? "active" : "passive"); - ic->ic_flags &= ~IEEE80211_F_ASCAN; - ni = TAILQ_FIRST(&ic->ic_node); + ic->ic_flags &= ~(IEEE80211_F_SCAN | IEEE80211_F_ASCAN); + nt = &ic->ic_scan; + ni = TAILQ_FIRST(&nt->nt_node); + + ieee80211_notify_scan_done(ic); if (ic->ic_opmode == IEEE80211_M_HOSTAP) { - /* XXX off stack? */ - u_char occupied[roundup(IEEE80211_CHAN_MAX, NBBY)]; + u_int8_t maxrssi[IEEE80211_CHAN_MAX]; /* XXX off stack? */ + int i, bestchan; + u_int8_t rssi; + /* * The passive scan to look for existing AP's completed, * select a channel to camp on. Identify the channels * that already have one or more AP's and try to locate - * an unnoccupied one. If that fails, pick a random - * channel from the active set. + * an unnoccupied one. If that fails, pick a channel that + * looks to be quietest. */ + memset(maxrssi, 0, sizeof(maxrssi)); for (; ni != NULL; ni = nextbs) { ieee80211_ref_node(ni); nextbs = TAILQ_NEXT(ni, ni_list); - setbit(occupied, ieee80211_chan2ieee(ic, ni->ni_chan)); - ieee80211_free_node(ic, ni); + rssi = ic->ic_node_getrssi(ni); + i = ieee80211_chan2ieee(ic, ni->ni_chan); + if (rssi > maxrssi[i]) + maxrssi[i] = rssi; + ieee80211_unref_node(&ni); } + /* XXX select channel more intelligently */ + bestchan = -1; for (i = 0; i < IEEE80211_CHAN_MAX; i++) - if (isset(ic->ic_chan_active, i) && isclr(occupied, i)) - break; - if (i == IEEE80211_CHAN_MAX) { - fail = arc4random() & 3; /* random 0-3 */ - for (i = 0; i < IEEE80211_CHAN_MAX; i++) - if (isset(ic->ic_chan_active, i) && fail-- == 0) + if (isset(ic->ic_chan_active, i)) { + /* + * If the channel is unoccupied the max rssi + * should be zero; just take it. Otherwise + * track the channel with the lowest rssi and + * use that when all channels appear occupied. + */ + if (maxrssi[i] == 0) { + bestchan = i; break; + } + if (maxrssi[i] < maxrssi[bestchan]) + bestchan = i; + } + if (bestchan != -1) { + ieee80211_create_ibss(ic, &ic->ic_channels[bestchan]); + return; } - ieee80211_create_ibss(ic, &ic->ic_channels[i]); - return; + /* no suitable channel, should not happen */ } + + /* + * When manually sequencing the state machine; scan just once + * regardless of whether we have a candidate or not. The + * controlling application is expected to setup state and + * initiate an association. + */ + if (ic->ic_roaming == IEEE80211_ROAMING_MANUAL) + return; + /* + * Automatic sequencing; look for a candidate and + * if found join the network. + */ if (ni == NULL) { - IEEE80211_DPRINTF(("%s: no scan candidate\n", __func__)); + IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN, + "%s: no scan candidate\n", __func__); notfound: if (ic->ic_opmode == IEEE80211_M_IBSS && (ic->ic_flags & IEEE80211_F_IBSSON) && @@ -359,13 +655,14 @@ ieee80211_end_scan(struct ifnet *ifp) /* * Reset the list of channels to scan and start again. */ - ieee80211_reset_scan(ifp); - ieee80211_next_scan(ifp); + ieee80211_reset_scan(ic); + ic->ic_flags |= IEEE80211_F_SCAN; + ieee80211_next_scan(ic); return; } selbs = NULL; - if (ifp->if_flags & IFF_DEBUG) - if_printf(ifp, "\tmacaddr bssid chan rssi rate flag wep essid\n"); + IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN, "\t%s\n", + "macaddr bssid chan rssi rate flag wep essid"); for (; ni != NULL; ni = nextbs) { ieee80211_ref_node(ni); nextbs = TAILQ_NEXT(ni, ni_list); @@ -375,14 +672,18 @@ ieee80211_end_scan(struct ifnet *ifp) * during my scan. So delete the entry for the AP * and retry to associate if there is another beacon. */ + IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN, + "%s: skip scan candidate %s, fails %u\n", + __func__, ether_sprintf(ni->ni_macaddr), + ni->ni_fails); if (ni->ni_fails++ > 2) - ieee80211_free_node(ic, ni); + ieee80211_free_node(ni); continue; } - if (ieee80211_match_bss(ifp, ni) == 0) { + if (ieee80211_match_bss(ic, ni) == 0) { if (selbs == NULL) selbs = ni; - else if (ni->ni_rssi > selbs->ni_rssi) { + else if (ieee80211_node_compare(ic, ni, selbs) > 0) { ieee80211_unref_node(&selbs); selbs = ni; } else @@ -393,148 +694,401 @@ ieee80211_end_scan(struct ifnet *ifp) } if (selbs == NULL) goto notfound; - (*ic->ic_node_copy)(ic, ic->ic_bss, selbs); + if (!ieee80211_sta_join(ic, selbs)) { + ieee80211_unref_node(&selbs); + goto notfound; + } +} + +/* + * Handle 802.11 ad hoc network merge. The + * convention, set by the Wireless Ethernet Compatibility Alliance + * (WECA), is that an 802.11 station will change its BSSID to match + * the "oldest" 802.11 ad hoc network, on the same channel, that + * has the station's desired SSID. The "oldest" 802.11 network + * sends beacons with the greatest TSF timestamp. + * + * The caller is assumed to validate TSF's before attempting a merge. + * + * Return !0 if the BSSID changed, 0 otherwise. + */ +int +ieee80211_ibss_merge(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + + if (IEEE80211_ADDR_EQ(ni->ni_bssid, ic->ic_bss->ni_bssid)) { + /* unchanged, nothing to do */ + return 0; + } + if (ieee80211_match_bss(ic, ni) != 0) { /* capabilities mismatch */ + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "%s: merge failed, capabilities mismatch\n", __func__); + ic->ic_stats.is_ibss_capmismatch++; + return 0; + } + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "%s: new bssid %s: %s preamble, %s slot time%s\n", __func__, + ether_sprintf(ni->ni_bssid), + ic->ic_flags&IEEE80211_F_SHPREAMBLE ? "short" : "long", + ic->ic_flags&IEEE80211_F_SHSLOT ? "short" : "long", + ic->ic_flags&IEEE80211_F_USEPROT ? ", protection" : "" + ); + return ieee80211_sta_join(ic, ni); +} + +/* + * Join the specified IBSS/BSS network. The node is assumed to + * be passed in with a held reference. + */ +int +ieee80211_sta_join(struct ieee80211com *ic, struct ieee80211_node *selbs) +{ + struct ieee80211_node *obss; + if (ic->ic_opmode == IEEE80211_M_IBSS) { - ieee80211_fix_rate(ic, ic->ic_bss, IEEE80211_F_DOFRATE | + /* + * Check rate set before committing to this node. + */ + ieee80211_fix_rate(ic, selbs, IEEE80211_F_DOFRATE | IEEE80211_F_DONEGO | IEEE80211_F_DODEL); - if (ic->ic_bss->ni_rates.rs_nrates == 0) { + if (selbs->ni_rates.rs_nrates == 0) { selbs->ni_fails++; - ieee80211_unref_node(&selbs); - goto notfound; + ic->ic_stats.is_ibss_norate++; + return 0; } - ieee80211_unref_node(&selbs); /* - * Discard scan set; the nodes have a refcnt of zero - * and have not asked the driver to setup private - * node state. Let them be repopulated on demand either - * through transmission (ieee80211_find_txnode) or receipt - * of a probe response (to be added). + * Create the neighbor table. */ - ieee80211_free_allnodes(ic); + ic->ic_sta = ieee80211_node_table_alloc(ic, + "neighbor", ic->ic_inact_run, + ieee80211_timeout_stations); + if (ic->ic_sta == NULL) { + /* + * Should remain in SCAN state and retry. + */ + /* XXX stat+msg */ + return 0; + } + } + + /* + * Committed to selbs, setup state. + */ + obss = ic->ic_bss; + ic->ic_bss = selbs; + if (obss != NULL) + ieee80211_free_node(obss); + /* + * Set the erp state (mostly the slot time) to deal with + * the auto-select case; this should be redundant if the + * mode is locked. + */ + ic->ic_curmode = ieee80211_chan2mode(ic, selbs->ni_chan); + ieee80211_reset_erp(ic); + ieee80211_wme_initparams(ic); + if (ic->ic_opmode == IEEE80211_M_IBSS) ieee80211_new_state(ic, IEEE80211_S_RUN, -1); - } else { - ieee80211_unref_node(&selbs); + else ieee80211_new_state(ic, IEEE80211_S_AUTH, -1); - } + return 1; +} + +/* + * Leave the specified IBSS/BSS network. The node is assumed to + * be passed in with a held reference. + */ +void +ieee80211_sta_leave(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + ic->ic_node_cleanup(ni); + ieee80211_notify_node_leave(ic, ni); } static struct ieee80211_node * -ieee80211_node_alloc(struct ieee80211com *ic) +node_alloc(struct ieee80211_node_table *nt) { struct ieee80211_node *ni; + MALLOC(ni, struct ieee80211_node *, sizeof(struct ieee80211_node), M_80211_NODE, M_NOWAIT | M_ZERO); return ni; } +/* + * Reclaim any resources in a node and reset any critical + * state. Typically nodes are free'd immediately after, + * but in some cases the storage may be reused so we need + * to insure consistent state (should probably fix that). + */ static void -ieee80211_node_free(struct ieee80211com *ic, struct ieee80211_node *ni) +node_cleanup(struct ieee80211_node *ni) { - FREE(ni, M_80211_NODE); +#define N(a) (sizeof(a)/sizeof(a[0])) + struct ieee80211com *ic = ni->ni_ic; + int i, qlen; + + /* NB: preserve ni_table */ + if (ni->ni_flags & IEEE80211_NODE_PWR_MGT) { + ic->ic_ps_sta--; + ni->ni_flags &= ~IEEE80211_NODE_PWR_MGT; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, + "[%s] power save mode off, %u sta's in ps mode\n", + ether_sprintf(ni->ni_macaddr), ic->ic_ps_sta); + } + + /* + * Drain power save queue and, if needed, clear TIM. + */ + IEEE80211_NODE_SAVEQ_DRAIN(ni, qlen); + if (qlen != 0 && ic->ic_set_tim != NULL) + ic->ic_set_tim(ic, ni, 0); + + ni->ni_associd = 0; + if (ni->ni_challenge != NULL) { + FREE(ni->ni_challenge, M_DEVBUF); + ni->ni_challenge = NULL; + } + /* + * Preserve SSID, WPA, and WME ie's so the bss node is + * reusable during a re-auth/re-assoc state transition. + * If we remove these data they will not be recreated + * because they come from a probe-response or beacon frame + * which cannot be expected prior to the association-response. + * This should not be an issue when operating in other modes + * as stations leaving always go through a full state transition + * which will rebuild this state. + * + * XXX does this leave us open to inheriting old state? + */ + for (i = 0; i < N(ni->ni_rxfrag); i++) + if (ni->ni_rxfrag[i] != NULL) { + m_freem(ni->ni_rxfrag[i]); + ni->ni_rxfrag[i] = NULL; + } + ieee80211_crypto_delkey(ic, &ni->ni_ucastkey); +#undef N } static void -ieee80211_node_copy(struct ieee80211com *ic, - struct ieee80211_node *dst, const struct ieee80211_node *src) +node_free(struct ieee80211_node *ni) { - *dst = *src; + struct ieee80211com *ic = ni->ni_ic; + + ic->ic_node_cleanup(ni); + if (ni->ni_wpa_ie != NULL) + FREE(ni->ni_wpa_ie, M_DEVBUF); + if (ni->ni_wme_ie != NULL) + FREE(ni->ni_wme_ie, M_DEVBUF); + IEEE80211_NODE_SAVEQ_DESTROY(ni); + FREE(ni, M_80211_NODE); } static u_int8_t -ieee80211_node_getrssi(struct ieee80211com *ic, struct ieee80211_node *ni) +node_getrssi(const struct ieee80211_node *ni) { return ni->ni_rssi; } static void -ieee80211_setup_node(struct ieee80211com *ic, - struct ieee80211_node *ni, u_int8_t *macaddr) +ieee80211_setup_node(struct ieee80211_node_table *nt, + struct ieee80211_node *ni, const u_int8_t *macaddr) { + struct ieee80211com *ic = nt->nt_ic; int hash; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, + "%s %s in %s table\n", __func__, + ether_sprintf(macaddr), nt->nt_name); + IEEE80211_ADDR_COPY(ni->ni_macaddr, macaddr); hash = IEEE80211_NODE_HASH(macaddr); - ni->ni_refcnt = 1; /* mark referenced */ - IEEE80211_NODE_LOCK(ic); - TAILQ_INSERT_TAIL(&ic->ic_node, ni, ni_list); - LIST_INSERT_HEAD(&ic->ic_hash[hash], ni, ni_hash); - /* - * Note we don't enable the inactive timer when acting - * as a station. Nodes created in this mode represent - * AP's identified while scanning. If we time them out - * then several things happen: we can't return the data - * to users to show the list of AP's we encountered, and - * more importantly, we'll incorrectly deauthenticate - * ourself because the inactivity timer will kick us off. - */ - if (ic->ic_opmode != IEEE80211_M_STA) - ic->ic_inact_timer = IEEE80211_INACT_WAIT; - IEEE80211_NODE_UNLOCK(ic); + ieee80211_node_initref(ni); /* mark referenced */ + ni->ni_chan = IEEE80211_CHAN_ANYC; + ni->ni_authmode = IEEE80211_AUTH_OPEN; + ni->ni_txpower = ic->ic_txpowlimit; /* max power */ + ieee80211_crypto_resetkey(ic, &ni->ni_ucastkey, IEEE80211_KEYIX_NONE); + ni->ni_inact = ni->ni_inact_reload = nt->nt_inact_init; + IEEE80211_NODE_SAVEQ_INIT(ni, "unknown"); + + IEEE80211_NODE_LOCK(nt); + TAILQ_INSERT_TAIL(&nt->nt_node, ni, ni_list); + LIST_INSERT_HEAD(&nt->nt_hash[hash], ni, ni_hash); + ni->ni_table = nt; + ni->ni_ic = ic; + IEEE80211_NODE_UNLOCK(nt); } struct ieee80211_node * -ieee80211_alloc_node(struct ieee80211com *ic, u_int8_t *macaddr) +ieee80211_alloc_node(struct ieee80211_node_table *nt, const u_int8_t *macaddr) { - struct ieee80211_node *ni = (*ic->ic_node_alloc)(ic); + struct ieee80211com *ic = nt->nt_ic; + struct ieee80211_node *ni; + + ni = ic->ic_node_alloc(nt); if (ni != NULL) - ieee80211_setup_node(ic, ni, macaddr); + ieee80211_setup_node(nt, ni, macaddr); else ic->ic_stats.is_rx_nodealloc++; return ni; } struct ieee80211_node * -ieee80211_dup_bss(struct ieee80211com *ic, u_int8_t *macaddr) +ieee80211_dup_bss(struct ieee80211_node_table *nt, const u_int8_t *macaddr) { - struct ieee80211_node *ni = (*ic->ic_node_alloc)(ic); + struct ieee80211com *ic = nt->nt_ic; + struct ieee80211_node *ni; + + ni = ic->ic_node_alloc(nt); if (ni != NULL) { - ieee80211_setup_node(ic, ni, macaddr); + ieee80211_setup_node(nt, ni, macaddr); /* * Inherit from ic_bss. */ + ni->ni_authmode = ic->ic_bss->ni_authmode; + ni->ni_txpower = ic->ic_bss->ni_txpower; + ni->ni_vlan = ic->ic_bss->ni_vlan; /* XXX?? */ IEEE80211_ADDR_COPY(ni->ni_bssid, ic->ic_bss->ni_bssid); - ni->ni_chan = ic->ic_bss->ni_chan; + ieee80211_set_chan(ic, ni, ic->ic_bss->ni_chan); + ni->ni_rsn = ic->ic_bss->ni_rsn; } else ic->ic_stats.is_rx_nodealloc++; return ni; } static struct ieee80211_node * -_ieee80211_find_node(struct ieee80211com *ic, u_int8_t *macaddr) +#ifdef IEEE80211_DEBUG_REFCNT +_ieee80211_find_node_debug(struct ieee80211_node_table *nt, + const u_int8_t *macaddr, const char *func, int line) +#else +_ieee80211_find_node(struct ieee80211_node_table *nt, + const u_int8_t *macaddr) +#endif { struct ieee80211_node *ni; int hash; - IEEE80211_NODE_LOCK_ASSERT(ic); + IEEE80211_NODE_LOCK_ASSERT(nt); hash = IEEE80211_NODE_HASH(macaddr); - LIST_FOREACH(ni, &ic->ic_hash[hash], ni_hash) { + LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { if (IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr)) { - atomic_add_int(&ni->ni_refcnt, 1);/* mark referenced */ + ieee80211_ref_node(ni); /* mark referenced */ +#ifdef IEEE80211_DEBUG_REFCNT + IEEE80211_DPRINTF(nt->nt_ic, IEEE80211_MSG_NODE, + "%s (%s:%u) %s refcnt %d\n", __func__, func, line, + ether_sprintf(ni->ni_macaddr), + ieee80211_node_refcnt(ni)); +#endif return ni; } } return NULL; } +#ifdef IEEE80211_DEBUG_REFCNT +#define _ieee80211_find_node(nt, mac) \ + _ieee80211_find_node_debug(nt, mac, func, line) +#endif struct ieee80211_node * -ieee80211_find_node(struct ieee80211com *ic, u_int8_t *macaddr) +#ifdef IEEE80211_DEBUG_REFCNT +ieee80211_find_node_debug(struct ieee80211_node_table *nt, + const u_int8_t *macaddr, const char *func, int line) +#else +ieee80211_find_node(struct ieee80211_node_table *nt, const u_int8_t *macaddr) +#endif { struct ieee80211_node *ni; - IEEE80211_NODE_LOCK(ic); - ni = _ieee80211_find_node(ic, macaddr); - IEEE80211_NODE_UNLOCK(ic); + IEEE80211_NODE_LOCK(nt); + ni = _ieee80211_find_node(nt, macaddr); + IEEE80211_NODE_UNLOCK(nt); + return ni; +} + +/* + * Fake up a node; this handles node discovery in adhoc mode. + * Note that for the driver's benefit we we treat this like + * an association so the driver has an opportunity to setup + * it's private state. + */ +struct ieee80211_node * +ieee80211_fakeup_adhoc_node(struct ieee80211_node_table *nt, + const u_int8_t macaddr[IEEE80211_ADDR_LEN]) +{ + struct ieee80211com *ic = nt->nt_ic; + struct ieee80211_node *ni; + + ni = ieee80211_dup_bss(nt, macaddr); + if (ni != NULL) { + /* XXX no rate negotiation; just dup */ + ni->ni_rates = ic->ic_bss->ni_rates; + if (ic->ic_newassoc) + ic->ic_newassoc(ic, ni, 1); + /* XXX not right for 802.1x/WPA */ + ieee80211_node_authorize(ic, ni); + ieee80211_ref_node(ni); /* hold reference */ + } return ni; } /* + * Locate the node for sender, track state, and then pass the + * (referenced) node up to the 802.11 layer for its use. We + * are required to pass some node so we fall back to ic_bss + * when this frame is from an unknown sender. The 802.11 layer + * knows this means the sender wasn't in the node table and + * acts accordingly. + */ +struct ieee80211_node * +#ifdef IEEE80211_DEBUG_REFCNT +ieee80211_find_rxnode_debug(struct ieee80211com *ic, + const struct ieee80211_frame_min *wh, const char *func, int line) +#else +ieee80211_find_rxnode(struct ieee80211com *ic, + const struct ieee80211_frame_min *wh) +#endif +{ +#define IS_CTL(wh) \ + ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) +#define IS_PSPOLL(wh) \ + ((wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PS_POLL) + struct ieee80211_node_table *nt; + struct ieee80211_node *ni; + + /* XXX may want scanned nodes in the neighbor table for adhoc */ + if (ic->ic_opmode == IEEE80211_M_STA || + ic->ic_opmode == IEEE80211_M_MONITOR || + (ic->ic_flags & IEEE80211_F_SCAN)) + nt = &ic->ic_scan; + else + nt = ic->ic_sta; + /* XXX check ic_bss first in station mode */ + /* XXX 4-address frames? */ + IEEE80211_NODE_LOCK(nt); + if (IS_CTL(wh) && !IS_PSPOLL(wh) /*&& !IS_RTS(ah)*/) + ni = _ieee80211_find_node(nt, wh->i_addr1); + else + ni = _ieee80211_find_node(nt, wh->i_addr2); + IEEE80211_NODE_UNLOCK(nt); + + return (ni != NULL ? ni : ieee80211_ref_node(ic->ic_bss)); +#undef IS_PSPOLL +#undef IS_CTL +} + +/* * Return a reference to the appropriate node for sending * a data frame. This handles node discovery in adhoc networks. */ struct ieee80211_node * -ieee80211_find_txnode(struct ieee80211com *ic, u_int8_t *macaddr) +#ifdef IEEE80211_DEBUG_REFCNT +ieee80211_find_txnode_debug(struct ieee80211com *ic, const u_int8_t *macaddr, + const char *func, int line) +#else +ieee80211_find_txnode(struct ieee80211com *ic, const u_int8_t *macaddr) +#endif { + struct ieee80211_node_table *nt = ic->ic_sta; struct ieee80211_node *ni; /* @@ -542,31 +1096,23 @@ ieee80211_find_txnode(struct ieee80211com *ic, u_int8_t *macaddr) * unless we are operating in station mode or this is a * multicast/broadcast frame. */ - if (ic->ic_opmode == IEEE80211_M_STA || IEEE80211_IS_MULTICAST(macaddr)) - return ic->ic_bss; + if (nt == NULL || IEEE80211_IS_MULTICAST(macaddr)) + return ieee80211_ref_node(ic->ic_bss); /* XXX can't hold lock across dup_bss 'cuz of recursive locking */ - IEEE80211_NODE_LOCK(ic); - ni = _ieee80211_find_node(ic, macaddr); - IEEE80211_NODE_UNLOCK(ic); - if (ni == NULL && - (ic->ic_opmode == IEEE80211_M_IBSS || - ic->ic_opmode == IEEE80211_M_AHDEMO)) { - /* - * Fake up a node; this handles node discovery in - * adhoc mode. Note that for the driver's benefit - * we we treat this like an association so the driver - * has an opportunity to setup it's private state. - * - * XXX need better way to handle this; issue probe - * request so we can deduce rate set, etc. - */ - ni = ieee80211_dup_bss(ic, macaddr); - if (ni != NULL) { - /* XXX no rate negotiation; just dup */ - ni->ni_rates = ic->ic_bss->ni_rates; - if (ic->ic_newassoc) - (*ic->ic_newassoc)(ic, ni, 1); + IEEE80211_NODE_LOCK(nt); + ni = _ieee80211_find_node(nt, macaddr); + IEEE80211_NODE_UNLOCK(nt); + + if (ni == NULL) { + if (ic->ic_opmode == IEEE80211_M_IBSS || + ic->ic_opmode == IEEE80211_M_AHDEMO) + ni = ieee80211_fakeup_adhoc_node(nt, macaddr); + else { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_OUTPUT, + "[%s] no node, discard frame (%s)\n", + ether_sprintf(macaddr), __func__); + ic->ic_stats.is_tx_nonode++; } } return ni; @@ -576,117 +1122,775 @@ ieee80211_find_txnode(struct ieee80211com *ic, u_int8_t *macaddr) * Like find but search based on the channel too. */ struct ieee80211_node * -ieee80211_lookup_node(struct ieee80211com *ic, - u_int8_t *macaddr, struct ieee80211_channel *chan) +#ifdef IEEE80211_DEBUG_REFCNT +ieee80211_find_node_with_channel_debug(struct ieee80211_node_table *nt, + const u_int8_t *macaddr, struct ieee80211_channel *chan, + const char *func, int line) +#else +ieee80211_find_node_with_channel(struct ieee80211_node_table *nt, + const u_int8_t *macaddr, struct ieee80211_channel *chan) +#endif { struct ieee80211_node *ni; int hash; hash = IEEE80211_NODE_HASH(macaddr); - IEEE80211_NODE_LOCK(ic); - LIST_FOREACH(ni, &ic->ic_hash[hash], ni_hash) { + IEEE80211_NODE_LOCK(nt); + LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { if (IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr) && ni->ni_chan == chan) { - atomic_add_int(&ni->ni_refcnt, 1);/* mark referenced */ + ieee80211_ref_node(ni); /* mark referenced */ + IEEE80211_DPRINTF(nt->nt_ic, IEEE80211_MSG_NODE, +#ifdef IEEE80211_DEBUG_REFCNT + "%s (%s:%u) %s refcnt %d\n", __func__, func, line, +#else + "%s %s refcnt %d\n", __func__, +#endif + ether_sprintf(ni->ni_macaddr), + ieee80211_node_refcnt(ni)); break; } } - IEEE80211_NODE_UNLOCK(ic); + IEEE80211_NODE_UNLOCK(nt); return ni; } +/* + * Like find but search based on the ssid too. + */ +struct ieee80211_node * +#ifdef IEEE80211_DEBUG_REFCNT +ieee80211_find_node_with_ssid_debug(struct ieee80211_node_table *nt, + const u_int8_t *macaddr, u_int ssidlen, const u_int8_t *ssid, + const char *func, int line) +#else +ieee80211_find_node_with_ssid(struct ieee80211_node_table *nt, + const u_int8_t *macaddr, u_int ssidlen, const u_int8_t *ssid) +#endif +{ + struct ieee80211com *ic = nt->nt_ic; + struct ieee80211_node *ni; + int hash; + + hash = IEEE80211_NODE_HASH(macaddr); + IEEE80211_NODE_LOCK(nt); + LIST_FOREACH(ni, &nt->nt_hash[hash], ni_hash) { + if (IEEE80211_ADDR_EQ(ni->ni_macaddr, macaddr) && + ni->ni_esslen == ic->ic_des_esslen && + memcmp(ni->ni_essid, ic->ic_des_essid, ni->ni_esslen) == 0) { + ieee80211_ref_node(ni); /* mark referenced */ + IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, +#ifdef IEEE80211_DEBUG_REFCNT + "%s (%s:%u) %s refcnt %d\n", __func__, func, line, +#else + "%s %s refcnt %d\n", __func__, +#endif + ether_sprintf(ni->ni_macaddr), + ieee80211_node_refcnt(ni)); + break; + } + } + IEEE80211_NODE_UNLOCK(nt); + return ni; +} + + static void -_ieee80211_free_node(struct ieee80211com *ic, struct ieee80211_node *ni) +_ieee80211_free_node(struct ieee80211_node *ni) { - KASSERT(ni != ic->ic_bss, ("freeing bss node")); + struct ieee80211com *ic = ni->ni_ic; + struct ieee80211_node_table *nt = ni->ni_table; + + IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, + "%s %s in %s table\n", __func__, ether_sprintf(ni->ni_macaddr), + nt != NULL ? nt->nt_name : "<gone>"); - TAILQ_REMOVE(&ic->ic_node, ni, ni_list); - LIST_REMOVE(ni, ni_hash); - if (TAILQ_EMPTY(&ic->ic_node)) - ic->ic_inact_timer = 0; - (*ic->ic_node_free)(ic, ni); + IEEE80211_AID_CLR(ni->ni_associd, ic->ic_aid_bitmap); + if (nt != NULL) { + TAILQ_REMOVE(&nt->nt_node, ni, ni_list); + LIST_REMOVE(ni, ni_hash); + } + ic->ic_node_free(ni); } void -ieee80211_free_node(struct ieee80211com *ic, struct ieee80211_node *ni) +#ifdef IEEE80211_DEBUG_REFCNT +ieee80211_free_node_debug(struct ieee80211_node *ni, const char *func, int line) +#else +ieee80211_free_node(struct ieee80211_node *ni) +#endif { - KASSERT(ni != ic->ic_bss, ("freeing ic_bss")); + struct ieee80211_node_table *nt = ni->ni_table; - /* XXX need equivalent of atomic_dec_and_test */ - atomic_subtract_int(&ni->ni_refcnt, 1); - if (atomic_cmpset_int(&ni->ni_refcnt, 0, 1)) { - IEEE80211_NODE_LOCK(ic); - _ieee80211_free_node(ic, ni); - IEEE80211_NODE_UNLOCK(ic); +#ifdef IEEE80211_DEBUG_REFCNT + IEEE80211_DPRINTF(nt->nt_ic, IEEE80211_MSG_NODE, + "%s (%s:%u) %s refcnt %d\n", __func__, func, line, + ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)-1); +#endif + if (ieee80211_node_dectestref(ni)) { + /* + * Beware; if the node is marked gone then it's already + * been removed from the table and we cannot assume the + * table still exists. Regardless, there's no need to lock + * the table. + */ + if (ni->ni_table != NULL) { + IEEE80211_NODE_LOCK(nt); + _ieee80211_free_node(ni); + IEEE80211_NODE_UNLOCK(nt); + } else + _ieee80211_free_node(ni); } } -void -ieee80211_free_allnodes(struct ieee80211com *ic) +/* + * Reclaim a node. If this is the last reference count then + * do the normal free work. Otherwise remove it from the node + * table and mark it gone by clearing the back-reference. + */ +static void +node_reclaim(struct ieee80211_node_table *nt, struct ieee80211_node *ni) { + + if (!ieee80211_node_dectestref(ni)) { + /* + * Other references are present, just remove the + * node from the table so it cannot be found. When + * the references are dropped storage will be + * reclaimed. This normally only happens for ic_bss. + */ + TAILQ_REMOVE(&nt->nt_node, ni, ni_list); + LIST_REMOVE(ni, ni_hash); + ni->ni_table = NULL; /* clear reference */ + } else + _ieee80211_free_node(ni); +} + +static void +ieee80211_free_allnodes_locked(struct ieee80211_node_table *nt) +{ + struct ieee80211com *ic = nt->nt_ic; struct ieee80211_node *ni; - IEEE80211_NODE_LOCK(ic); - while ((ni = TAILQ_FIRST(&ic->ic_node)) != NULL) - _ieee80211_free_node(ic, ni); - IEEE80211_NODE_UNLOCK(ic); + IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, + "%s: free all nodes in %s table\n", __func__, nt->nt_name); + + while ((ni = TAILQ_FIRST(&nt->nt_node)) != NULL) { + if (ni->ni_associd != 0) { + if (ic->ic_auth->ia_node_leave != NULL) + ic->ic_auth->ia_node_leave(ic, ni); + IEEE80211_AID_CLR(ni->ni_associd, ic->ic_aid_bitmap); + } + node_reclaim(nt, ni); + } + ieee80211_reset_erp(ic); +} + +static void +ieee80211_free_allnodes(struct ieee80211_node_table *nt) +{ + + IEEE80211_NODE_LOCK(nt); + ieee80211_free_allnodes_locked(nt); + IEEE80211_NODE_UNLOCK(nt); +} + +/* + * Timeout entries in the scan cache. + */ +static void +ieee80211_timeout_scan_candidates(struct ieee80211_node_table *nt) +{ + struct ieee80211com *ic = nt->nt_ic; + struct ieee80211_node *ni, *tni; + + IEEE80211_NODE_LOCK(nt); + ni = ic->ic_bss; + /* XXX belongs elsewhere */ + if (ni->ni_rxfrag[0] != NULL && ticks > ni->ni_rxfragstamp + hz) { + m_freem(ni->ni_rxfrag[0]); + ni->ni_rxfrag[0] = NULL; + } + TAILQ_FOREACH_SAFE(ni, &nt->nt_node, ni_list, tni) { + if (ni->ni_inact && --ni->ni_inact == 0) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, + "[%s] scan candidate purged from cache " + "(refcnt %u)\n", ether_sprintf(ni->ni_macaddr), + ieee80211_node_refcnt(ni)-1); + node_reclaim(nt, ni); + } + } + IEEE80211_NODE_UNLOCK(nt); + + nt->nt_inact_timer = IEEE80211_INACT_WAIT; } /* - * Timeout inactive nodes. Note that we cannot hold the node - * lock while sending a frame as this would lead to a LOR. - * Instead we use a generation number to mark nodes that we've - * scanned and drop the lock and restart a scan if we have to - * time out a node. Since we are single-threaded by virtue of + * Timeout inactive stations and do related housekeeping. + * Note that we cannot hold the node lock while sending a + * frame as this would lead to a LOR. Instead we use a + * generation number to mark nodes that we've scanned and + * drop the lock and restart a scan if we have to time out + * a node. Since we are single-threaded by virtue of * controlling the inactivity timer we can be sure this will * process each node only once. */ -void -ieee80211_timeout_nodes(struct ieee80211com *ic) +static void +ieee80211_timeout_stations(struct ieee80211_node_table *nt) { + struct ieee80211com *ic = nt->nt_ic; struct ieee80211_node *ni; - u_int gen = ic->ic_scangen++; /* NB: ok 'cuz single-threaded*/ + u_int gen; + IEEE80211_SCAN_LOCK(nt); + gen = nt->nt_scangen++; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, + "%s: sta scangen %u\n", __func__, gen); restart: - IEEE80211_NODE_LOCK(ic); - TAILQ_FOREACH(ni, &ic->ic_node, ni_list) { + IEEE80211_NODE_LOCK(nt); + TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { if (ni->ni_scangen == gen) /* previously handled */ continue; ni->ni_scangen = gen; - if (++ni->ni_inact > IEEE80211_INACT_MAX) { - IEEE80211_DPRINTF(("station %s timed out " - "due to inactivity (%u secs)\n", - ether_sprintf(ni->ni_macaddr), - ni->ni_inact)); + /* + * Free fragment if not needed anymore + * (last fragment older than 1s). + * XXX doesn't belong here + */ + if (ni->ni_rxfrag[0] != NULL && + ticks > ni->ni_rxfragstamp + hz) { + m_freem(ni->ni_rxfrag[0]); + ni->ni_rxfrag[0] = NULL; + } + ni->ni_inact--; + if (ni->ni_associd != 0) { /* - * Send a deauthenticate frame. + * Age frames on the power save queue. The + * aging interval is 4 times the listen + * interval specified by the station. This + * number is factored into the age calculations + * when the frame is placed on the queue. We + * store ages as time differences we can check + * and/or adjust only the head of the list. + */ + if (IEEE80211_NODE_SAVEQ_QLEN(ni) != 0) { + struct mbuf *m; + int discard = 0; + + IEEE80211_NODE_SAVEQ_LOCK(ni); + while (IF_POLL(&ni->ni_savedq, m) != NULL && + M_AGE_GET(m) < IEEE80211_INACT_WAIT) { +IEEE80211_DPRINTF(ic, IEEE80211_MSG_POWER, "[%s] discard frame, age %u\n", ether_sprintf(ni->ni_macaddr), M_AGE_GET(m));/*XXX*/ + _IEEE80211_NODE_SAVEQ_DEQUEUE_HEAD(ni, m); + m_freem(m); + discard++; + } + if (m != NULL) + M_AGE_SUB(m, IEEE80211_INACT_WAIT); + IEEE80211_NODE_SAVEQ_UNLOCK(ni); + + if (discard != 0) { + IEEE80211_DPRINTF(ic, + IEEE80211_MSG_POWER, + "[%s] discard %u frames for age\n", + ether_sprintf(ni->ni_macaddr), + discard); + IEEE80211_NODE_STAT_ADD(ni, + ps_discard, discard); + if (IEEE80211_NODE_SAVEQ_QLEN(ni) == 0) + ic->ic_set_tim(ic, ni, 0); + } + } + /* + * Probe the station before time it out. We + * send a null data frame which may not be + * universally supported by drivers (need it + * for ps-poll support so it should be...). + */ + if (ni->ni_inact == ic->ic_inact_probe) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, + "[%s] probe station due to inactivity\n", + ether_sprintf(ni->ni_macaddr)); + IEEE80211_NODE_UNLOCK(nt); + ieee80211_send_nulldata(ic, ni); + /* XXX stat? */ + goto restart; + } + } + if (ni->ni_inact <= 0) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, + "[%s] station timed out due to inactivity " + "(refcnt %u)\n", ether_sprintf(ni->ni_macaddr), + ieee80211_node_refcnt(ni)); + /* + * Send a deauthenticate frame and drop the station. + * This is somewhat complicated due to reference counts + * and locking. At this point a station will typically + * have a reference count of 1. ieee80211_node_leave + * will do a "free" of the node which will drop the + * reference count. But in the meantime a reference + * wil be held by the deauth frame. The actual reclaim + * of the node will happen either after the tx is + * completed or by ieee80211_node_leave. * - * Drop the node lock before sending the - * deauthentication frame in case the driver takes - * a lock, as this will result in a LOR between the - * node lock and the driver lock. + * Separately we must drop the node lock before sending + * in case the driver takes a lock, as this will result + * in LOR between the node lock and the driver lock. */ - IEEE80211_NODE_UNLOCK(ic); - IEEE80211_SEND_MGMT(ic, ni, - IEEE80211_FC0_SUBTYPE_DEAUTH, - IEEE80211_REASON_AUTH_EXPIRE); - ieee80211_free_node(ic, ni); + IEEE80211_NODE_UNLOCK(nt); + if (ni->ni_associd != 0) { + IEEE80211_SEND_MGMT(ic, ni, + IEEE80211_FC0_SUBTYPE_DEAUTH, + IEEE80211_REASON_AUTH_EXPIRE); + } + ieee80211_node_leave(ic, ni); ic->ic_stats.is_node_timeout++; goto restart; } } - if (!TAILQ_EMPTY(&ic->ic_node)) - ic->ic_inact_timer = IEEE80211_INACT_WAIT; - IEEE80211_NODE_UNLOCK(ic); + IEEE80211_NODE_UNLOCK(nt); + + IEEE80211_SCAN_UNLOCK(nt); + + nt->nt_inact_timer = IEEE80211_INACT_WAIT; } void -ieee80211_iterate_nodes(struct ieee80211com *ic, ieee80211_iter_func *f, void *arg) +ieee80211_iterate_nodes(struct ieee80211_node_table *nt, ieee80211_iter_func *f, void *arg) { struct ieee80211_node *ni; + u_int gen; + + IEEE80211_SCAN_LOCK(nt); + gen = nt->nt_scangen++; + + IEEE80211_DPRINTF(nt->nt_ic, IEEE80211_MSG_NODE, + "%s: sta scangen %u\n", __func__, gen); +restart: + IEEE80211_NODE_LOCK(nt); + TAILQ_FOREACH(ni, &nt->nt_node, ni_list) { + if (ni->ni_scangen != gen) { + ni->ni_scangen = gen; + (void) ieee80211_ref_node(ni); + IEEE80211_NODE_UNLOCK(nt); + (*f)(arg, ni); + ieee80211_free_node(ni); + goto restart; + } + } + IEEE80211_NODE_UNLOCK(nt); + + IEEE80211_SCAN_UNLOCK(nt); +} + +void +ieee80211_dump_node(struct ieee80211_node_table *nt, struct ieee80211_node *ni) +{ + printf("0x%p: mac %s refcnt %d\n", ni, + ether_sprintf(ni->ni_macaddr), ieee80211_node_refcnt(ni)); + printf("\tscangen %u authmode %u flags 0x%x\n", + ni->ni_scangen, ni->ni_authmode, ni->ni_flags); + printf("\tassocid 0x%x txpower %u vlan %u\n", + ni->ni_associd, ni->ni_txpower, ni->ni_vlan); + printf("\ttxseq %u rxseq %u fragno %u rxfragstamp %u\n", + ni->ni_txseqs[0], + ni->ni_rxseqs[0] >> IEEE80211_SEQ_SEQ_SHIFT, + ni->ni_rxseqs[0] & IEEE80211_SEQ_FRAG_MASK, + ni->ni_rxfragstamp); + printf("\trstamp %u rssi %u intval %u capinfo 0x%x\n", + ni->ni_rstamp, ni->ni_rssi, ni->ni_intval, ni->ni_capinfo); + printf("\tbssid %s essid \"%.*s\" channel %u:0x%x\n", + ether_sprintf(ni->ni_bssid), + ni->ni_esslen, ni->ni_essid, + ni->ni_chan->ic_freq, ni->ni_chan->ic_flags); + printf("\tfails %u inact %u txrate %u\n", + ni->ni_fails, ni->ni_inact, ni->ni_txrate); +} + +void +ieee80211_dump_nodes(struct ieee80211_node_table *nt) +{ + ieee80211_iterate_nodes(nt, + (ieee80211_iter_func *) ieee80211_dump_node, nt); +} + +/* + * Handle a station joining an 11g network. + */ +static void +ieee80211_node_join_11g(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + + /* + * Station isn't capable of short slot time. Bump + * the count of long slot time stations and disable + * use of short slot time. Note that the actual switch + * over to long slot time use may not occur until the + * next beacon transmission (per sec. 7.3.1.4 of 11g). + */ + if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) == 0) { + ic->ic_longslotsta++; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] station needs long slot time, count %d\n", + ether_sprintf(ni->ni_macaddr), ic->ic_longslotsta); + /* XXX vap's w/ conflicting needs won't work */ + ieee80211_set_shortslottime(ic, 0); + } + /* + * If the new station is not an ERP station + * then bump the counter and enable protection + * if configured. + */ + if (!ieee80211_iserp_rateset(ic, &ni->ni_rates)) { + ic->ic_nonerpsta++; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] station is !ERP, %d non-ERP stations associated\n", + ether_sprintf(ni->ni_macaddr), ic->ic_nonerpsta); + /* + * If protection is configured, enable it. + */ + if (ic->ic_protmode != IEEE80211_PROT_NONE) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "%s: enable use of protection\n", __func__); + ic->ic_flags |= IEEE80211_F_USEPROT; + } + /* + * If station does not support short preamble + * then we must enable use of Barker preamble. + */ + if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) == 0) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] station needs long preamble\n", + ether_sprintf(ni->ni_macaddr)); + ic->ic_flags |= IEEE80211_F_USEBARKER; + ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE; + } + } else + ni->ni_flags |= IEEE80211_NODE_ERP; +} + +void +ieee80211_node_join(struct ieee80211com *ic, struct ieee80211_node *ni, int resp) +{ + int newassoc; + + if (ni->ni_associd == 0) { + u_int16_t aid; + + /* + * It would be good to search the bitmap + * more efficiently, but this will do for now. + */ + for (aid = 1; aid < ic->ic_max_aid; aid++) { + if (!IEEE80211_AID_ISSET(aid, + ic->ic_aid_bitmap)) + break; + } + if (aid >= ic->ic_max_aid) { + IEEE80211_SEND_MGMT(ic, ni, resp, + IEEE80211_REASON_ASSOC_TOOMANY); + ieee80211_node_leave(ic, ni); + return; + } + ni->ni_associd = aid | 0xc000; + IEEE80211_AID_SET(ni->ni_associd, ic->ic_aid_bitmap); + ic->ic_sta_assoc++; + newassoc = 1; + if (ic->ic_curmode == IEEE80211_MODE_11G) + ieee80211_node_join_11g(ic, ni); + } else + newassoc = 0; + + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, + "[%s] station %s associated at aid %d\n", + ether_sprintf(ni->ni_macaddr), newassoc ? "newly" : "already", + IEEE80211_NODE_AID(ni)); + + /* give driver a chance to setup state like ni_txrate */ + if (ic->ic_newassoc) + ic->ic_newassoc(ic, ni, newassoc); + ni->ni_inact_reload = ic->ic_inact_run; + IEEE80211_SEND_MGMT(ic, ni, resp, IEEE80211_STATUS_SUCCESS); + /* tell the authenticator about new station */ + if (ic->ic_auth->ia_node_join != NULL) + ic->ic_auth->ia_node_join(ic, ni); + ieee80211_notify_node_join(ic, ni, newassoc); +} + +/* + * Handle a station leaving an 11g network. + */ +static void +ieee80211_node_leave_11g(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + + KASSERT(ic->ic_curmode == IEEE80211_MODE_11G, + ("not in 11g, curmode %x", ic->ic_curmode)); + + /* + * If a long slot station do the slot time bookkeeping. + */ + if ((ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME) == 0) { + KASSERT(ic->ic_longslotsta > 0, + ("bogus long slot station count %d", ic->ic_longslotsta)); + ic->ic_longslotsta--; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] long slot time station leaves, count now %d\n", + ether_sprintf(ni->ni_macaddr), ic->ic_longslotsta); + if (ic->ic_longslotsta == 0) { + /* + * Re-enable use of short slot time if supported + * and not operating in IBSS mode (per spec). + */ + if ((ic->ic_caps & IEEE80211_C_SHSLOT) && + ic->ic_opmode != IEEE80211_M_IBSS) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "%s: re-enable use of short slot time\n", + __func__); + ieee80211_set_shortslottime(ic, 1); + } + } + } + /* + * If a non-ERP station do the protection-related bookkeeping. + */ + if ((ni->ni_flags & IEEE80211_NODE_ERP) == 0) { + KASSERT(ic->ic_nonerpsta > 0, + ("bogus non-ERP station count %d", ic->ic_nonerpsta)); + ic->ic_nonerpsta--; + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "[%s] non-ERP station leaves, count now %d\n", + ether_sprintf(ni->ni_macaddr), ic->ic_nonerpsta); + if (ic->ic_nonerpsta == 0) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "%s: disable use of protection\n", __func__); + ic->ic_flags &= ~IEEE80211_F_USEPROT; + /* XXX verify mode? */ + if (ic->ic_caps & IEEE80211_C_SHPREAMBLE) { + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC, + "%s: re-enable use of short preamble\n", + __func__); + ic->ic_flags |= IEEE80211_F_SHPREAMBLE; + ic->ic_flags &= ~IEEE80211_F_USEBARKER; + } + } + } +} + +/* + * Handle bookkeeping for station deauthentication/disassociation + * when operating as an ap. + */ +void +ieee80211_node_leave(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + + IEEE80211_DPRINTF(ic, IEEE80211_MSG_ASSOC | IEEE80211_MSG_DEBUG, + "[%s] station with aid %d leaves\n", + ether_sprintf(ni->ni_macaddr), IEEE80211_NODE_AID(ni)); + + KASSERT(ic->ic_opmode == IEEE80211_M_HOSTAP || + ic->ic_opmode == IEEE80211_M_IBSS || + ic->ic_opmode == IEEE80211_M_AHDEMO, + ("unexpected operating mode %u", ic->ic_opmode)); + /* + * If node wasn't previously associated all + * we need to do is reclaim the reference. + */ + /* XXX ibss mode bypasses 11g and notification */ + if (ni->ni_associd == 0) + goto done; + /* + * Tell the authenticator the station is leaving. + * Note that we must do this before yanking the + * association id as the authenticator uses the + * associd to locate it's state block. + */ + if (ic->ic_auth->ia_node_leave != NULL) + ic->ic_auth->ia_node_leave(ic, ni); + IEEE80211_AID_CLR(ni->ni_associd, ic->ic_aid_bitmap); + ni->ni_associd = 0; + ic->ic_sta_assoc--; + + if (ic->ic_curmode == IEEE80211_MODE_11G) + ieee80211_node_leave_11g(ic, ni); + /* + * Cleanup station state. In particular clear various + * state that might otherwise be reused if the node + * is reused before the reference count goes to zero + * (and memory is reclaimed). + */ + ieee80211_sta_leave(ic, ni); +done: + ni->ni_inact_reload = ic->ic_inact_init; /* just in case */ + ieee80211_free_node(ni); +} + +u_int8_t +ieee80211_getrssi(struct ieee80211com *ic) +{ +#define NZ(x) ((x) == 0 ? 1 : (x)) + struct ieee80211_node_table *nt = ic->ic_sta; + u_int32_t rssi_samples, rssi_total; + struct ieee80211_node *ni; + + rssi_total = 0; + rssi_samples = 0; + switch (ic->ic_opmode) { + case IEEE80211_M_IBSS: /* average of all ibss neighbors */ + nt = ic->ic_sta; + if (nt == NULL) + break; + /* XXX locking */ + TAILQ_FOREACH(ni, &ic->ic_sta->nt_node, ni_list) + if (ni->ni_capinfo & IEEE80211_CAPINFO_IBSS) { + rssi_samples++; + rssi_total += ic->ic_node_getrssi(ni); + } + break; + case IEEE80211_M_AHDEMO: /* average of all neighbors */ + nt = ic->ic_sta; + if (nt == NULL) + break; + /* XXX locking */ + TAILQ_FOREACH(ni, &ic->ic_sta->nt_node, ni_list) { + rssi_samples++; + rssi_total += ic->ic_node_getrssi(ni); + } + break; + case IEEE80211_M_HOSTAP: /* average of all associated stations */ + nt = ic->ic_sta; + if (nt == NULL) + break; + /* XXX locking */ + TAILQ_FOREACH(ni, &ic->ic_sta->nt_node, ni_list) + if (IEEE80211_AID(ni->ni_associd) != 0) { + rssi_samples++; + rssi_total += ic->ic_node_getrssi(ni); + } + break; + case IEEE80211_M_MONITOR: /* XXX */ + case IEEE80211_M_STA: /* use stats from associated ap */ + default: + if (ic->ic_bss != NULL) + rssi_total = ic->ic_node_getrssi(ic->ic_bss); + rssi_samples = 1; + break; + } + return rssi_total / NZ(rssi_samples); +#undef NZ +} + +/* + * Indicate whether there are frames queued for a station in power-save mode. + */ +static void +ieee80211_set_tim(struct ieee80211com *ic, struct ieee80211_node *ni, int set) +{ + u_int16_t aid; + + KASSERT(ic->ic_opmode == IEEE80211_M_HOSTAP || + ic->ic_opmode == IEEE80211_M_IBSS, + ("operating mode %u", ic->ic_opmode)); + + aid = IEEE80211_AID(ni->ni_associd); + KASSERT(aid < ic->ic_max_aid, + ("bogus aid %u, max %u", aid, ic->ic_max_aid)); + + IEEE80211_BEACON_LOCK(ic); + if (set != (isset(ic->ic_tim_bitmap, aid) != 0)) { + if (set) { + setbit(ic->ic_tim_bitmap, aid); + ic->ic_ps_pending++; + } else { + clrbit(ic->ic_tim_bitmap, aid); + ic->ic_ps_pending--; + } + ic->ic_flags |= IEEE80211_F_TIMUPDATE; + } + IEEE80211_BEACON_UNLOCK(ic); +} + +/* + * Node table support. + */ + +static void +ieee80211_node_table_init(struct ieee80211com *ic, + struct ieee80211_node_table *nt, + const char *name, int inact, + void (*timeout)(struct ieee80211_node_table *)) +{ + + IEEE80211_DPRINTF(ic, IEEE80211_MSG_NODE, + "%s %s table, inact %u\n", __func__, name, inact); + + nt->nt_ic = ic; + /* XXX need unit */ + IEEE80211_NODE_LOCK_INIT(nt, ic->ic_ifp->if_xname); + IEEE80211_SCAN_LOCK_INIT(nt, ic->ic_ifp->if_xname); + TAILQ_INIT(&nt->nt_node); + nt->nt_name = name; + nt->nt_scangen = 1; + nt->nt_inact_init = inact; + nt->nt_timeout = timeout; +} + +static struct ieee80211_node_table * +ieee80211_node_table_alloc(struct ieee80211com *ic, + const char *name, int inact, + void (*timeout)(struct ieee80211_node_table *)) +{ + struct ieee80211_node_table *nt; + + MALLOC(nt, struct ieee80211_node_table *, + sizeof(struct ieee80211_node_table), + M_DEVBUF, M_NOWAIT | M_ZERO); + if (nt == NULL) { + printf("%s: no memory node table!\n", __func__); + return NULL; + } + ieee80211_node_table_init(ic, nt, name, inact, timeout); + return nt; +} + +void +ieee80211_node_table_reset(struct ieee80211_node_table *nt) +{ + + IEEE80211_DPRINTF(nt->nt_ic, IEEE80211_MSG_NODE, + "%s %s table\n", __func__, nt->nt_name); + + IEEE80211_NODE_LOCK(nt); + nt->nt_inact_timer = 0; + ieee80211_free_allnodes_locked(nt); + IEEE80211_NODE_UNLOCK(nt); +} + +static void +ieee80211_node_table_cleanup(struct ieee80211_node_table *nt) +{ + + IEEE80211_DPRINTF(nt->nt_ic, IEEE80211_MSG_NODE, + "%s %s table\n", __func__, nt->nt_name); + + ieee80211_free_allnodes_locked(nt); + IEEE80211_SCAN_LOCK_DESTROY(nt); + IEEE80211_NODE_LOCK_DESTROY(nt); +} + +/* + * NB: public for use in ieee80211_proto.c + */ +void +ieee80211_node_table_free(struct ieee80211_node_table *nt) +{ + + IEEE80211_DPRINTF(nt->nt_ic, IEEE80211_MSG_NODE, + "%s %s table\n", __func__, nt->nt_name); - IEEE80211_NODE_LOCK(ic); - TAILQ_FOREACH(ni, &ic->ic_node, ni_list) - (*f)(arg, ni); - IEEE80211_NODE_UNLOCK(ic); + IEEE80211_NODE_LOCK(nt); + nt->nt_inact_timer = 0; + ieee80211_node_table_cleanup(nt); + FREE(nt, M_DEVBUF); } |