/* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright (c) 2001 Atsushi Onoe * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Alternatively, this software may be distributed under the terms of the * GNU General Public License ("GPL") version 2 as published by the Free * Software Foundation. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Node management routines */ #include "net80211_impl.h" static ieee80211_node_t *ieee80211_node_alloc(ieee80211com_t *); static void ieee80211_node_cleanup(ieee80211_node_t *); static void ieee80211_node_free(ieee80211_node_t *); static uint8_t ieee80211_node_getrssi(const ieee80211_node_t *); static void ieee80211_setup_node(ieee80211com_t *, ieee80211_node_table_t *, ieee80211_node_t *, const uint8_t *); static void ieee80211_node_reclaim(ieee80211_node_table_t *, ieee80211_node_t *); static void ieee80211_free_node_locked(ieee80211_node_t *); static void ieee80211_free_allnodes(ieee80211_node_table_t *); static void ieee80211_node_leave(ieee80211com_t *, ieee80211_node_t *); static void ieee80211_timeout_scan_candidates(ieee80211_node_table_t *); static void ieee80211_timeout_stations(ieee80211_node_table_t *); static void ieee80211_node_table_init(ieee80211com_t *, ieee80211_node_table_t *, const char *, int, int, void (*timeout)(ieee80211_node_table_t *)); static void ieee80211_node_table_cleanup(ieee80211_node_table_t *); /* * association failures before ignored * The failure may be caused by the response frame is lost for * environmental reason. So Try associate more than once before * ignore the node */ #define IEEE80211_STA_FAILS_MAX 2 /* * Initialize node database management callbacks for the interface. * This function is called by ieee80211_attach(). These callback * functions may be overridden in special circumstances, as long as * as this is done after calling ieee80211_attach() and prior to any * other call which may allocate a node */ void ieee80211_node_attach(ieee80211com_t *ic) { struct ieee80211_impl *im = ic->ic_private; ic->ic_node_alloc = ieee80211_node_alloc; ic->ic_node_free = ieee80211_node_free; ic->ic_node_cleanup = ieee80211_node_cleanup; ic->ic_node_getrssi = ieee80211_node_getrssi; /* default station inactivity timer setings */ im->im_inact_init = IEEE80211_INACT_INIT; im->im_inact_assoc = IEEE80211_INACT_ASSOC; im->im_inact_run = IEEE80211_INACT_RUN; im->im_inact_probe = IEEE80211_INACT_PROBE; } /* * Initialize node databases and the ic_bss node element. */ void ieee80211_node_lateattach(ieee80211com_t *ic) { /* * Calculate ic_tim_bitmap size in bytes * IEEE80211_AID_MAX defines maximum bits in ic_tim_bitmap */ ic->ic_tim_len = howmany(IEEE80211_AID_MAX, 8) * sizeof (uint8_t); ieee80211_node_table_init(ic, &ic->ic_sta, "station", IEEE80211_INACT_INIT, IEEE80211_WEP_NKID, ieee80211_timeout_stations); ieee80211_node_table_init(ic, &ic->ic_scan, "scan", IEEE80211_INACT_SCAN, 0, ieee80211_timeout_scan_candidates); ieee80211_reset_bss(ic); } /* * Destroy all node databases and is usually called during device detach */ void ieee80211_node_detach(ieee80211com_t *ic) { /* Node Detach */ if (ic->ic_bss != NULL) { ieee80211_free_node(ic->ic_bss); ic->ic_bss = NULL; } ieee80211_node_table_cleanup(&ic->ic_scan); ieee80211_node_table_cleanup(&ic->ic_sta); } /* * Increase a node's reference count * * Return pointer to the node */ ieee80211_node_t * ieee80211_ref_node(ieee80211_node_t *in) { ieee80211_node_incref(in); return (in); } /* * Dexrease a node's reference count */ void ieee80211_unref_node(ieee80211_node_t **in) { ieee80211_node_decref(*in); *in = NULL; /* guard against use */ } /* * Mark ports authorized for data traffic. This function is usually * used by 802.1x authenticator. */ void ieee80211_node_authorize(ieee80211_node_t *in) { ieee80211_impl_t *im = in->in_ic->ic_private; in->in_flags |= IEEE80211_NODE_AUTH; in->in_inact_reload = im->im_inact_run; } /* * Mark ports unauthorized for data traffic. This function is usually * used by 802.1x authenticator. */ void ieee80211_node_unauthorize(ieee80211_node_t *in) { in->in_flags &= ~IEEE80211_NODE_AUTH; } /* * Set/change the channel. The rate set is also updated as * to insure a consistent view by drivers. */ static void ieee80211_node_setchan(ieee80211com_t *ic, ieee80211_node_t *in, struct ieee80211_channel *chan) { if (chan == IEEE80211_CHAN_ANYC) chan = ic->ic_curchan; in->in_chan = chan; in->in_rates = ic->ic_sup_rates[ieee80211_chan2mode(ic, chan)]; } /* * Initialize the channel set to scan based on the available channels * and the current PHY mode. */ static void ieee80211_reset_scan(ieee80211com_t *ic) { ieee80211_impl_t *im = ic->ic_private; if (ic->ic_des_chan != IEEE80211_CHAN_ANYC) { (void) memset(im->im_chan_scan, 0, sizeof (im->im_chan_scan)); ieee80211_setbit(im->im_chan_scan, ieee80211_chan2ieee(ic, ic->ic_des_chan)); } else { bcopy(ic->ic_chan_active, im->im_chan_scan, sizeof (ic->ic_chan_active)); } ieee80211_dbg(IEEE80211_MSG_SCAN, "ieee80211_reset_scan(): " "start chan %u\n", ieee80211_chan2ieee(ic, ic->ic_curchan)); } /* * Begin an active scan. Initialize the node cache. The scan * begins on the next radio channel by calling ieee80211_next_scan(). * The actual scanning is not automated. The driver itself * only handles setting the radio frequency and stepping through * the channels. */ void ieee80211_begin_scan(ieee80211com_t *ic, boolean_t reset) { IEEE80211_LOCK(ic); if (ic->ic_opmode != IEEE80211_M_HOSTAP) ic->ic_flags |= IEEE80211_F_ASCAN; ieee80211_dbg(IEEE80211_MSG_SCAN, "begin %s scan in %s mode on channel %u\n", (ic->ic_flags & IEEE80211_F_ASCAN) ? "active" : "passive", ieee80211_phymode_name[ic->ic_curmode], ieee80211_chan2ieee(ic, ic->ic_curchan)); /* * Clear scan state and flush any previously seen AP's. */ ieee80211_reset_scan(ic); if (reset) ieee80211_free_allnodes(&ic->ic_scan); ic->ic_flags |= IEEE80211_F_SCAN; IEEE80211_UNLOCK(ic); /* Scan the next channel. */ ieee80211_next_scan(ic); } /* * Switch to the next channel marked for scanning. * A driver is expected to first call ieee80211_begin_scan(), * to initialize the node cache, then set the radio channel * on the device. And then after a certain time has elapsed, * call ieee80211_next_scan() to move to the next channel. * Typically, a timeout routine is used to automate this process. */ void ieee80211_next_scan(ieee80211com_t *ic) { ieee80211_impl_t *im = ic->ic_private; struct ieee80211_channel *chan; IEEE80211_LOCK(ic); /* * 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. */ im->im_mgt_timer = 0; chan = ic->ic_curchan; do { if (++chan > &ic->ic_sup_channels[IEEE80211_CHAN_MAX]) chan = &ic->ic_sup_channels[0]; if (ieee80211_isset(im->im_chan_scan, ieee80211_chan2ieee(ic, chan))) { ieee80211_clrbit(im->im_chan_scan, ieee80211_chan2ieee(ic, chan)); ieee80211_dbg(IEEE80211_MSG_SCAN, "ieee80211_next_scan: chan %d->%d\n", ieee80211_chan2ieee(ic, ic->ic_curchan), ieee80211_chan2ieee(ic, chan)); ic->ic_curchan = chan; /* * drivers should do this as needed, * for now maintain compatibility */ ic->ic_bss->in_rates = ic->ic_sup_rates[ieee80211_chan2mode(ic, chan)]; IEEE80211_UNLOCK(ic); ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); return; } } while (chan != ic->ic_curchan); IEEE80211_UNLOCK(ic); ieee80211_end_scan(ic); } /* * Copy useful state from node obss into nbss. */ static void ieee80211_copy_bss(ieee80211_node_t *nbss, const ieee80211_node_t *obss) { /* propagate useful state */ nbss->in_authmode = obss->in_authmode; nbss->in_txpower = obss->in_txpower; nbss->in_vlan = obss->in_vlan; } /* * Setup the net80211 specific portion of an interface's softc, ic, * for use in IBSS mode */ void ieee80211_create_ibss(ieee80211com_t *ic, struct ieee80211_channel *chan) { ieee80211_impl_t *im = ic->ic_private; ieee80211_node_table_t *nt; ieee80211_node_t *in; IEEE80211_LOCK_ASSERT(ic); ieee80211_dbg(IEEE80211_MSG_SCAN, "ieee80211_create_ibss: " "creating ibss\n"); /* * 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. */ nt = &ic->ic_sta; IEEE80211_NODE_LOCK(nt); nt->nt_name = "neighbor"; nt->nt_inact_init = im->im_inact_run; IEEE80211_NODE_UNLOCK(nt); in = ieee80211_alloc_node(ic, &ic->ic_sta, ic->ic_macaddr); if (in == NULL) { ieee80211_err("ieee80211_create_ibss(): alloc node failed\n"); return; } IEEE80211_ADDR_COPY(in->in_bssid, ic->ic_macaddr); in->in_esslen = ic->ic_des_esslen; (void) memcpy(in->in_essid, ic->ic_des_essid, in->in_esslen); ieee80211_copy_bss(in, ic->ic_bss); in->in_intval = ic->ic_bintval; if (ic->ic_flags & IEEE80211_F_PRIVACY) in->in_capinfo |= IEEE80211_CAPINFO_PRIVACY; if (ic->ic_phytype == IEEE80211_T_FH) { in->in_fhdwell = 200; in->in_fhindex = 1; } switch (ic->ic_opmode) { case IEEE80211_M_IBSS: ic->ic_flags |= IEEE80211_F_SIBSS; in->in_capinfo |= IEEE80211_CAPINFO_IBSS; if (ic->ic_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(in->in_bssid, ic->ic_des_bssid); else in->in_bssid[0] |= 0x02; /* local bit for IBSS */ break; case IEEE80211_M_AHDEMO: if (ic->ic_flags & IEEE80211_F_DESBSSID) IEEE80211_ADDR_COPY(in->in_bssid, ic->ic_des_bssid); else (void) memset(in->in_bssid, 0, IEEE80211_ADDR_LEN); break; default: ieee80211_err("ieee80211_create_ibss(): " "wrong opmode %u to creat IBSS, abort\n", ic->ic_opmode); ieee80211_free_node(in); return; } /* * Fix the channel and related attributes. */ ieee80211_node_setchan(ic, in, chan); ic->ic_curchan = chan; ic->ic_curmode = ieee80211_chan2mode(ic, chan); /* * Do mode-specific rate setup. */ ieee80211_setbasicrates(&in->in_rates, ic->ic_curmode); IEEE80211_UNLOCK(ic); ieee80211_sta_join(ic, in); IEEE80211_LOCK(ic); } void ieee80211_reset_bss(ieee80211com_t *ic) { ieee80211_node_t *in; ieee80211_node_t *obss; in = ieee80211_alloc_node(ic, &ic->ic_scan, ic->ic_macaddr); ASSERT(in != NULL); obss = ic->ic_bss; ic->ic_bss = ieee80211_ref_node(in); if (obss != NULL) { ieee80211_copy_bss(in, obss); in->in_intval = ic->ic_bintval; ieee80211_free_node(obss); } } static int ieee80211_match_bss(ieee80211com_t *ic, ieee80211_node_t *in) { uint8_t rate; int fail; fail = 0; if (ieee80211_isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, in->in_chan))) { fail |= IEEE80211_BADCHAN; } if (ic->ic_des_chan != IEEE80211_CHAN_ANYC && in->in_chan != ic->ic_des_chan) { fail |= IEEE80211_BADCHAN; } if (ic->ic_opmode == IEEE80211_M_IBSS) { if (!(in->in_capinfo & IEEE80211_CAPINFO_IBSS)) fail |= IEEE80211_BADOPMODE; } else { if (!(in->in_capinfo & IEEE80211_CAPINFO_ESS)) fail |= IEEE80211_BADOPMODE; } if (ic->ic_flags & IEEE80211_F_PRIVACY) { if (!(in->in_capinfo & IEEE80211_CAPINFO_PRIVACY)) fail |= IEEE80211_BADPRIVACY; } else { if (in->in_capinfo & IEEE80211_CAPINFO_PRIVACY) fail |= IEEE80211_BADPRIVACY; } rate = ieee80211_fix_rate(in, IEEE80211_F_DONEGO | IEEE80211_F_DOFRATE); if (rate & IEEE80211_RATE_BASIC) fail |= IEEE80211_BADRATE; if (ic->ic_des_esslen != 0 && (in->in_esslen != ic->ic_des_esslen || memcmp(in->in_essid, ic->ic_des_essid, ic->ic_des_esslen) != 0)) { fail |= IEEE80211_BADESSID; } if ((ic->ic_flags & IEEE80211_F_DESBSSID) && !IEEE80211_ADDR_EQ(ic->ic_des_bssid, in->in_bssid)) { fail |= IEEE80211_BADBSSID; } if (in->in_fails >= IEEE80211_STA_FAILS_MAX) fail |= IEEE80211_NODEFAIL; return (fail); } #define IEEE80211_MAXRATE(_rs) \ ((_rs).ir_rates[(_rs).ir_nrates - 1] & IEEE80211_RATE_VAL) /* * Compare the capabilities of node a with node b and decide which is * more desirable (return b if b is considered better than a). 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. * * Return desired node */ static ieee80211_node_t * ieee80211_node_compare(ieee80211com_t *ic, ieee80211_node_t *a, ieee80211_node_t *b) { uint8_t maxa; uint8_t maxb; uint8_t rssia; uint8_t rssib; /* privacy support preferred */ if ((a->in_capinfo & IEEE80211_CAPINFO_PRIVACY) && !(b->in_capinfo & IEEE80211_CAPINFO_PRIVACY)) { return (a); } if (!(a->in_capinfo & IEEE80211_CAPINFO_PRIVACY) && (b->in_capinfo & IEEE80211_CAPINFO_PRIVACY)) { return (b); } /* compare count of previous failures */ if (b->in_fails != a->in_fails) return ((a->in_fails > b->in_fails) ? b : a); rssia = ic->ic_node_getrssi(a); rssib = ic->ic_node_getrssi(b); if (ABS(rssib - rssia) < IEEE80211_RSSI_CMP_THRESHOLD) { /* best/max rate preferred if signal level close enough */ maxa = IEEE80211_MAXRATE(a->in_rates); maxb = IEEE80211_MAXRATE(b->in_rates); if (maxa != maxb) return ((maxb > maxa) ? b : a); /* for now just prefer 5Ghz band to all other bands */ if (IEEE80211_IS_CHAN_5GHZ(a->in_chan) && !IEEE80211_IS_CHAN_5GHZ(b->in_chan)) { return (a); } if (!IEEE80211_IS_CHAN_5GHZ(a->in_chan) && IEEE80211_IS_CHAN_5GHZ(b->in_chan)) { return (b); } } /* all things being equal, compare signal level */ return ((rssib > rssia) ? b : a); } /* * Mark an ongoing scan stopped. */ void ieee80211_cancel_scan(ieee80211com_t *ic) { IEEE80211_LOCK(ic); ieee80211_dbg(IEEE80211_MSG_SCAN, "ieee80211_cancel_scan()" "end %s scan\n", (ic->ic_flags & IEEE80211_F_ASCAN) ? "active" : "passive"); ic->ic_flags &= ~(IEEE80211_F_SCAN | IEEE80211_F_ASCAN); cv_broadcast(&((ieee80211_impl_t *)ic->ic_private)->im_scan_cv); IEEE80211_UNLOCK(ic); } /* * Complete a scan of potential channels. It is called by * ieee80211_next_scan() when the state machine has performed * a full cycle of scaning on all available radio channels. * ieee80211_end_scan() will inspect the node cache for suitable * APs found during scaning, and associate with one, should * the parameters of the node match those of the configuration * requested from userland. */ void ieee80211_end_scan(ieee80211com_t *ic) { ieee80211_node_table_t *nt = &ic->ic_scan; ieee80211_node_t *in; ieee80211_node_t *selbs; ieee80211_cancel_scan(ic); IEEE80211_LOCK(ic); /* * Automatic sequencing; look for a candidate and * if found join the network. */ /* NB: unlocked read should be ok */ in = list_head(&nt->nt_node); if (in == NULL) { ieee80211_dbg(IEEE80211_MSG_SCAN, "ieee80211_end_scan: " "no scan candidate\n"); notfound: if (ic->ic_opmode == IEEE80211_M_IBSS && (ic->ic_flags & IEEE80211_F_IBSSON) && ic->ic_des_esslen != 0) { ieee80211_create_ibss(ic, ic->ic_ibss_chan); IEEE80211_UNLOCK(ic); return; } /* * Reset the list of channels to scan and start again. */ ieee80211_reset_scan(ic); ic->ic_flags |= IEEE80211_F_SCAN | IEEE80211_F_ASCAN; IEEE80211_UNLOCK(ic); ieee80211_next_scan(ic); return; } if (ic->ic_flags & IEEE80211_F_SCANONLY) { /* scan only */ ic->ic_flags &= ~IEEE80211_F_SCANONLY; IEEE80211_UNLOCK(ic); ieee80211_new_state(ic, IEEE80211_S_INIT, -1); return; } selbs = NULL; IEEE80211_NODE_LOCK(nt); while (in != NULL) { if (in->in_fails >= IEEE80211_STA_FAILS_MAX) { ieee80211_node_t *tmpin = in; /* * The configuration of the access points may change * during my scan. So delete the entry for the AP * and retry to associate if there is another beacon. */ in = list_next(&nt->nt_node, tmpin); ieee80211_node_reclaim(nt, tmpin); continue; } if (ieee80211_match_bss(ic, in) == 0) { if (selbs == NULL) selbs = in; else selbs = ieee80211_node_compare(ic, selbs, in); } in = list_next(&nt->nt_node, in); } IEEE80211_NODE_UNLOCK(nt); if (selbs == NULL) goto notfound; IEEE80211_UNLOCK(ic); ieee80211_sta_join(ic, selbs); } /* * 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 B_TRUE if the BSSID changed, B_FALSE otherwise. */ boolean_t ieee80211_ibss_merge(ieee80211_node_t *in) { ieee80211com_t *ic = in->in_ic; if (in == ic->ic_bss || IEEE80211_ADDR_EQ(in->in_bssid, ic->ic_bss->in_bssid)) { /* unchanged, nothing to do */ return (B_FALSE); } if (ieee80211_match_bss(ic, in) != 0) { /* capabilities mismatch */ ieee80211_dbg(IEEE80211_MSG_ASSOC, "ieee80211_ibss_merge: " " merge failed, capabilities mismatch\n"); return (B_FALSE); } ieee80211_dbg(IEEE80211_MSG_ASSOC, "ieee80211_ibss_merge: " "new bssid %s: %s preamble, %s slot time%s\n", ieee80211_macaddr_sprintf(in->in_bssid), (ic->ic_flags & IEEE80211_F_SHPREAMBLE) ? "short" : "long", (ic->ic_flags & IEEE80211_F_SHSLOT) ? "short" : "long", (ic->ic_flags&IEEE80211_F_USEPROT) ? ", protection" : ""); ieee80211_sta_join(ic, in); return (B_TRUE); } /* * Join the specified IBSS/BSS network. The node is assumed to * be passed in with a held reference. */ void ieee80211_sta_join(ieee80211com_t *ic, ieee80211_node_t *selbs) { ieee80211_impl_t *im = ic->ic_private; ieee80211_node_t *obss; IEEE80211_LOCK(ic); if (ic->ic_opmode == IEEE80211_M_IBSS) { ieee80211_node_table_t *nt; /* * Delete unusable rates; we've already checked * that the negotiated rate set is acceptable. */ (void) ieee80211_fix_rate(selbs, IEEE80211_F_DODEL); /* * Fillin the neighbor table */ nt = &ic->ic_sta; IEEE80211_NODE_LOCK(nt); nt->nt_name = "neighbor"; nt->nt_inact_init = im->im_inact_run; IEEE80211_NODE_UNLOCK(nt); } /* * Committed to selbs, setup state. */ obss = ic->ic_bss; ic->ic_bss = ieee80211_ref_node(selbs); /* Grab reference */ if (obss != NULL) { ieee80211_copy_bss(selbs, obss); ieee80211_free_node(obss); } ic->ic_curmode = ieee80211_chan2mode(ic, selbs->in_chan); ic->ic_curchan = selbs->in_chan; /* * 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_UNLOCK(ic); if (ic->ic_opmode == IEEE80211_M_STA) ieee80211_new_state(ic, IEEE80211_S_AUTH, -1); else ieee80211_new_state(ic, IEEE80211_S_RUN, -1); } /* * Leave the specified IBSS/BSS network. The node is assumed to * be passed in with a held reference. */ void ieee80211_sta_leave(ieee80211com_t *ic, ieee80211_node_t *in) { IEEE80211_LOCK(ic); ic->ic_node_cleanup(in); ieee80211_notify_node_leave(ic, in); IEEE80211_UNLOCK(ic); } /* * Allocate a node. This is the default callback function for * ic_node_alloc. This function may be overridden by the driver * to allocate device specific node structure. */ /* ARGSUSED */ static ieee80211_node_t * ieee80211_node_alloc(ieee80211com_t *ic) { return (kmem_zalloc(sizeof (ieee80211_node_t), KM_SLEEP)); } /* * Cleanup a node, free any memory associated with the node. * This is the default callback function for ic_node_cleanup * and may be overridden by the driver. */ static void ieee80211_node_cleanup(ieee80211_node_t *in) { in->in_associd = 0; in->in_rssi = 0; in->in_rstamp = 0; if (in->in_challenge != NULL) { kmem_free(in->in_challenge, IEEE80211_CHALLENGE_LEN); in->in_challenge = NULL; } if (in->in_rxfrag != NULL) { freemsg(in->in_rxfrag); in->in_rxfrag = NULL; } } /* * Free a node. This is the default callback function for ic_node_free * and may be overridden by the driver to free memory used by device * specific node structure */ static void ieee80211_node_free(ieee80211_node_t *in) { ieee80211com_t *ic = in->in_ic; ic->ic_node_cleanup(in); kmem_free(in, sizeof (ieee80211_node_t)); } /* * Get a node current RSSI value. This is the default callback function * for ic_node_getrssi and may be overridden by the driver to provide * device specific RSSI calculation algorithm. */ static uint8_t ieee80211_node_getrssi(const ieee80211_node_t *in) { return (in->in_rssi); } /* Free fragment if not needed anymore */ static void node_cleanfrag(ieee80211_node_t *in) { clock_t ticks; ticks = ddi_get_lbolt(); if (in->in_rxfrag != NULL && ticks > (in->in_rxfragstamp + hz)) { freemsg(in->in_rxfrag); in->in_rxfrag = NULL; } } /* * Setup a node. Initialize the node with specified macaddr. Associate * with the interface softc, ic, and add it to the specified node * database. */ static void ieee80211_setup_node(ieee80211com_t *ic, ieee80211_node_table_t *nt, ieee80211_node_t *in, const uint8_t *macaddr) { int32_t hash; ieee80211_dbg(IEEE80211_MSG_NODE, "ieee80211_setup_node(): " "%p<%s> in %s table\n", in, ieee80211_macaddr_sprintf(macaddr), (nt != NULL) ? nt->nt_name : "NULL"); in->in_ic = ic; IEEE80211_ADDR_COPY(in->in_macaddr, macaddr); hash = ieee80211_node_hash(macaddr); ieee80211_node_initref(in); /* mark referenced */ in->in_authmode = IEEE80211_AUTH_OPEN; in->in_txpower = ic->ic_txpowlimit; /* max power */ in->in_chan = IEEE80211_CHAN_ANYC; in->in_inact_reload = IEEE80211_INACT_INIT; in->in_inact = in->in_inact_reload; ieee80211_crypto_resetkey(ic, &in->in_ucastkey, IEEE80211_KEYIX_NONE); if (nt != NULL) { IEEE80211_NODE_LOCK(nt); list_insert_tail(&nt->nt_node, in); list_insert_tail(&nt->nt_hash[hash], in); in->in_table = nt; in->in_inact_reload = nt->nt_inact_init; IEEE80211_NODE_UNLOCK(nt); } } /* * Allocates and initialize a node with specified MAC address. * Associate the node with the interface ic. If the allocation * is successful, the node structure is initialized by * ieee80211_setup_node(); otherwise, NULL is returned */ ieee80211_node_t * ieee80211_alloc_node(ieee80211com_t *ic, ieee80211_node_table_t *nt, const uint8_t *macaddr) { ieee80211_node_t *in; in = ic->ic_node_alloc(ic); if (in != NULL) ieee80211_setup_node(ic, nt, in, macaddr); return (in); } /* * Craft a temporary node suitable for sending a management frame * to the specified station. We craft only as much state as we * need to do the work since the node will be immediately reclaimed * once the send completes. */ ieee80211_node_t * ieee80211_tmp_node(ieee80211com_t *ic, const uint8_t *macaddr) { ieee80211_node_t *in; in = ic->ic_node_alloc(ic); if (in != NULL) { ieee80211_dbg(IEEE80211_MSG_NODE, "ieee80211_tmp_node: " "%p<%s>\n", in, ieee80211_macaddr_sprintf(macaddr)); IEEE80211_ADDR_COPY(in->in_macaddr, macaddr); IEEE80211_ADDR_COPY(in->in_bssid, ic->ic_bss->in_bssid); ieee80211_node_initref(in); /* mark referenced */ in->in_txpower = ic->ic_bss->in_txpower; /* NB: required by ieee80211_fix_rate */ ieee80211_node_setchan(ic, in, ic->ic_bss->in_chan); ieee80211_crypto_resetkey(ic, &in->in_ucastkey, IEEE80211_KEYIX_NONE); in->in_table = NULL; /* NB: pedantic */ in->in_ic = ic; } return (in); } /* * ieee80211_dup_bss() is similar to ieee80211_alloc_node(), * but is instead used to create a node database entry for * the specified BSSID. If the allocation is successful, the * node is initialized, otherwise, NULL is returned. */ ieee80211_node_t * ieee80211_dup_bss(ieee80211_node_table_t *nt, const uint8_t *macaddr) { ieee80211com_t *ic = nt->nt_ic; ieee80211_node_t *in; in = ieee80211_alloc_node(ic, nt, macaddr); if (in != NULL) { /* * Inherit from ic_bss. */ ieee80211_copy_bss(in, ic->ic_bss); IEEE80211_ADDR_COPY(in->in_bssid, ic->ic_bss->in_bssid); ieee80211_node_setchan(ic, in, ic->ic_bss->in_chan); } return (in); } /* * Iterate through the node table, searching for a node entry which * matches macaddr. If the entry is found, its reference count is * incremented, and a pointer to the node is returned; otherwise, * NULL will be returned. * The node table lock is acquired by the caller. */ static ieee80211_node_t * ieee80211_find_node_locked(ieee80211_node_table_t *nt, const uint8_t *macaddr) { ieee80211_node_t *in; int hash; ASSERT(IEEE80211_NODE_IS_LOCKED(nt)); hash = ieee80211_node_hash(macaddr); in = list_head(&nt->nt_hash[hash]); while (in != NULL) { if (IEEE80211_ADDR_EQ(in->in_macaddr, macaddr)) return (ieee80211_ref_node(in)); /* mark referenced */ in = list_next(&nt->nt_hash[hash], in); } return (NULL); } /* * Iterate through the node table, searching for a node entry * which match specified mac address. * Return NULL if no matching node found. */ ieee80211_node_t * ieee80211_find_node(ieee80211_node_table_t *nt, const uint8_t *macaddr) { ieee80211_node_t *in; IEEE80211_NODE_LOCK(nt); in = ieee80211_find_node_locked(nt, macaddr); IEEE80211_NODE_UNLOCK(nt); return (in); } /* * Fake up a node; this handles node discovery in adhoc mode. * Note that for the driver's benefit we treat this like an * association so the driver has an opportunity to setup it's * private state. */ ieee80211_node_t * ieee80211_fakeup_adhoc_node(ieee80211_node_table_t *nt, const uint8_t *macaddr) { ieee80211com_t *ic = nt->nt_ic; ieee80211_node_t *in; ieee80211_dbg(IEEE80211_MSG_NODE, "ieee80211_fakeup_adhoc_node: " "mac<%s>\n", ieee80211_macaddr_sprintf(macaddr)); in = ieee80211_dup_bss(nt, macaddr); if (in != NULL) { /* no rate negotiation; just dup */ in->in_rates = ic->ic_bss->in_rates; if (ic->ic_node_newassoc != NULL) ic->ic_node_newassoc(in, 1); ieee80211_node_authorize(in); } return (in); } /* * Process a beacon or probe response frame. */ void ieee80211_add_scan(ieee80211com_t *ic, const struct ieee80211_scanparams *sp, const struct ieee80211_frame *wh, int subtype, int rssi, int rstamp) { ieee80211_node_table_t *nt = &ic->ic_scan; ieee80211_node_t *in; boolean_t newnode = B_FALSE; in = ieee80211_find_node(nt, wh->i_addr2); if (in == NULL) { /* * Create a new entry. */ in = ieee80211_alloc_node(ic, nt, wh->i_addr2); if (in == NULL) { ieee80211_dbg(IEEE80211_MSG_ANY, "ieee80211_add_scan: " "alloc node failed\n"); return; } /* * inherit from ic_bss. */ ieee80211_copy_bss(in, ic->ic_bss); ieee80211_node_setchan(ic, in, ic->ic_curchan); newnode = B_TRUE; } /* ap beaconing multiple ssid w/ same bssid */ /* * sp->ssid[0] - element ID * sp->ssid[1] - length * sp->ssid[2]... - ssid */ if (sp->ssid[1] != 0 && subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP || in->in_esslen == 0) { in->in_esslen = sp->ssid[1]; bzero(in->in_essid, sizeof (in->in_essid)); bcopy(sp->ssid + 2, in->in_essid, sp->ssid[1]); } IEEE80211_ADDR_COPY(in->in_bssid, wh->i_addr3); in->in_rssi = (uint8_t)rssi; in->in_rstamp = rstamp; bcopy(sp->tstamp, in->in_tstamp.data, sizeof (in->in_tstamp)); in->in_intval = sp->bintval; in->in_capinfo = sp->capinfo; in->in_chan = &ic->ic_sup_channels[sp->chan]; in->in_phytype = sp->phytype; in->in_fhdwell = sp->fhdwell; in->in_fhindex = sp->fhindex; in->in_erp = sp->erp; if (sp->tim != NULL) { struct ieee80211_tim_ie *ie; ie = (struct ieee80211_tim_ie *)sp->tim; in->in_dtim_count = ie->tim_count; in->in_dtim_period = ie->tim_period; } /* * Record the byte offset from the mac header to * the start of the TIM information element for * use by hardware and/or to speedup software * processing of beacon frames. */ in->in_tim_off = sp->timoff; /* NB: must be after in_chan is setup */ (void) ieee80211_setup_rates(in, sp->rates, sp->xrates, IEEE80211_F_DOSORT); if (!newnode) ieee80211_free_node(in); } /* * Initialize/update an ad-hoc node with contents from a received * beacon frame. */ void ieee80211_init_neighbor(ieee80211_node_t *in, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { in->in_esslen = sp->ssid[1]; (void) memcpy(in->in_essid, sp->ssid + 2, sp->ssid[1]); IEEE80211_ADDR_COPY(in->in_bssid, wh->i_addr3); (void) memcpy(in->in_tstamp.data, sp->tstamp, sizeof (in->in_tstamp)); in->in_intval = sp->bintval; in->in_capinfo = sp->capinfo; in->in_chan = in->in_ic->ic_curchan; in->in_fhdwell = sp->fhdwell; in->in_fhindex = sp->fhindex; in->in_erp = sp->erp; in->in_tim_off = sp->timoff; /* NB: must be after in_chan is setup */ (void) ieee80211_setup_rates(in, sp->rates, sp->xrates, IEEE80211_F_DOSORT); } /* * Do node discovery in adhoc mode on receipt of a beacon * or probe response frame. Note that for the driver's * benefit we we treat this like an association so the * driver has an opportuinty to setup it's private state. */ ieee80211_node_t * ieee80211_add_neighbor(ieee80211com_t *ic, const struct ieee80211_frame *wh, const struct ieee80211_scanparams *sp) { ieee80211_node_t *in; in = ieee80211_dup_bss(&ic->ic_sta, wh->i_addr2); if (in != NULL) { ieee80211_init_neighbor(in, wh, sp); if (ic->ic_node_newassoc != NULL) ic->ic_node_newassoc(in, 1); } return (in); } #define IEEE80211_IS_CTL(wh) \ ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) /* * 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. */ ieee80211_node_t * ieee80211_find_rxnode(ieee80211com_t *ic, const struct ieee80211_frame *wh) { ieee80211_node_table_t *nt; ieee80211_node_t *in; /* may want scanned nodes in the neighbor table for adhoc */ if (ic->ic_opmode == IEEE80211_M_STA || (ic->ic_flags & IEEE80211_F_SCAN)) { nt = &ic->ic_scan; } else { nt = &ic->ic_sta; } IEEE80211_NODE_LOCK(nt); if (IEEE80211_IS_CTL(wh)) in = ieee80211_find_node_locked(nt, wh->i_addr1); else in = ieee80211_find_node_locked(nt, wh->i_addr2); IEEE80211_NODE_UNLOCK(nt); if (in == NULL) in = ieee80211_ref_node(ic->ic_bss); return (in); } /* * Return a reference to the appropriate node for sending * a data frame. This handles node discovery in adhoc networks. */ ieee80211_node_t * ieee80211_find_txnode(ieee80211com_t *ic, const uint8_t *daddr) { ieee80211_node_table_t *nt = &ic->ic_sta; ieee80211_node_t *in; /* * The destination address should be in the node table * unless this is a multicast/broadcast frame. We can * also optimize station mode operation, all frames go * to the bss node. */ IEEE80211_NODE_LOCK(nt); if (ic->ic_opmode == IEEE80211_M_STA || IEEE80211_IS_MULTICAST(daddr)) in = ieee80211_ref_node(ic->ic_bss); else in = ieee80211_find_node_locked(nt, daddr); IEEE80211_NODE_UNLOCK(nt); if (in == NULL) { if (ic->ic_opmode == IEEE80211_M_IBSS) { /* * In adhoc mode cons up a node for the destination. * Note that we need an additional reference for the * caller to be consistent with * ieee80211_find_node_locked * can't hold lock across ieee80211_dup_bss 'cuz of * recursive locking */ in = ieee80211_fakeup_adhoc_node(nt, daddr); if (in != NULL) (void) ieee80211_ref_node(in); } else { ieee80211_dbg(IEEE80211_MSG_OUTPUT, "ieee80211_find_txnode: " "[%s] no node, discard frame\n", ieee80211_macaddr_sprintf(daddr)); } } return (in); } /* * Remove a node from the node database entries and free memory * associated with the node. The node table lock is acquired by * the caller. */ static void ieee80211_free_node_locked(ieee80211_node_t *in) { ieee80211com_t *ic = in->in_ic; ieee80211_node_table_t *nt = in->in_table; int32_t hash; if (nt != NULL) { hash = ieee80211_node_hash(in->in_macaddr); list_remove(&nt->nt_hash[hash], in); list_remove(&nt->nt_node, in); } ic->ic_node_free(in); } /* * Remove a node from the node database entries and free any * memory associated with the node. * This method can be overridden in ieee80211_attach() */ void ieee80211_free_node(ieee80211_node_t *in) { ieee80211_node_table_t *nt = in->in_table; if (nt != NULL) IEEE80211_NODE_LOCK(nt); if (ieee80211_node_decref_nv(in) == 0) ieee80211_free_node_locked(in); if (nt != NULL) IEEE80211_NODE_UNLOCK(nt); } /* * 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 ieee80211_node_reclaim(ieee80211_node_table_t *nt, ieee80211_node_t *in) { int32_t hash; IEEE80211_NODE_LOCK_ASSERT(nt); ieee80211_dbg(IEEE80211_MSG_NODE, "node_reclaim: " " remove %p<%s> from %s table, refcnt %d\n", in, ieee80211_macaddr_sprintf(in->in_macaddr), nt->nt_name, ieee80211_node_refcnt(in)); if (ieee80211_node_decref_nv(in) != 0) { /* * Clear any entry in the unicast key mapping table. * We need to do it here so rx lookups don't find it * in the mapping table even if it's not in the hash * table. We cannot depend on the mapping table entry * being cleared because the node may not be free'd. */ hash = ieee80211_node_hash(in->in_macaddr); list_remove(&nt->nt_hash[hash], in); list_remove(&nt->nt_node, in); in->in_table = NULL; } else { ieee80211_free_node_locked(in); } } /* * Iterate through the node list and reclaim all node in the node table. * The node table lock is acquired by the caller */ static void ieee80211_free_allnodes_locked(ieee80211_node_table_t *nt) { ieee80211_node_t *in; ieee80211_dbg(IEEE80211_MSG_NODE, "ieee80211_free_allnodes_locked(): " "free all nodes in %s table\n", nt->nt_name); in = list_head(&nt->nt_node); while (in != NULL) { ieee80211_node_reclaim(nt, in); in = list_head(&nt->nt_node); } ieee80211_reset_erp(nt->nt_ic); } /* * Iterate through the node list, calling ieee80211_node_reclaim() for * all nodes associated with the interface. */ static void ieee80211_free_allnodes(ieee80211_node_table_t *nt) { IEEE80211_NODE_LOCK(nt); ieee80211_free_allnodes_locked(nt); IEEE80211_NODE_UNLOCK(nt); } /* * Timeout entries in the scan cache. This is the timeout callback * function of node table ic_scan which is called when the inactivity * timer expires. */ static void ieee80211_timeout_scan_candidates(ieee80211_node_table_t *nt) { ieee80211com_t *ic = nt->nt_ic; ieee80211_node_t *in; IEEE80211_NODE_LOCK(nt); in = ic->ic_bss; node_cleanfrag(in); /* Free fragment if not needed */ nt->nt_inact_timer = IEEE80211_INACT_WAIT; IEEE80211_NODE_UNLOCK(nt); } /* * 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. */ static void ieee80211_timeout_stations(ieee80211_node_table_t *nt) { ieee80211com_t *ic = nt->nt_ic; ieee80211_impl_t *im = ic->ic_private; ieee80211_node_t *in = NULL; uint32_t gen; boolean_t isadhoc; IEEE80211_LOCK_ASSERT(ic); isadhoc = (ic->ic_opmode == IEEE80211_M_IBSS || ic->ic_opmode == IEEE80211_M_AHDEMO); IEEE80211_SCAN_LOCK(nt); gen = ++nt->nt_scangen; restart: IEEE80211_NODE_LOCK(nt); for (in = list_head(&nt->nt_node); in != NULL; in = list_next(&nt->nt_node, in)) { if (in->in_scangen == gen) /* previously handled */ continue; in->in_scangen = gen; node_cleanfrag(in); /* free fragment if not needed */ /* * Special case ourself; we may be idle for extended periods * of time and regardless reclaiming our state is wrong. */ if (in == ic->ic_bss) continue; in->in_inact--; if (in->in_associd != 0 || isadhoc) { /* * Probe the station before time it out. We * send a null data frame which may not be * uinversally supported by drivers (need it * for ps-poll support so it should be...). */ if (0 < in->in_inact && in->in_inact <= im->im_inact_probe) { ieee80211_dbg(IEEE80211_MSG_NODE, "net80211: " "probe station due to inactivity\n"); IEEE80211_NODE_UNLOCK(nt); IEEE80211_UNLOCK(ic); (void) ieee80211_send_nulldata(in); IEEE80211_LOCK(ic); goto restart; } } if (in->in_inact <= 0) { ieee80211_dbg(IEEE80211_MSG_NODE, "net80211: " "station timed out due to inact (refcnt %u)\n", ieee80211_node_refcnt(in)); /* * 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. * * 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(nt); if (in->in_associd != 0) { IEEE80211_UNLOCK(ic); IEEE80211_SEND_MGMT(ic, in, IEEE80211_FC0_SUBTYPE_DEAUTH, IEEE80211_REASON_AUTH_EXPIRE); IEEE80211_LOCK(ic); } ieee80211_node_leave(ic, in); goto restart; } } IEEE80211_NODE_UNLOCK(nt); IEEE80211_SCAN_UNLOCK(nt); nt->nt_inact_timer = IEEE80211_INACT_WAIT; } /* * Call the user-defined call back function for all nodes in * the node cache. The callback is invoked with the user-supplied * value and a pointer to the current node. */ void ieee80211_iterate_nodes(ieee80211_node_table_t *nt, ieee80211_iter_func *f, void *arg) { ieee80211_node_t *in; IEEE80211_NODE_LOCK(nt); in = list_head(&nt->nt_node); while (in != NULL) { (void) ieee80211_ref_node(in); IEEE80211_NODE_UNLOCK(nt); (*f)(arg, in); ieee80211_free_node(in); IEEE80211_NODE_LOCK(nt); in = list_next(&nt->nt_node, in); } IEEE80211_NODE_UNLOCK(nt); } /* * Handle bookkeeping for station deauthentication/disassociation * when operating as an ap. */ static void ieee80211_node_leave(ieee80211com_t *ic, ieee80211_node_t *in) { ieee80211_node_table_t *nt = in->in_table; ASSERT(ic->ic_opmode == IEEE80211_M_IBSS); /* * Remove the node from any table it's recorded in and * drop the caller's reference. Removal from the table * is important to insure the node is not reprocessed * for inactivity. */ if (nt != NULL) { IEEE80211_NODE_LOCK(nt); ieee80211_node_reclaim(nt, in); IEEE80211_NODE_UNLOCK(nt); } else { ieee80211_free_node(in); } } /* * Initialize a node table with specified name, inactivity timer value * and callback inactivity timeout function. Associate the node table * with interface softc, ic. */ static void ieee80211_node_table_init(ieee80211com_t *ic, ieee80211_node_table_t *nt, const char *name, int inact, int keyixmax, void (*timeout)(ieee80211_node_table_t *)) { int i; ieee80211_dbg(IEEE80211_MSG_NODE, "ieee80211_node_table_init():" "%s table, inact %d\n", name, inact); nt->nt_ic = ic; nt->nt_name = name; nt->nt_inact_timer = 0; nt->nt_inact_init = inact; nt->nt_timeout = timeout; nt->nt_keyixmax = keyixmax; nt->nt_scangen = 1; mutex_init(&nt->nt_scanlock, NULL, MUTEX_DRIVER, NULL); mutex_init(&nt->nt_nodelock, NULL, MUTEX_DRIVER, NULL); list_create(&nt->nt_node, sizeof (ieee80211_node_t), offsetof(ieee80211_node_t, in_node)); for (i = 0; i < IEEE80211_NODE_HASHSIZE; i++) { list_create(&nt->nt_hash[i], sizeof (ieee80211_node_t), offsetof(ieee80211_node_t, in_hash)); } } /* * Reset a node table. Clean its inactivity timer and call * ieee80211_free_allnodes_locked() to free all nodes in the * node table. */ void ieee80211_node_table_reset(ieee80211_node_table_t *nt) { ieee80211_dbg(IEEE80211_MSG_NODE, "ieee80211_node_table_reset(): " "%s table\n", nt->nt_name); IEEE80211_NODE_LOCK(nt); nt->nt_inact_timer = 0; ieee80211_free_allnodes_locked(nt); IEEE80211_NODE_UNLOCK(nt); } /* * Destroy a node table. Free all nodes in the node table. * This function is usually called by node detach function. */ static void ieee80211_node_table_cleanup(ieee80211_node_table_t *nt) { ieee80211_dbg(IEEE80211_MSG_NODE, "ieee80211_node_table_cleanup(): " "%s table\n", nt->nt_name); IEEE80211_NODE_LOCK(nt); ieee80211_free_allnodes_locked(nt); IEEE80211_NODE_UNLOCK(nt); mutex_destroy(&nt->nt_nodelock); mutex_destroy(&nt->nt_scanlock); }