68e8e04e93
o major overhaul of the way channels are handled: channels are now fully enumerated and uniquely identify the operating characteristics; these changes are visible to user applications which require changes o make scanning support independent of the state machine to enable background scanning and roaming o move scanning support into loadable modules based on the operating mode to enable different policies and reduce the memory footprint on systems w/ constrained resources o add background scanning in station mode (no support for adhoc/ibss mode yet) o significantly speedup sta mode scanning with a variety of techniques o add roaming support when background scanning is supported; for now we use a simple algorithm to trigger a roam: we threshold the rssi and tx rate, if either drops too low we try to roam to a new ap o add tx fragmentation support o add first cut at 802.11n support: this code works with forthcoming drivers but is incomplete; it's included now to establish a baseline for other drivers to be developed and for user applications o adjust max_linkhdr et. al. to reflect 802.11 requirements; this eliminates prepending mbufs for traffic generated locally o add support for Atheros protocol extensions; mainly the fast frames encapsulation (note this can be used with any card that can tx+rx large frames correctly) o add sta support for ap's that beacon both WPA1+2 support o change all data types from bsd-style to posix-style o propagate noise floor data from drivers to net80211 and on to user apps o correct various issues in the sta mode state machine related to handling authentication and association failures o enable the addition of sta mode power save support for drivers that need net80211 support (not in this commit) o remove old WI compatibility ioctls (wicontrol is officially dead) o change the data structures returned for get sta info and get scan results so future additions will not break user apps o fixed tx rate is now maintained internally as an ieee rate and not an index into the rate set; this needs to be extended to deal with multi-mode operation o add extended channel specifications to radiotap to enable 11n sniffing Drivers: o ath: add support for bg scanning, tx fragmentation, fast frames, dynamic turbo (lightly tested), 11n (sniffing only and needs new hal) o awi: compile tested only o ndis: lightly tested o ipw: lightly tested o iwi: add support for bg scanning (well tested but may have some rough edges) o ral, ural, rum: add suppoort for bg scanning, calibrate rssi data o wi: lightly tested This work is based on contributions by Atheros, kmacy, sephe, thompsa, mlaier, kevlo, and others. Much of the scanning work was supported by Atheros. The 11n work was supported by Marvell.
1439 lines
39 KiB
C
1439 lines
39 KiB
C
/*-
|
|
* Copyright (c) 2002-2007 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.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* IEEE 802.11 station scanning support.
|
|
*/
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/module.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_media.h>
|
|
#include <net/ethernet.h>
|
|
|
|
#include <net80211/ieee80211_var.h>
|
|
|
|
#include <net/bpf.h>
|
|
|
|
/*
|
|
* Parameters for managing cache entries:
|
|
*
|
|
* o a station with STA_FAILS_MAX failures is not considered
|
|
* when picking a candidate
|
|
* o a station that hasn't had an update in STA_PURGE_SCANS
|
|
* (background) scans is discarded
|
|
* o after STA_FAILS_AGE seconds we clear the failure count
|
|
*/
|
|
#define STA_FAILS_MAX 2 /* assoc failures before ignored */
|
|
#define STA_FAILS_AGE (2*60) /* time before clearing fails (secs) */
|
|
#define STA_PURGE_SCANS 2 /* age for purging entries (scans) */
|
|
|
|
/* XXX tunable */
|
|
#define STA_RSSI_MIN 8 /* min acceptable rssi */
|
|
#define STA_RSSI_MAX 40 /* max rssi for comparison */
|
|
|
|
#define RSSI_LPF_LEN 10
|
|
#define RSSI_EP_MULTIPLIER (1<<7) /* pow2 to optimize out * and / */
|
|
#define RSSI_IN(x) ((x) * RSSI_EP_MULTIPLIER)
|
|
#define LPF_RSSI(x, y, len) (((x) * ((len) - 1) + (y)) / (len))
|
|
#define RSSI_LPF(x, y) do { \
|
|
if ((y) >= -20) \
|
|
x = LPF_RSSI((x), RSSI_IN((y)), RSSI_LPF_LEN); \
|
|
} while (0)
|
|
#define EP_RND(x, mul) \
|
|
((((x)%(mul)) >= ((mul)/2)) ? howmany(x, mul) : (x)/(mul))
|
|
#define RSSI_GET(x) EP_RND(x, RSSI_EP_MULTIPLIER)
|
|
|
|
struct sta_entry {
|
|
struct ieee80211_scan_entry base;
|
|
TAILQ_ENTRY(sta_entry) se_list;
|
|
LIST_ENTRY(sta_entry) se_hash;
|
|
uint8_t se_fails; /* failure to associate count */
|
|
uint8_t se_seen; /* seen during current scan */
|
|
uint8_t se_notseen; /* not seen in previous scans */
|
|
uint8_t se_flags;
|
|
#define STA_SSID_MATCH 0x01
|
|
#define STA_BSSID_MATCH 0x02
|
|
uint32_t se_avgrssi; /* LPF rssi state */
|
|
unsigned long se_lastupdate; /* time of last update */
|
|
unsigned long se_lastfail; /* time of last failure */
|
|
unsigned long se_lastassoc; /* time of last association */
|
|
u_int se_scangen; /* iterator scan gen# */
|
|
};
|
|
|
|
#define STA_HASHSIZE 32
|
|
/* simple hash is enough for variation of macaddr */
|
|
#define STA_HASH(addr) \
|
|
(((const uint8_t *)(addr))[IEEE80211_ADDR_LEN - 1] % STA_HASHSIZE)
|
|
|
|
struct sta_table {
|
|
struct mtx st_lock; /* on scan table */
|
|
TAILQ_HEAD(, sta_entry) st_entry; /* all entries */
|
|
LIST_HEAD(, sta_entry) st_hash[STA_HASHSIZE];
|
|
struct mtx st_scanlock; /* on st_scangen */
|
|
u_int st_scangen; /* gen# for iterator */
|
|
int st_newscan;
|
|
};
|
|
|
|
static void sta_flush_table(struct sta_table *);
|
|
static int match_bss(struct ieee80211com *,
|
|
const struct ieee80211_scan_state *, struct sta_entry *, int);
|
|
|
|
/* number of references from net80211 layer */
|
|
static int nrefs = 0;
|
|
|
|
/*
|
|
* Attach prior to any scanning work.
|
|
*/
|
|
static int
|
|
sta_attach(struct ieee80211_scan_state *ss)
|
|
{
|
|
struct sta_table *st;
|
|
|
|
MALLOC(st, struct sta_table *, sizeof(struct sta_table),
|
|
M_80211_SCAN, M_NOWAIT | M_ZERO);
|
|
if (st == NULL)
|
|
return 0;
|
|
mtx_init(&st->st_lock, "scantable", "802.11 scan table", MTX_DEF);
|
|
mtx_init(&st->st_scanlock, "scangen", "802.11 scangen", MTX_DEF);
|
|
TAILQ_INIT(&st->st_entry);
|
|
ss->ss_priv = st;
|
|
nrefs++; /* NB: we assume caller locking */
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Cleanup any private state.
|
|
*/
|
|
static int
|
|
sta_detach(struct ieee80211_scan_state *ss)
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
|
|
if (st != NULL) {
|
|
sta_flush_table(st);
|
|
mtx_destroy(&st->st_lock);
|
|
mtx_destroy(&st->st_scanlock);
|
|
FREE(st, M_80211_SCAN);
|
|
KASSERT(nrefs > 0, ("imbalanced attach/detach"));
|
|
nrefs--; /* NB: we assume caller locking */
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Flush all per-scan state.
|
|
*/
|
|
static int
|
|
sta_flush(struct ieee80211_scan_state *ss)
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
|
|
mtx_lock(&st->st_lock);
|
|
sta_flush_table(st);
|
|
mtx_unlock(&st->st_lock);
|
|
ss->ss_last = 0;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Flush all entries in the scan cache.
|
|
*/
|
|
static void
|
|
sta_flush_table(struct sta_table *st)
|
|
{
|
|
struct sta_entry *se, *next;
|
|
|
|
TAILQ_FOREACH_SAFE(se, &st->st_entry, se_list, next) {
|
|
TAILQ_REMOVE(&st->st_entry, se, se_list);
|
|
LIST_REMOVE(se, se_hash);
|
|
FREE(se, M_80211_SCAN);
|
|
}
|
|
}
|
|
|
|
static void
|
|
saveie(uint8_t **iep, const uint8_t *ie)
|
|
{
|
|
|
|
if (ie == NULL)
|
|
*iep = NULL;
|
|
else
|
|
ieee80211_saveie(iep, ie);
|
|
}
|
|
|
|
/*
|
|
* Process a beacon or probe response frame; create an
|
|
* entry in the scan cache or update any previous entry.
|
|
*/
|
|
static int
|
|
sta_add(struct ieee80211_scan_state *ss,
|
|
const struct ieee80211_scanparams *sp,
|
|
const struct ieee80211_frame *wh,
|
|
int subtype, int rssi, int noise, int rstamp)
|
|
{
|
|
#define ISPROBE(_st) ((_st) == IEEE80211_FC0_SUBTYPE_PROBE_RESP)
|
|
#define PICK1ST(_ss) \
|
|
((ss->ss_flags & (IEEE80211_SCAN_PICK1ST | IEEE80211_SCAN_GOTPICK)) == \
|
|
IEEE80211_SCAN_PICK1ST)
|
|
struct sta_table *st = ss->ss_priv;
|
|
const uint8_t *macaddr = wh->i_addr2;
|
|
struct ieee80211com *ic = ss->ss_ic;
|
|
struct sta_entry *se;
|
|
struct ieee80211_scan_entry *ise;
|
|
int hash;
|
|
|
|
hash = STA_HASH(macaddr);
|
|
|
|
mtx_lock(&st->st_lock);
|
|
LIST_FOREACH(se, &st->st_hash[hash], se_hash)
|
|
if (IEEE80211_ADDR_EQ(se->base.se_macaddr, macaddr))
|
|
goto found;
|
|
MALLOC(se, struct sta_entry *, sizeof(struct sta_entry),
|
|
M_80211_SCAN, M_NOWAIT | M_ZERO);
|
|
if (se == NULL) {
|
|
mtx_unlock(&st->st_lock);
|
|
return 0;
|
|
}
|
|
se->se_scangen = st->st_scangen-1;
|
|
IEEE80211_ADDR_COPY(se->base.se_macaddr, macaddr);
|
|
TAILQ_INSERT_TAIL(&st->st_entry, se, se_list);
|
|
LIST_INSERT_HEAD(&st->st_hash[hash], se, se_hash);
|
|
found:
|
|
ise = &se->base;
|
|
/* XXX ap beaconing multiple ssid w/ same bssid */
|
|
if (sp->ssid[1] != 0 &&
|
|
(ISPROBE(subtype) || ise->se_ssid[1] == 0))
|
|
memcpy(ise->se_ssid, sp->ssid, 2+sp->ssid[1]);
|
|
KASSERT(sp->rates[1] <= IEEE80211_RATE_MAXSIZE,
|
|
("rate set too large: %u", sp->rates[1]));
|
|
memcpy(ise->se_rates, sp->rates, 2+sp->rates[1]);
|
|
if (sp->xrates != NULL) {
|
|
/* XXX validate xrates[1] */
|
|
KASSERT(sp->xrates[1] + sp->rates[1] <= IEEE80211_RATE_MAXSIZE,
|
|
("xrate set too large: %u", sp->xrates[1]));
|
|
memcpy(ise->se_xrates, sp->xrates, 2+sp->xrates[1]);
|
|
} else
|
|
ise->se_xrates[1] = 0;
|
|
IEEE80211_ADDR_COPY(ise->se_bssid, wh->i_addr3);
|
|
/*
|
|
* Record rssi data using extended precision LPF filter.
|
|
*/
|
|
if (se->se_lastupdate == 0) /* first sample */
|
|
se->se_avgrssi = RSSI_IN(rssi);
|
|
else /* avg w/ previous samples */
|
|
RSSI_LPF(se->se_avgrssi, rssi);
|
|
se->base.se_rssi = RSSI_GET(se->se_avgrssi);
|
|
se->base.se_noise = noise;
|
|
ise->se_rstamp = rstamp;
|
|
memcpy(ise->se_tstamp.data, sp->tstamp, sizeof(ise->se_tstamp));
|
|
ise->se_intval = sp->bintval;
|
|
ise->se_capinfo = sp->capinfo;
|
|
ise->se_chan = ic->ic_curchan;
|
|
ise->se_fhdwell = sp->fhdwell;
|
|
ise->se_fhindex = sp->fhindex;
|
|
ise->se_erp = sp->erp;
|
|
ise->se_timoff = sp->timoff;
|
|
if (sp->tim != NULL) {
|
|
const struct ieee80211_tim_ie *tim =
|
|
(const struct ieee80211_tim_ie *) sp->tim;
|
|
ise->se_dtimperiod = tim->tim_period;
|
|
}
|
|
saveie(&ise->se_wme_ie, sp->wme);
|
|
saveie(&ise->se_wpa_ie, sp->wpa);
|
|
saveie(&ise->se_rsn_ie, sp->rsn);
|
|
saveie(&ise->se_ath_ie, sp->ath);
|
|
saveie(&ise->se_htcap_ie, sp->htcap);
|
|
saveie(&ise->se_htinfo_ie, sp->htinfo);
|
|
|
|
/* clear failure count after STA_FAIL_AGE passes */
|
|
if (se->se_fails && (ticks - se->se_lastfail) > STA_FAILS_AGE*hz) {
|
|
se->se_fails = 0;
|
|
IEEE80211_NOTE_MAC(ic, IEEE80211_MSG_SCAN, macaddr,
|
|
"%s: fails %u", __func__, se->se_fails);
|
|
}
|
|
|
|
se->se_lastupdate = ticks; /* update time */
|
|
se->se_seen = 1;
|
|
se->se_notseen = 0;
|
|
|
|
mtx_unlock(&st->st_lock);
|
|
|
|
/*
|
|
* If looking for a quick choice and nothing's
|
|
* been found check here.
|
|
*/
|
|
if (PICK1ST(ss) && match_bss(ic, ss, se, IEEE80211_MSG_SCAN) == 0)
|
|
ss->ss_flags |= IEEE80211_SCAN_GOTPICK;
|
|
|
|
return 1;
|
|
#undef PICK1ST
|
|
#undef ISPROBE
|
|
}
|
|
|
|
/*
|
|
* Check if a channel is excluded by user request.
|
|
*/
|
|
static int
|
|
isexcluded(struct ieee80211com *ic, const struct ieee80211_channel *c)
|
|
{
|
|
return (isclr(ic->ic_chan_active, c->ic_ieee) ||
|
|
(ic->ic_des_chan != IEEE80211_CHAN_ANYC &&
|
|
c->ic_freq != ic->ic_des_chan->ic_freq));
|
|
}
|
|
|
|
static struct ieee80211_channel *
|
|
find11gchannel(struct ieee80211com *ic, int i, int freq)
|
|
{
|
|
struct ieee80211_channel *c;
|
|
int j;
|
|
|
|
/*
|
|
* The normal ordering in the channel list is b channel
|
|
* immediately followed by g so optimize the search for
|
|
* this. We'll still do a full search just in case.
|
|
*/
|
|
for (j = i+1; j < ic->ic_nchans; j++) {
|
|
c = &ic->ic_channels[j];
|
|
if (c->ic_freq == freq && IEEE80211_IS_CHAN_ANYG(c))
|
|
return c;
|
|
}
|
|
for (j = 0; j < i; j++) {
|
|
c = &ic->ic_channels[j];
|
|
if (c->ic_freq == freq && IEEE80211_IS_CHAN_ANYG(c))
|
|
return c;
|
|
}
|
|
return NULL;
|
|
}
|
|
static const u_int chanflags[IEEE80211_MODE_MAX] = {
|
|
IEEE80211_CHAN_B, /* IEEE80211_MODE_AUTO */
|
|
IEEE80211_CHAN_A, /* IEEE80211_MODE_11A */
|
|
IEEE80211_CHAN_B, /* IEEE80211_MODE_11B */
|
|
IEEE80211_CHAN_G, /* IEEE80211_MODE_11G */
|
|
IEEE80211_CHAN_FHSS, /* IEEE80211_MODE_FH */
|
|
IEEE80211_CHAN_A, /* IEEE80211_MODE_TURBO_A (check base channel)*/
|
|
IEEE80211_CHAN_G, /* IEEE80211_MODE_TURBO_G */
|
|
IEEE80211_CHAN_ST, /* IEEE80211_MODE_STURBO_A */
|
|
IEEE80211_CHAN_A, /* IEEE80211_MODE_11NA (check legacy) */
|
|
IEEE80211_CHAN_G, /* IEEE80211_MODE_11NG (check legacy) */
|
|
};
|
|
|
|
static void
|
|
add_channels(struct ieee80211com *ic,
|
|
struct ieee80211_scan_state *ss,
|
|
enum ieee80211_phymode mode, const uint16_t freq[], int nfreq)
|
|
{
|
|
#define N(a) (sizeof(a) / sizeof(a[0]))
|
|
struct ieee80211_channel *c, *cg;
|
|
u_int modeflags;
|
|
int i;
|
|
|
|
KASSERT(mode < N(chanflags), ("Unexpected mode %u", mode));
|
|
modeflags = chanflags[mode];
|
|
for (i = 0; i < nfreq; i++) {
|
|
if (ss->ss_last >= IEEE80211_SCAN_MAX)
|
|
break;
|
|
|
|
c = ieee80211_find_channel(ic, freq[i], modeflags);
|
|
if (c == NULL || isexcluded(ic, c))
|
|
continue;
|
|
if (mode == IEEE80211_MODE_AUTO) {
|
|
/*
|
|
* XXX special-case 11b/g channels so we select
|
|
* the g channel if both are present.
|
|
*/
|
|
if (IEEE80211_IS_CHAN_B(c) &&
|
|
(cg = find11gchannel(ic, i, c->ic_freq)) != NULL)
|
|
c = cg;
|
|
}
|
|
ss->ss_chans[ss->ss_last++] = c;
|
|
}
|
|
#undef N
|
|
}
|
|
|
|
static const uint16_t rcl1[] = /* 8 FCC channel: 52, 56, 60, 64, 36, 40, 44, 48 */
|
|
{ 5260, 5280, 5300, 5320, 5180, 5200, 5220, 5240 };
|
|
static const uint16_t rcl2[] = /* 4 MKK channels: 34, 38, 42, 46 */
|
|
{ 5170, 5190, 5210, 5230 };
|
|
static const uint16_t rcl3[] = /* 2.4Ghz ch: 1,6,11,7,13 */
|
|
{ 2412, 2437, 2462, 2442, 2472 };
|
|
static const uint16_t rcl4[] = /* 5 FCC channel: 149, 153, 161, 165 */
|
|
{ 5745, 5765, 5785, 5805, 5825 };
|
|
static const uint16_t rcl7[] = /* 11 ETSI channel: 100,104,108,112,116,120,124,128,132,136,140 */
|
|
{ 5500, 5520, 5540, 5560, 5580, 5600, 5620, 5640, 5660, 5680, 5700 };
|
|
static const uint16_t rcl8[] = /* 2.4Ghz ch: 2,3,4,5,8,9,10,12 */
|
|
{ 2417, 2422, 2427, 2432, 2447, 2452, 2457, 2467 };
|
|
static const uint16_t rcl9[] = /* 2.4Ghz ch: 14 */
|
|
{ 2484 };
|
|
static const uint16_t rcl10[] = /* Added Korean channels 2312-2372 */
|
|
{ 2312, 2317, 2322, 2327, 2332, 2337, 2342, 2347, 2352, 2357, 2362, 2367, 2372 };
|
|
static const uint16_t rcl11[] = /* Added Japan channels in 4.9/5.0 spectrum */
|
|
{ 5040, 5060, 5080, 4920, 4940, 4960, 4980 };
|
|
#ifdef ATH_TURBO_SCAN
|
|
static const uint16_t rcl5[] = /* 3 static turbo channels */
|
|
{ 5210, 5250, 5290 };
|
|
static const uint16_t rcl6[] = /* 2 static turbo channels */
|
|
{ 5760, 5800 };
|
|
static const uint16_t rcl6x[] = /* 4 FCC3 turbo channels */
|
|
{ 5540, 5580, 5620, 5660 };
|
|
static const uint16_t rcl12[] = /* 2.4Ghz Turbo channel 6 */
|
|
{ 2437 };
|
|
static const uint16_t rcl13[] = /* dynamic Turbo channels */
|
|
{ 5200, 5240, 5280, 5765, 5805 };
|
|
#endif /* ATH_TURBO_SCAN */
|
|
|
|
struct scanlist {
|
|
uint16_t mode;
|
|
uint16_t count;
|
|
const uint16_t *list;
|
|
};
|
|
|
|
#define X(a) .count = sizeof(a)/sizeof(a[0]), .list = a
|
|
|
|
static const struct scanlist staScanTable[] = {
|
|
{ IEEE80211_MODE_11B, X(rcl3) },
|
|
{ IEEE80211_MODE_11A, X(rcl1) },
|
|
{ IEEE80211_MODE_11A, X(rcl2) },
|
|
{ IEEE80211_MODE_11B, X(rcl8) },
|
|
{ IEEE80211_MODE_11B, X(rcl9) },
|
|
{ IEEE80211_MODE_11A, X(rcl4) },
|
|
#ifdef ATH_TURBO_SCAN
|
|
{ IEEE80211_MODE_STURBO_A, X(rcl5) },
|
|
{ IEEE80211_MODE_STURBO_A, X(rcl6) },
|
|
{ IEEE80211_MODE_TURBO_A, X(rcl6x) },
|
|
{ IEEE80211_MODE_TURBO_A, X(rcl13) },
|
|
#endif /* ATH_TURBO_SCAN */
|
|
{ IEEE80211_MODE_11A, X(rcl7) },
|
|
{ IEEE80211_MODE_11B, X(rcl10) },
|
|
{ IEEE80211_MODE_11A, X(rcl11) },
|
|
#ifdef ATH_TURBO_SCAN
|
|
{ IEEE80211_MODE_TURBO_G, X(rcl12) },
|
|
#endif /* ATH_TURBO_SCAN */
|
|
{ .list = NULL }
|
|
};
|
|
|
|
static int
|
|
checktable(const struct scanlist *scan, const struct ieee80211_channel *c)
|
|
{
|
|
int i;
|
|
|
|
for (; scan->list != NULL; scan++) {
|
|
for (i = 0; i < scan->count; i++)
|
|
if (scan->list[i] == c->ic_freq)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Start a station-mode scan by populating the channel list.
|
|
*/
|
|
static int
|
|
sta_start(struct ieee80211_scan_state *ss, struct ieee80211com *ic)
|
|
{
|
|
#define N(a) (sizeof(a)/sizeof(a[0]))
|
|
struct sta_table *st = ss->ss_priv;
|
|
const struct scanlist *scan;
|
|
enum ieee80211_phymode mode;
|
|
struct ieee80211_channel *c;
|
|
int i;
|
|
|
|
ss->ss_last = 0;
|
|
/*
|
|
* Use the table of ordered channels to construct the list
|
|
* of channels for scanning. Any channels in the ordered
|
|
* list not in the master list will be discarded.
|
|
*/
|
|
for (scan = staScanTable; scan->list != NULL; scan++) {
|
|
mode = scan->mode;
|
|
if (ic->ic_des_mode != IEEE80211_MODE_AUTO) {
|
|
/*
|
|
* If a desired mode was specified, scan only
|
|
* channels that satisfy that constraint.
|
|
*/
|
|
if (ic->ic_des_mode != mode) {
|
|
/*
|
|
* The scan table marks 2.4Ghz channels as b
|
|
* so if the desired mode is 11g, then use
|
|
* the 11b channel list but upgrade the mode.
|
|
*/
|
|
if (ic->ic_des_mode != IEEE80211_MODE_11G ||
|
|
mode != IEEE80211_MODE_11B)
|
|
continue;
|
|
mode = IEEE80211_MODE_11G; /* upgrade */
|
|
}
|
|
} else {
|
|
/*
|
|
* This lets add_channels upgrade an 11b channel
|
|
* to 11g if available.
|
|
*/
|
|
if (mode == IEEE80211_MODE_11B)
|
|
mode = IEEE80211_MODE_AUTO;
|
|
}
|
|
#ifdef IEEE80211_F_XR
|
|
/* XR does not operate on turbo channels */
|
|
if ((ic->ic_flags & IEEE80211_F_XR) &&
|
|
(mode == IEEE80211_MODE_TURBO_A ||
|
|
mode == IEEE80211_MODE_TURBO_G ||
|
|
mode == IEEE80211_MODE_STURBO_A))
|
|
continue;
|
|
#endif
|
|
/*
|
|
* Add the list of the channels; any that are not
|
|
* in the master channel list will be discarded.
|
|
*/
|
|
add_channels(ic, ss, mode, scan->list, scan->count);
|
|
}
|
|
|
|
/*
|
|
* Add the channels from the ic (from HAL) that are not present
|
|
* in the staScanTable.
|
|
*/
|
|
for (i = 0; i < ic->ic_nchans; i++) {
|
|
if (ss->ss_last >= IEEE80211_SCAN_MAX)
|
|
break;
|
|
|
|
c = &ic->ic_channels[i];
|
|
/*
|
|
* Ignore dynamic turbo channels; we scan them
|
|
* in normal mode (i.e. not boosted). Likewise
|
|
* for HT channels, they get scanned using
|
|
* legacy rates.
|
|
*/
|
|
if (IEEE80211_IS_CHAN_DTURBO(c) || IEEE80211_IS_CHAN_HT(c))
|
|
continue;
|
|
|
|
/*
|
|
* If a desired mode was specified, scan only
|
|
* channels that satisfy that constraint.
|
|
*/
|
|
if (ic->ic_des_mode != IEEE80211_MODE_AUTO &&
|
|
ic->ic_des_mode != ieee80211_chan2mode(c))
|
|
continue;
|
|
|
|
/*
|
|
* Skip channels excluded by user request.
|
|
*/
|
|
if (isexcluded(ic, c))
|
|
continue;
|
|
|
|
/*
|
|
* Add the channel unless it is listed in the
|
|
* fixed scan order tables. This insures we
|
|
* don't sweep back in channels we filtered out
|
|
* above.
|
|
*/
|
|
if (checktable(staScanTable, c))
|
|
continue;
|
|
|
|
/* Add channel to scanning list. */
|
|
ss->ss_chans[ss->ss_last++] = c;
|
|
}
|
|
|
|
ss->ss_next = 0;
|
|
/* XXX tunables */
|
|
ss->ss_mindwell = msecs_to_ticks(20); /* 20ms */
|
|
ss->ss_maxdwell = msecs_to_ticks(200); /* 200ms */
|
|
|
|
#ifdef IEEE80211_DEBUG
|
|
if (ieee80211_msg_scan(ic)) {
|
|
if_printf(ic->ic_ifp, "scan set ");
|
|
ieee80211_scan_dump_channels(ss);
|
|
printf(" dwell min %ld max %ld\n",
|
|
ss->ss_mindwell, ss->ss_maxdwell);
|
|
}
|
|
#endif /* IEEE80211_DEBUG */
|
|
|
|
st->st_newscan = 1;
|
|
|
|
return 0;
|
|
#undef N
|
|
}
|
|
|
|
/*
|
|
* Restart a bg scan.
|
|
*/
|
|
static int
|
|
sta_restart(struct ieee80211_scan_state *ss, struct ieee80211com *ic)
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
|
|
st->st_newscan = 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Cancel an ongoing scan.
|
|
*/
|
|
static int
|
|
sta_cancel(struct ieee80211_scan_state *ss, struct ieee80211com *ic)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t
|
|
maxrate(const struct ieee80211_scan_entry *se)
|
|
{
|
|
uint8_t rmax, r;
|
|
int i;
|
|
|
|
rmax = 0;
|
|
for (i = 0; i < se->se_rates[1]; i++) {
|
|
r = se->se_rates[2+i] & IEEE80211_RATE_VAL;
|
|
if (r > rmax)
|
|
rmax = r;
|
|
}
|
|
for (i = 0; i < se->se_xrates[1]; i++) {
|
|
r = se->se_xrates[2+i] & IEEE80211_RATE_VAL;
|
|
if (r > rmax)
|
|
rmax = r;
|
|
}
|
|
return rmax;
|
|
}
|
|
|
|
/*
|
|
* Compare the capabilities of two entries 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
|
|
sta_compare(const struct sta_entry *a, const struct sta_entry *b)
|
|
{
|
|
#define PREFER(_a,_b,_what) do { \
|
|
if (((_a) ^ (_b)) & (_what)) \
|
|
return ((_a) & (_what)) ? 1 : -1; \
|
|
} while (0)
|
|
uint8_t maxa, maxb;
|
|
int8_t rssia, rssib;
|
|
int weight;
|
|
|
|
/* desired bssid */
|
|
PREFER(a->se_flags, b->se_flags, STA_BSSID_MATCH);
|
|
/* desired ssid */
|
|
PREFER(a->se_flags, b->se_flags, STA_SSID_MATCH);
|
|
/* privacy support */
|
|
PREFER(a->base.se_capinfo, b->base.se_capinfo,
|
|
IEEE80211_CAPINFO_PRIVACY);
|
|
|
|
/* compare count of previous failures */
|
|
weight = b->se_fails - a->se_fails;
|
|
if (abs(weight) > 1)
|
|
return weight;
|
|
|
|
/*
|
|
* Compare rssi. If the two are considered equivalent
|
|
* then fallback to other criteria. We threshold the
|
|
* comparisons to avoid selecting an ap purely by rssi
|
|
* when both values may be good but one ap is otherwise
|
|
* more desirable (e.g. an 11b-only ap with stronger
|
|
* signal than an 11g ap).
|
|
*/
|
|
rssia = MIN(a->base.se_rssi, STA_RSSI_MAX);
|
|
rssib = MIN(b->base.se_rssi, STA_RSSI_MAX);
|
|
if (abs(rssib - rssia) < 5) {
|
|
/* best/max rate preferred if signal level close enough XXX */
|
|
maxa = maxrate(&a->base);
|
|
maxb = maxrate(&b->base);
|
|
if (maxa != maxb)
|
|
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->base.se_chan) &&
|
|
!IEEE80211_IS_CHAN_5GHZ(b->base.se_chan))
|
|
return 1;
|
|
if (!IEEE80211_IS_CHAN_5GHZ(a->base.se_chan) &&
|
|
IEEE80211_IS_CHAN_5GHZ(b->base.se_chan))
|
|
return -1;
|
|
}
|
|
/* all things being equal, use signal level */
|
|
return a->base.se_rssi - b->base.se_rssi;
|
|
#undef PREFER
|
|
}
|
|
|
|
/*
|
|
* Check rate set suitability and return the best supported rate.
|
|
*/
|
|
static int
|
|
check_rate(struct ieee80211com *ic, const struct ieee80211_scan_entry *se)
|
|
{
|
|
#define RV(v) ((v) & IEEE80211_RATE_VAL)
|
|
const struct ieee80211_rateset *srs;
|
|
int i, j, nrs, r, okrate, badrate, fixedrate;
|
|
const uint8_t *rs;
|
|
|
|
okrate = badrate = fixedrate = 0;
|
|
|
|
srs = ieee80211_get_suprates(ic, se->se_chan);
|
|
nrs = se->se_rates[1];
|
|
rs = se->se_rates+2;
|
|
fixedrate = IEEE80211_FIXED_RATE_NONE;
|
|
again:
|
|
for (i = 0; i < nrs; i++) {
|
|
r = RV(rs[i]);
|
|
badrate = r;
|
|
/*
|
|
* Check any fixed rate is included.
|
|
*/
|
|
if (r == ic->ic_fixed_rate)
|
|
fixedrate = r;
|
|
/*
|
|
* Check against our supported rates.
|
|
*/
|
|
for (j = 0; j < srs->rs_nrates; j++)
|
|
if (r == RV(srs->rs_rates[j])) {
|
|
if (r > okrate) /* NB: track max */
|
|
okrate = r;
|
|
break;
|
|
}
|
|
|
|
if (j == srs->rs_nrates && (rs[i] & IEEE80211_RATE_BASIC)) {
|
|
/*
|
|
* Don't try joining a BSS, if we don't support
|
|
* one of its basic rates.
|
|
*/
|
|
okrate = 0;
|
|
goto back;
|
|
}
|
|
}
|
|
if (rs == se->se_rates+2) {
|
|
/* scan xrates too; sort of an algol68-style for loop */
|
|
nrs = se->se_xrates[1];
|
|
rs = se->se_xrates+2;
|
|
goto again;
|
|
}
|
|
|
|
back:
|
|
if (okrate == 0 || ic->ic_fixed_rate != fixedrate)
|
|
return badrate | IEEE80211_RATE_BASIC;
|
|
else
|
|
return RV(okrate);
|
|
#undef RV
|
|
}
|
|
|
|
static int
|
|
match_ssid(const uint8_t *ie,
|
|
int nssid, const struct ieee80211_scan_ssid ssids[])
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < nssid; i++) {
|
|
if (ie[1] == ssids[i].len &&
|
|
memcmp(ie+2, ssids[i].ssid, ie[1]) == 0)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Test a scan candidate for suitability/compatibility.
|
|
*/
|
|
static int
|
|
match_bss(struct ieee80211com *ic,
|
|
const struct ieee80211_scan_state *ss, struct sta_entry *se0,
|
|
int debug)
|
|
{
|
|
struct ieee80211_scan_entry *se = &se0->base;
|
|
uint8_t rate;
|
|
int fail;
|
|
|
|
fail = 0;
|
|
if (isclr(ic->ic_chan_active, ieee80211_chan2ieee(ic, se->se_chan)))
|
|
fail |= 0x01;
|
|
/*
|
|
* NB: normally the desired mode is used to construct
|
|
* the channel list, but it's possible for the scan
|
|
* cache to include entries for stations outside this
|
|
* list so we check the desired mode here to weed them
|
|
* out.
|
|
*/
|
|
if (ic->ic_des_mode != IEEE80211_MODE_AUTO &&
|
|
(se->se_chan->ic_flags & IEEE80211_CHAN_ALLTURBO) !=
|
|
chanflags[ic->ic_des_mode])
|
|
fail |= 0x01;
|
|
if (ic->ic_opmode == IEEE80211_M_IBSS) {
|
|
if ((se->se_capinfo & IEEE80211_CAPINFO_IBSS) == 0)
|
|
fail |= 0x02;
|
|
} else {
|
|
if ((se->se_capinfo & IEEE80211_CAPINFO_ESS) == 0)
|
|
fail |= 0x02;
|
|
}
|
|
if (ic->ic_flags & IEEE80211_F_PRIVACY) {
|
|
if ((se->se_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0)
|
|
fail |= 0x04;
|
|
} else {
|
|
/* XXX does this mean privacy is supported or required? */
|
|
if (se->se_capinfo & IEEE80211_CAPINFO_PRIVACY)
|
|
fail |= 0x04;
|
|
}
|
|
rate = check_rate(ic, se);
|
|
if (rate & IEEE80211_RATE_BASIC)
|
|
fail |= 0x08;
|
|
if (ss->ss_nssid != 0 &&
|
|
match_ssid(se->se_ssid, ss->ss_nssid, ss->ss_ssid))
|
|
se0->se_flags |= STA_SSID_MATCH;
|
|
else
|
|
se0->se_flags &= ~STA_SSID_MATCH;
|
|
if ((ic->ic_flags & IEEE80211_F_DESBSSID) &&
|
|
IEEE80211_ADDR_EQ(ic->ic_des_bssid, se->se_bssid))
|
|
se0->se_flags |= STA_BSSID_MATCH;
|
|
else
|
|
se0->se_flags &= ~STA_BSSID_MATCH;
|
|
if (se0->se_fails >= STA_FAILS_MAX)
|
|
fail |= 0x40;
|
|
if (se0->se_notseen >= STA_PURGE_SCANS)
|
|
fail |= 0x80;
|
|
if (se->se_rssi < STA_RSSI_MIN)
|
|
fail |= 0x100;
|
|
#ifdef IEEE80211_DEBUG
|
|
if (ieee80211_msg(ic, debug)) {
|
|
printf(" %c %s",
|
|
fail & 0x40 ? '=' : fail & 0x80 ? '^' : fail ? '-' : '+',
|
|
ether_sprintf(se->se_macaddr));
|
|
printf(" %s%c", ether_sprintf(se->se_bssid),
|
|
se0->se_flags & STA_BSSID_MATCH ? '*' : ' ');
|
|
printf(" %3d%c", ieee80211_chan2ieee(ic, se->se_chan),
|
|
fail & 0x01 ? '!' : ' ');
|
|
printf(" %+4d%c", se->se_rssi, fail & 0x100 ? '!' : ' ');
|
|
printf(" %2dM%c", (rate & IEEE80211_RATE_VAL) / 2,
|
|
fail & 0x08 ? '!' : ' ');
|
|
printf(" %4s%c",
|
|
(se->se_capinfo & IEEE80211_CAPINFO_ESS) ? "ess" :
|
|
(se->se_capinfo & IEEE80211_CAPINFO_IBSS) ? "ibss" :
|
|
"????",
|
|
fail & 0x02 ? '!' : ' ');
|
|
printf(" %3s%c ",
|
|
(se->se_capinfo & IEEE80211_CAPINFO_PRIVACY) ?
|
|
"wep" : "no",
|
|
fail & 0x04 ? '!' : ' ');
|
|
ieee80211_print_essid(se->se_ssid+2, se->se_ssid[1]);
|
|
printf("%s\n", se0->se_flags & STA_SSID_MATCH ? "*" : "");
|
|
}
|
|
#endif
|
|
return fail;
|
|
}
|
|
|
|
static void
|
|
sta_update_notseen(struct sta_table *st)
|
|
{
|
|
struct sta_entry *se;
|
|
|
|
mtx_lock(&st->st_lock);
|
|
TAILQ_FOREACH(se, &st->st_entry, se_list) {
|
|
/*
|
|
* If seen the reset and don't bump the count;
|
|
* otherwise bump the ``not seen'' count. Note
|
|
* that this insures that stations for which we
|
|
* see frames while not scanning but not during
|
|
* this scan will not be penalized.
|
|
*/
|
|
if (se->se_seen)
|
|
se->se_seen = 0;
|
|
else
|
|
se->se_notseen++;
|
|
}
|
|
mtx_unlock(&st->st_lock);
|
|
}
|
|
|
|
static void
|
|
sta_dec_fails(struct sta_table *st)
|
|
{
|
|
struct sta_entry *se;
|
|
|
|
mtx_lock(&st->st_lock);
|
|
TAILQ_FOREACH(se, &st->st_entry, se_list)
|
|
if (se->se_fails)
|
|
se->se_fails--;
|
|
mtx_unlock(&st->st_lock);
|
|
}
|
|
|
|
static struct sta_entry *
|
|
select_bss(struct ieee80211_scan_state *ss, struct ieee80211com *ic, int debug)
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
struct sta_entry *se, *selbs = NULL;
|
|
|
|
IEEE80211_DPRINTF(ic, debug, " %s\n",
|
|
"macaddr bssid chan rssi rate flag wep essid");
|
|
mtx_lock(&st->st_lock);
|
|
TAILQ_FOREACH(se, &st->st_entry, se_list) {
|
|
if (match_bss(ic, ss, se, debug) == 0) {
|
|
if (selbs == NULL)
|
|
selbs = se;
|
|
else if (sta_compare(se, selbs) > 0)
|
|
selbs = se;
|
|
}
|
|
}
|
|
mtx_unlock(&st->st_lock);
|
|
|
|
return selbs;
|
|
}
|
|
|
|
/*
|
|
* Pick an ap or ibss network to join or find a channel
|
|
* to use to start an ibss network.
|
|
*/
|
|
static int
|
|
sta_pick_bss(struct ieee80211_scan_state *ss, struct ieee80211com *ic)
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
struct sta_entry *selbs;
|
|
|
|
KASSERT(ic->ic_opmode == IEEE80211_M_STA,
|
|
("wrong mode %u", ic->ic_opmode));
|
|
|
|
if (st->st_newscan) {
|
|
sta_update_notseen(st);
|
|
st->st_newscan = 0;
|
|
}
|
|
if (ss->ss_flags & IEEE80211_SCAN_NOPICK) {
|
|
/*
|
|
* Manual/background scan, don't select+join the
|
|
* bss, just return. The scanning framework will
|
|
* handle notification that this has completed.
|
|
*/
|
|
ss->ss_flags &= ~IEEE80211_SCAN_NOPICK;
|
|
return 1;
|
|
}
|
|
/*
|
|
* Automatic sequencing; look for a candidate and
|
|
* if found join the network.
|
|
*/
|
|
/* NB: unlocked read should be ok */
|
|
if (TAILQ_FIRST(&st->st_entry) == NULL) {
|
|
IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN,
|
|
"%s: no scan candidate\n", __func__);
|
|
notfound:
|
|
/*
|
|
* If nothing suitable was found decrement
|
|
* the failure counts so entries will be
|
|
* reconsidered the next time around. We
|
|
* really want to do this only for sta's
|
|
* where we've previously had some success.
|
|
*/
|
|
sta_dec_fails(st);
|
|
st->st_newscan = 1;
|
|
return 0; /* restart scan */
|
|
}
|
|
selbs = select_bss(ss, ic, IEEE80211_MSG_SCAN);
|
|
if (selbs == NULL || !ieee80211_sta_join(ic, &selbs->base))
|
|
goto notfound;
|
|
return 1; /* terminate scan */
|
|
}
|
|
|
|
/*
|
|
* Lookup an entry in the scan cache. We assume we're
|
|
* called from the bottom half or such that we don't need
|
|
* to block the bottom half so that it's safe to return
|
|
* a reference to an entry w/o holding the lock on the table.
|
|
*/
|
|
static struct sta_entry *
|
|
sta_lookup(struct sta_table *st, const uint8_t macaddr[IEEE80211_ADDR_LEN])
|
|
{
|
|
struct sta_entry *se;
|
|
int hash = STA_HASH(macaddr);
|
|
|
|
mtx_lock(&st->st_lock);
|
|
LIST_FOREACH(se, &st->st_hash[hash], se_hash)
|
|
if (IEEE80211_ADDR_EQ(se->base.se_macaddr, macaddr))
|
|
break;
|
|
mtx_unlock(&st->st_lock);
|
|
|
|
return se; /* NB: unlocked */
|
|
}
|
|
|
|
static void
|
|
sta_roam_check(struct ieee80211_scan_state *ss, struct ieee80211com *ic)
|
|
{
|
|
struct ieee80211_node *ni = ic->ic_bss;
|
|
struct sta_table *st = ss->ss_priv;
|
|
struct sta_entry *se, *selbs;
|
|
uint8_t roamRate, curRate;
|
|
int8_t roamRssi, curRssi;
|
|
|
|
se = sta_lookup(st, ni->ni_macaddr);
|
|
if (se == NULL) {
|
|
/* XXX something is wrong */
|
|
return;
|
|
}
|
|
|
|
/* XXX do we need 11g too? */
|
|
if (IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan)) {
|
|
roamRate = ic->ic_roam.rate11b;
|
|
roamRssi = ic->ic_roam.rssi11b;
|
|
} else if (IEEE80211_IS_CHAN_B(ic->ic_bsschan)) {
|
|
roamRate = ic->ic_roam.rate11bOnly;
|
|
roamRssi = ic->ic_roam.rssi11bOnly;
|
|
} else {
|
|
roamRate = ic->ic_roam.rate11a;
|
|
roamRssi = ic->ic_roam.rssi11a;
|
|
}
|
|
/* NB: the most up to date rssi is in the node, not the scan cache */
|
|
curRssi = ic->ic_node_getrssi(ni);
|
|
if (ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) {
|
|
curRate = ni->ni_rates.rs_rates[ni->ni_txrate] & IEEE80211_RATE_VAL;
|
|
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ROAM,
|
|
"%s: currssi %d currate %u roamrssi %d roamrate %u\n",
|
|
__func__, curRssi, curRate, roamRssi, roamRate);
|
|
} else {
|
|
curRate = roamRate; /* NB: insure compare below fails */
|
|
IEEE80211_DPRINTF(ic, IEEE80211_MSG_ROAM,
|
|
"%s: currssi %d roamrssi %d\n", __func__, curRssi, roamRssi);
|
|
}
|
|
/*
|
|
* Check if a new ap should be used and switch.
|
|
* XXX deauth current ap
|
|
*/
|
|
if (curRate < roamRate || curRssi < roamRssi) {
|
|
if (time_after(ticks, ic->ic_lastscan + ic->ic_scanvalid)) {
|
|
/*
|
|
* Scan cache contents are too old; force a scan now
|
|
* if possible so we have current state to make a
|
|
* decision with. We don't kick off a bg scan if
|
|
* we're using dynamic turbo and boosted or if the
|
|
* channel is busy.
|
|
* XXX force immediate switch on scan complete
|
|
*/
|
|
if (!IEEE80211_IS_CHAN_DTURBO(ic->ic_curchan) &&
|
|
time_after(ticks, ic->ic_lastdata + ic->ic_bgscanidle))
|
|
ieee80211_bg_scan(ic);
|
|
return;
|
|
}
|
|
se->base.se_rssi = curRssi;
|
|
selbs = select_bss(ss, ic, IEEE80211_MSG_ROAM);
|
|
if (selbs != NULL && selbs != se) {
|
|
IEEE80211_DPRINTF(ic,
|
|
IEEE80211_MSG_ROAM | IEEE80211_MSG_DEBUG,
|
|
"%s: ROAM: curRate %u, roamRate %u, "
|
|
"curRssi %d, roamRssi %d\n", __func__,
|
|
curRate, roamRate, curRssi, roamRssi);
|
|
ieee80211_sta_join(ic, &selbs->base);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Age entries in the scan cache.
|
|
* XXX also do roaming since it's convenient
|
|
*/
|
|
static void
|
|
sta_age(struct ieee80211_scan_state *ss)
|
|
{
|
|
struct ieee80211com *ic = ss->ss_ic;
|
|
struct sta_table *st = ss->ss_priv;
|
|
struct sta_entry *se, *next;
|
|
|
|
mtx_lock(&st->st_lock);
|
|
TAILQ_FOREACH_SAFE(se, &st->st_entry, se_list, next) {
|
|
if (se->se_notseen > STA_PURGE_SCANS) {
|
|
TAILQ_REMOVE(&st->st_entry, se, se_list);
|
|
LIST_REMOVE(se, se_hash);
|
|
FREE(se, M_80211_SCAN);
|
|
}
|
|
}
|
|
mtx_unlock(&st->st_lock);
|
|
/*
|
|
* If rate control is enabled check periodically to see if
|
|
* we should roam from our current connection to one that
|
|
* might be better. This only applies when we're operating
|
|
* in sta mode and automatic roaming is set.
|
|
* XXX defer if busy
|
|
* XXX repeater station
|
|
* XXX do when !bgscan?
|
|
*/
|
|
KASSERT(ic->ic_opmode == IEEE80211_M_STA,
|
|
("wrong mode %u", ic->ic_opmode));
|
|
if (ic->ic_roaming == IEEE80211_ROAMING_AUTO &&
|
|
(ic->ic_flags & IEEE80211_F_BGSCAN) &&
|
|
ic->ic_state >= IEEE80211_S_RUN)
|
|
/* XXX vap is implicit */
|
|
sta_roam_check(ss, ic);
|
|
}
|
|
|
|
/*
|
|
* Iterate over the entries in the scan cache, invoking
|
|
* the callback function on each one.
|
|
*/
|
|
static void
|
|
sta_iterate(struct ieee80211_scan_state *ss,
|
|
ieee80211_scan_iter_func *f, void *arg)
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
struct sta_entry *se;
|
|
u_int gen;
|
|
|
|
mtx_lock(&st->st_scanlock);
|
|
gen = st->st_scangen++;
|
|
restart:
|
|
mtx_lock(&st->st_lock);
|
|
TAILQ_FOREACH(se, &st->st_entry, se_list) {
|
|
if (se->se_scangen != gen) {
|
|
se->se_scangen = gen;
|
|
/* update public state */
|
|
se->base.se_age = ticks - se->se_lastupdate;
|
|
mtx_unlock(&st->st_lock);
|
|
(*f)(arg, &se->base);
|
|
goto restart;
|
|
}
|
|
}
|
|
mtx_unlock(&st->st_lock);
|
|
|
|
mtx_unlock(&st->st_scanlock);
|
|
}
|
|
|
|
static void
|
|
sta_assoc_fail(struct ieee80211_scan_state *ss,
|
|
const uint8_t macaddr[IEEE80211_ADDR_LEN], int reason)
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
struct sta_entry *se;
|
|
|
|
se = sta_lookup(st, macaddr);
|
|
if (se != NULL) {
|
|
se->se_fails++;
|
|
se->se_lastfail = ticks;
|
|
IEEE80211_NOTE_MAC(ss->ss_ic, IEEE80211_MSG_SCAN,
|
|
macaddr, "%s: reason %u fails %u",
|
|
__func__, reason, se->se_fails);
|
|
}
|
|
}
|
|
|
|
static void
|
|
sta_assoc_success(struct ieee80211_scan_state *ss,
|
|
const uint8_t macaddr[IEEE80211_ADDR_LEN])
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
struct sta_entry *se;
|
|
|
|
se = sta_lookup(st, macaddr);
|
|
if (se != NULL) {
|
|
#if 0
|
|
se->se_fails = 0;
|
|
IEEE80211_NOTE_MAC(ss->ss_ic, IEEE80211_MSG_SCAN,
|
|
macaddr, "%s: fails %u",
|
|
__func__, se->se_fails);
|
|
#endif
|
|
se->se_lastassoc = ticks;
|
|
}
|
|
}
|
|
|
|
static const struct ieee80211_scanner sta_default = {
|
|
.scan_name = "default",
|
|
.scan_attach = sta_attach,
|
|
.scan_detach = sta_detach,
|
|
.scan_start = sta_start,
|
|
.scan_restart = sta_restart,
|
|
.scan_cancel = sta_cancel,
|
|
.scan_end = sta_pick_bss,
|
|
.scan_flush = sta_flush,
|
|
.scan_add = sta_add,
|
|
.scan_age = sta_age,
|
|
.scan_iterate = sta_iterate,
|
|
.scan_assoc_fail = sta_assoc_fail,
|
|
.scan_assoc_success = sta_assoc_success,
|
|
};
|
|
|
|
/*
|
|
* Adhoc mode-specific support.
|
|
*/
|
|
|
|
static const uint16_t adhocWorld[] = /* 36, 40, 44, 48 */
|
|
{ 5180, 5200, 5220, 5240 };
|
|
static const uint16_t adhocFcc3[] = /* 36, 40, 44, 48 145, 149, 153, 157, 161, 165 */
|
|
{ 5180, 5200, 5220, 5240, 5725, 5745, 5765, 5785, 5805, 5825 };
|
|
static const uint16_t adhocMkk[] = /* 34, 38, 42, 46 */
|
|
{ 5170, 5190, 5210, 5230 };
|
|
static const uint16_t adhoc11b[] = /* 10, 11 */
|
|
{ 2457, 2462 };
|
|
|
|
static const struct scanlist adhocScanTable[] = {
|
|
{ IEEE80211_MODE_11B, X(adhoc11b) },
|
|
{ IEEE80211_MODE_11A, X(adhocWorld) },
|
|
{ IEEE80211_MODE_11A, X(adhocFcc3) },
|
|
{ IEEE80211_MODE_11B, X(adhocMkk) },
|
|
{ .list = NULL }
|
|
};
|
|
#undef X
|
|
|
|
/*
|
|
* Start an adhoc-mode scan by populating the channel list.
|
|
*/
|
|
static int
|
|
adhoc_start(struct ieee80211_scan_state *ss, struct ieee80211com *ic)
|
|
{
|
|
#define N(a) (sizeof(a)/sizeof(a[0]))
|
|
struct sta_table *st = ss->ss_priv;
|
|
const struct scanlist *scan;
|
|
enum ieee80211_phymode mode;
|
|
|
|
ss->ss_last = 0;
|
|
/*
|
|
* Use the table of ordered channels to construct the list
|
|
* of channels for scanning. Any channels in the ordered
|
|
* list not in the master list will be discarded.
|
|
*/
|
|
for (scan = adhocScanTable; scan->list != NULL; scan++) {
|
|
mode = scan->mode;
|
|
if (ic->ic_des_mode != IEEE80211_MODE_AUTO) {
|
|
/*
|
|
* If a desired mode was specified, scan only
|
|
* channels that satisfy that constraint.
|
|
*/
|
|
if (ic->ic_des_mode != mode) {
|
|
/*
|
|
* The scan table marks 2.4Ghz channels as b
|
|
* so if the desired mode is 11g, then use
|
|
* the 11b channel list but upgrade the mode.
|
|
*/
|
|
if (ic->ic_des_mode != IEEE80211_MODE_11G ||
|
|
mode != IEEE80211_MODE_11B)
|
|
continue;
|
|
mode = IEEE80211_MODE_11G; /* upgrade */
|
|
}
|
|
} else {
|
|
/*
|
|
* This lets add_channels upgrade an 11b channel
|
|
* to 11g if available.
|
|
*/
|
|
if (mode == IEEE80211_MODE_11B)
|
|
mode = IEEE80211_MODE_AUTO;
|
|
}
|
|
#ifdef IEEE80211_F_XR
|
|
/* XR does not operate on turbo channels */
|
|
if ((ic->ic_flags & IEEE80211_F_XR) &&
|
|
(mode == IEEE80211_MODE_TURBO_A ||
|
|
mode == IEEE80211_MODE_TURBO_G))
|
|
continue;
|
|
#endif
|
|
/*
|
|
* Add the list of the channels; any that are not
|
|
* in the master channel list will be discarded.
|
|
*/
|
|
add_channels(ic, ss, mode, scan->list, scan->count);
|
|
}
|
|
ss->ss_next = 0;
|
|
/* XXX tunables */
|
|
ss->ss_mindwell = msecs_to_ticks(200); /* 200ms */
|
|
ss->ss_maxdwell = msecs_to_ticks(200); /* 200ms */
|
|
|
|
#ifdef IEEE80211_DEBUG
|
|
if (ieee80211_msg_scan(ic)) {
|
|
if_printf(ic->ic_ifp, "scan set ");
|
|
ieee80211_scan_dump_channels(ss);
|
|
printf(" dwell min %ld max %ld\n",
|
|
ss->ss_mindwell, ss->ss_maxdwell);
|
|
}
|
|
#endif /* IEEE80211_DEBUG */
|
|
|
|
st->st_newscan = 1;
|
|
|
|
return 0;
|
|
#undef N
|
|
}
|
|
|
|
/*
|
|
* Select a channel to start an adhoc network on.
|
|
* The channel list was populated with appropriate
|
|
* channels so select one that looks least occupied.
|
|
* XXX need regulatory domain constraints
|
|
*/
|
|
static struct ieee80211_channel *
|
|
adhoc_pick_channel(struct ieee80211_scan_state *ss)
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
struct sta_entry *se;
|
|
struct ieee80211_channel *c, *bestchan;
|
|
int i, bestrssi, maxrssi;
|
|
|
|
bestchan = NULL;
|
|
bestrssi = -1;
|
|
|
|
mtx_lock(&st->st_lock);
|
|
for (i = 0; i < ss->ss_last; i++) {
|
|
c = ss->ss_chans[i];
|
|
maxrssi = 0;
|
|
TAILQ_FOREACH(se, &st->st_entry, se_list) {
|
|
if (se->base.se_chan != c)
|
|
continue;
|
|
if (se->base.se_rssi > maxrssi)
|
|
maxrssi = se->base.se_rssi;
|
|
}
|
|
if (bestchan == NULL || maxrssi < bestrssi)
|
|
bestchan = c;
|
|
}
|
|
mtx_unlock(&st->st_lock);
|
|
|
|
return bestchan;
|
|
}
|
|
|
|
/*
|
|
* Pick an ibss network to join or find a channel
|
|
* to use to start an ibss network.
|
|
*/
|
|
static int
|
|
adhoc_pick_bss(struct ieee80211_scan_state *ss, struct ieee80211com *ic)
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
struct sta_entry *selbs;
|
|
struct ieee80211_channel *chan;
|
|
|
|
KASSERT(ic->ic_opmode == IEEE80211_M_IBSS ||
|
|
ic->ic_opmode == IEEE80211_M_AHDEMO,
|
|
("wrong opmode %u", ic->ic_opmode));
|
|
|
|
if (st->st_newscan) {
|
|
sta_update_notseen(st);
|
|
st->st_newscan = 0;
|
|
}
|
|
if (ss->ss_flags & IEEE80211_SCAN_NOPICK) {
|
|
/*
|
|
* Manual/background scan, don't select+join the
|
|
* bss, just return. The scanning framework will
|
|
* handle notification that this has completed.
|
|
*/
|
|
ss->ss_flags &= ~IEEE80211_SCAN_NOPICK;
|
|
return 1;
|
|
}
|
|
/*
|
|
* Automatic sequencing; look for a candidate and
|
|
* if found join the network.
|
|
*/
|
|
/* NB: unlocked read should be ok */
|
|
if (TAILQ_FIRST(&st->st_entry) == NULL) {
|
|
IEEE80211_DPRINTF(ic, IEEE80211_MSG_SCAN,
|
|
"%s: no scan candidate\n", __func__);
|
|
notfound:
|
|
if (ic->ic_des_nssid) {
|
|
/*
|
|
* No existing adhoc network to join and we have
|
|
* an ssid; start one up. If no channel was
|
|
* specified, try to select a channel.
|
|
*/
|
|
if (ic->ic_des_chan == IEEE80211_CHAN_ANYC)
|
|
chan = adhoc_pick_channel(ss);
|
|
else
|
|
chan = ic->ic_des_chan;
|
|
if (chan != NULL) {
|
|
ieee80211_create_ibss(ic, chan);
|
|
return 1;
|
|
}
|
|
}
|
|
/*
|
|
* If nothing suitable was found decrement
|
|
* the failure counts so entries will be
|
|
* reconsidered the next time around. We
|
|
* really want to do this only for sta's
|
|
* where we've previously had some success.
|
|
*/
|
|
sta_dec_fails(st);
|
|
st->st_newscan = 1;
|
|
return 0; /* restart scan */
|
|
}
|
|
selbs = select_bss(ss, ic, IEEE80211_MSG_SCAN);
|
|
if (selbs == NULL || !ieee80211_sta_join(ic, &selbs->base))
|
|
goto notfound;
|
|
return 1; /* terminate scan */
|
|
}
|
|
|
|
/*
|
|
* Age entries in the scan cache.
|
|
*/
|
|
static void
|
|
adhoc_age(struct ieee80211_scan_state *ss)
|
|
{
|
|
struct sta_table *st = ss->ss_priv;
|
|
struct sta_entry *se, *next;
|
|
|
|
mtx_lock(&st->st_lock);
|
|
TAILQ_FOREACH_SAFE(se, &st->st_entry, se_list, next) {
|
|
if (se->se_notseen > STA_PURGE_SCANS) {
|
|
TAILQ_REMOVE(&st->st_entry, se, se_list);
|
|
LIST_REMOVE(se, se_hash);
|
|
FREE(se, M_80211_SCAN);
|
|
}
|
|
}
|
|
mtx_unlock(&st->st_lock);
|
|
}
|
|
|
|
static const struct ieee80211_scanner adhoc_default = {
|
|
.scan_name = "default",
|
|
.scan_attach = sta_attach,
|
|
.scan_detach = sta_detach,
|
|
.scan_start = adhoc_start,
|
|
.scan_restart = sta_restart,
|
|
.scan_cancel = sta_cancel,
|
|
.scan_end = adhoc_pick_bss,
|
|
.scan_flush = sta_flush,
|
|
.scan_add = sta_add,
|
|
.scan_age = adhoc_age,
|
|
.scan_iterate = sta_iterate,
|
|
.scan_assoc_fail = sta_assoc_fail,
|
|
.scan_assoc_success = sta_assoc_success,
|
|
};
|
|
|
|
/*
|
|
* Module glue.
|
|
*/
|
|
static int
|
|
wlan_modevent(module_t mod, int type, void *unused)
|
|
{
|
|
switch (type) {
|
|
case MOD_LOAD:
|
|
ieee80211_scanner_register(IEEE80211_M_STA, &sta_default);
|
|
ieee80211_scanner_register(IEEE80211_M_IBSS, &adhoc_default);
|
|
ieee80211_scanner_register(IEEE80211_M_AHDEMO, &adhoc_default);
|
|
return 0;
|
|
case MOD_UNLOAD:
|
|
case MOD_QUIESCE:
|
|
if (nrefs) {
|
|
printf("wlan_scan_sta: still in use (%u dynamic refs)\n",
|
|
nrefs);
|
|
return EBUSY;
|
|
}
|
|
if (type == MOD_UNLOAD) {
|
|
ieee80211_scanner_unregister_all(&sta_default);
|
|
ieee80211_scanner_unregister_all(&adhoc_default);
|
|
}
|
|
return 0;
|
|
}
|
|
return EINVAL;
|
|
}
|
|
|
|
static moduledata_t wlan_mod = {
|
|
"wlan_scan_sta",
|
|
wlan_modevent,
|
|
0
|
|
};
|
|
DECLARE_MODULE(wlan_scan_sta, wlan_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);
|
|
MODULE_VERSION(wlan_scan_sta, 1);
|
|
MODULE_DEPEND(wlan_scan_sta, wlan, 1, 1, 1);
|