o station mode channel switch support
o IEEE80211_IOC_CHANSWITCH fixups: - restrict to hostap vaps - return EOPNOTSUPP instead of EINVAL when applied to !hostap vap or to a vap w/o 11h enabled - interpret count of 0 to mean cancel the current CSA Reviewed by: rpaulo, avatar
This commit is contained in:
parent
3144f81221
commit
c70761e6b5
@ -686,7 +686,7 @@ enum {
|
||||
IEEE80211_ELEMID_TPCREQ = 34,
|
||||
IEEE80211_ELEMID_TPCREP = 35,
|
||||
IEEE80211_ELEMID_SUPPCHAN = 36,
|
||||
IEEE80211_ELEMID_CHANSWITCHANN = 37,
|
||||
IEEE80211_ELEMID_CSA = 37,
|
||||
IEEE80211_ELEMID_MEASREQ = 38,
|
||||
IEEE80211_ELEMID_MEASREP = 39,
|
||||
IEEE80211_ELEMID_QUIET = 40,
|
||||
@ -736,6 +736,14 @@ struct ieee80211_csa_ie {
|
||||
uint8_t csa_count; /* Channel Switch Count */
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Note the min acceptable CSA count is used to guard against
|
||||
* malicious CSA injection in station mode. Defining this value
|
||||
* as other than 0 violates the 11h spec.
|
||||
*/
|
||||
#define IEEE80211_CSA_COUNT_MIN 2
|
||||
#define IEEE80211_CSA_COUNT_MAX 255
|
||||
|
||||
/* rate set entries are in .5 Mb/s units, and potentially marked as basic */
|
||||
#define IEEE80211_RATE_BASIC 0x80
|
||||
#define IEEE80211_RATE_VAL 0x7f
|
||||
|
@ -475,6 +475,7 @@ ieee80211_parse_beacon(struct ieee80211_node *ni, struct mbuf *m,
|
||||
* [tlv] ssid
|
||||
* [tlv] supported rates
|
||||
* [tlv] country information
|
||||
* [tlv] channel switch announcement (CSA)
|
||||
* [tlv] parameter set (FH/DS)
|
||||
* [tlv] erp information
|
||||
* [tlv] extended supported rates
|
||||
@ -508,6 +509,9 @@ ieee80211_parse_beacon(struct ieee80211_node *ni, struct mbuf *m,
|
||||
case IEEE80211_ELEMID_COUNTRY:
|
||||
scan->country = frm;
|
||||
break;
|
||||
case IEEE80211_ELEMID_CSA:
|
||||
scan->csa = frm;
|
||||
break;
|
||||
case IEEE80211_ELEMID_FHPARMS:
|
||||
if (ic->ic_phytype == IEEE80211_T_FH) {
|
||||
scan->fhdwell = LE_READ_2(&frm[2]);
|
||||
@ -642,6 +646,14 @@ ieee80211_parse_beacon(struct ieee80211_node *ni, struct mbuf *m,
|
||||
IEEE80211_VERIFY_LENGTH(scan->country[1], 3 * sizeof(uint8_t),
|
||||
scan->country = NULL);
|
||||
}
|
||||
if (scan->csa != NULL) {
|
||||
/*
|
||||
* Validate Channel Switch Announcement; this must
|
||||
* be the correct length or we toss the frame.
|
||||
*/
|
||||
IEEE80211_VERIFY_LENGTH(scan->csa[1], 3 * sizeof(uint8_t),
|
||||
scan->status |= IEEE80211_BPARSE_CSA_INVALID);
|
||||
}
|
||||
/*
|
||||
* Process HT ie's. This is complicated by our
|
||||
* accepting both the standard ie's and the pre-draft
|
||||
|
@ -2304,8 +2304,10 @@ ieee80211_ioctl_chanswitch(struct ieee80211vap *vap, struct ieee80211req *ireq)
|
||||
error = copyin(ireq->i_data, &csr, sizeof(csr));
|
||||
if (error != 0)
|
||||
return error;
|
||||
if ((vap->iv_flags & IEEE80211_F_DOTH) == 0)
|
||||
return EINVAL;
|
||||
/* XXX adhoc mode not supported */
|
||||
if (vap->iv_opmode != IEEE80211_M_HOSTAP ||
|
||||
(vap->iv_flags & IEEE80211_F_DOTH) == 0)
|
||||
return EOPNOTSUPP;
|
||||
c = ieee80211_find_channel(ic,
|
||||
csr.csa_chan.ic_freq, csr.csa_chan.ic_flags);
|
||||
if (c == NULL)
|
||||
@ -2313,6 +2315,8 @@ ieee80211_ioctl_chanswitch(struct ieee80211vap *vap, struct ieee80211req *ireq)
|
||||
IEEE80211_LOCK(ic);
|
||||
if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0)
|
||||
ieee80211_csa_startswitch(ic, c, csr.csa_mode, csr.csa_count);
|
||||
else if (csr.csa_count == 0)
|
||||
ieee80211_csa_cancelswitch(ic);
|
||||
else
|
||||
error = EBUSY;
|
||||
IEEE80211_UNLOCK(ic);
|
||||
|
@ -1468,7 +1468,7 @@ ieee80211_add_csa(uint8_t *frm, struct ieee80211vap *vap)
|
||||
struct ieee80211com *ic = vap->iv_ic;
|
||||
struct ieee80211_csa_ie *csa = (struct ieee80211_csa_ie *) frm;
|
||||
|
||||
csa->csa_ie = IEEE80211_ELEMID_CHANSWITCHANN;
|
||||
csa->csa_ie = IEEE80211_ELEMID_CSA;
|
||||
csa->csa_len = 3;
|
||||
csa->csa_mode = 1; /* XXX force quiet on channel */
|
||||
csa->csa_newchan = ieee80211_chan2ieee(ic, ic->ic_csa_newchan);
|
||||
|
@ -1374,7 +1374,7 @@ beacon_miss(void *arg, int npending)
|
||||
* handlers duplicating these checks.
|
||||
*/
|
||||
if (vap->iv_opmode == IEEE80211_M_STA &&
|
||||
vap->iv_state == IEEE80211_S_RUN &&
|
||||
vap->iv_state >= IEEE80211_S_RUN &&
|
||||
vap->iv_bmiss != NULL)
|
||||
vap->iv_bmiss(vap);
|
||||
}
|
||||
@ -1451,8 +1451,8 @@ ieee80211_csa_startswitch(struct ieee80211com *ic,
|
||||
IEEE80211_LOCK_ASSERT(ic);
|
||||
|
||||
ic->ic_csa_newchan = c;
|
||||
ic->ic_csa_mode = mode;
|
||||
ic->ic_csa_count = count;
|
||||
/* XXX record mode? */
|
||||
ic->ic_flags |= IEEE80211_F_CSAPENDING;
|
||||
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
|
||||
if (vap->iv_opmode == IEEE80211_M_HOSTAP ||
|
||||
@ -1465,6 +1465,19 @@ ieee80211_csa_startswitch(struct ieee80211com *ic,
|
||||
ieee80211_notify_csa(ic, c, mode, count);
|
||||
}
|
||||
|
||||
static void
|
||||
csa_completeswitch(struct ieee80211com *ic)
|
||||
{
|
||||
struct ieee80211vap *vap;
|
||||
|
||||
ic->ic_csa_newchan = NULL;
|
||||
ic->ic_flags &= ~IEEE80211_F_CSAPENDING;
|
||||
|
||||
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next)
|
||||
if (vap->iv_state == IEEE80211_S_CSA)
|
||||
ieee80211_new_state_locked(vap, IEEE80211_S_RUN, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Complete an 802.11h channel switch started by ieee80211_csa_startswitch.
|
||||
* We clear state and move all vap's in CSA state to RUN state
|
||||
@ -1473,19 +1486,25 @@ ieee80211_csa_startswitch(struct ieee80211com *ic,
|
||||
void
|
||||
ieee80211_csa_completeswitch(struct ieee80211com *ic)
|
||||
{
|
||||
struct ieee80211vap *vap;
|
||||
|
||||
IEEE80211_LOCK_ASSERT(ic);
|
||||
|
||||
KASSERT(ic->ic_flags & IEEE80211_F_CSAPENDING, ("csa not pending"));
|
||||
|
||||
ieee80211_setcurchan(ic, ic->ic_csa_newchan);
|
||||
ic->ic_csa_newchan = NULL;
|
||||
ic->ic_flags &= ~IEEE80211_F_CSAPENDING;
|
||||
csa_completeswitch(ic);
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next)
|
||||
if (vap->iv_state == IEEE80211_S_CSA)
|
||||
ieee80211_new_state_locked(vap, IEEE80211_S_RUN, 0);
|
||||
/*
|
||||
* Cancel an 802.11h channel switch started by ieee80211_csa_startswitch.
|
||||
* We clear state and move all vap's in CSA state to RUN state
|
||||
* so they can again transmit.
|
||||
*/
|
||||
void
|
||||
ieee80211_csa_cancelswitch(struct ieee80211com *ic)
|
||||
{
|
||||
IEEE80211_LOCK_ASSERT(ic);
|
||||
|
||||
csa_completeswitch(ic);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -340,6 +340,7 @@ int ieee80211_beacon_update(struct ieee80211_node *,
|
||||
void ieee80211_csa_startswitch(struct ieee80211com *,
|
||||
struct ieee80211_channel *, int mode, int count);
|
||||
void ieee80211_csa_completeswitch(struct ieee80211com *);
|
||||
void ieee80211_csa_cancelswitch(struct ieee80211com *);
|
||||
void ieee80211_cac_completeswitch(struct ieee80211vap *);
|
||||
|
||||
/*
|
||||
|
@ -176,6 +176,7 @@ enum {
|
||||
IEEE80211_BPARSE_CHAN_INVALID = 0x10, /* invalid FH/DSPARMS chan */
|
||||
IEEE80211_BPARSE_OFFCHAN = 0x20, /* DSPARMS chan != curchan */
|
||||
IEEE80211_BPARSE_BINTVAL_INVALID= 0x40, /* invalid beacon interval */
|
||||
IEEE80211_BPARSE_CSA_INVALID = 0x80, /* invalid CSA ie */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -211,7 +212,8 @@ struct ieee80211_scanparams {
|
||||
uint8_t *htinfo;
|
||||
uint8_t *ath;
|
||||
uint8_t *tdma;
|
||||
uint8_t *spare[4];
|
||||
uint8_t *csa;
|
||||
uint8_t *spare[3];
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -106,15 +106,28 @@ sta_vattach(struct ieee80211vap *vap)
|
||||
static void
|
||||
sta_beacon_miss(struct ieee80211vap *vap)
|
||||
{
|
||||
KASSERT((vap->iv_ic->ic_flags & IEEE80211_F_SCAN) == 0, ("scanning"));
|
||||
KASSERT(vap->iv_state == IEEE80211_S_RUN,
|
||||
("wrong state %d", vap->iv_state));
|
||||
struct ieee80211com *ic = vap->iv_ic;
|
||||
|
||||
IEEE80211_DPRINTF(vap,
|
||||
IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG,
|
||||
"beacon miss, mode %u state %s\n",
|
||||
vap->iv_opmode, ieee80211_state_name[vap->iv_state]);
|
||||
KASSERT((ic->ic_flags & IEEE80211_F_SCAN) == 0, ("scanning"));
|
||||
KASSERT(vap->iv_state >= IEEE80211_S_RUN,
|
||||
("wrong state %s", ieee80211_state_name[vap->iv_state]));
|
||||
|
||||
IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE | IEEE80211_MSG_DEBUG,
|
||||
"beacon miss, mode %s state %s\n",
|
||||
ieee80211_opmode_name[vap->iv_opmode],
|
||||
ieee80211_state_name[vap->iv_state]);
|
||||
|
||||
if (vap->iv_state == IEEE80211_S_CSA) {
|
||||
/*
|
||||
* A Channel Switch is pending; assume we missed the
|
||||
* beacon that would've completed the process and just
|
||||
* force the switch. If we made a mistake we'll not
|
||||
* find the AP on the new channel and fall back to a
|
||||
* normal scan.
|
||||
*/
|
||||
ieee80211_csa_completeswitch(ic);
|
||||
return;
|
||||
}
|
||||
if (++vap->iv_bmiss_count < vap->iv_bmiss_max) {
|
||||
/*
|
||||
* Send a directed probe req before falling back to a
|
||||
@ -359,6 +372,7 @@ sta_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
|
||||
}
|
||||
switch (ostate) {
|
||||
case IEEE80211_S_RUN:
|
||||
case IEEE80211_S_CSA:
|
||||
break;
|
||||
case IEEE80211_S_AUTH: /* when join is done in fw */
|
||||
case IEEE80211_S_ASSOC:
|
||||
@ -412,6 +426,10 @@ sta_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
|
||||
if (ic->ic_newassoc != NULL)
|
||||
ic->ic_newassoc(vap->iv_bss, ostate != IEEE80211_S_RUN);
|
||||
break;
|
||||
case IEEE80211_S_CSA:
|
||||
if (ostate != IEEE80211_S_RUN)
|
||||
goto invalid;
|
||||
break;
|
||||
case IEEE80211_S_SLEEP:
|
||||
ieee80211_sta_pwrsave(vap, 0);
|
||||
break;
|
||||
@ -1079,6 +1097,112 @@ ieee80211_parse_wmeparams(struct ieee80211vap *vap, uint8_t *frm,
|
||||
#undef MS
|
||||
}
|
||||
|
||||
/*
|
||||
* Process 11h Channel Switch Announcement (CSA) ie. If this
|
||||
* is the first CSA then initiate the switch. Otherwise we
|
||||
* track state and trigger completion and/or cancel of the switch.
|
||||
* XXX should be public for IBSS use
|
||||
*/
|
||||
static void
|
||||
ieee80211_parse_csaparams(struct ieee80211vap *vap, uint8_t *frm,
|
||||
const struct ieee80211_frame *wh)
|
||||
{
|
||||
struct ieee80211com *ic = vap->iv_ic;
|
||||
const struct ieee80211_csa_ie *csa =
|
||||
(const struct ieee80211_csa_ie *) frm;
|
||||
|
||||
KASSERT(vap->iv_state >= IEEE80211_S_RUN,
|
||||
("state %s", ieee80211_state_name[vap->iv_state]));
|
||||
|
||||
if (csa->csa_mode > 1) {
|
||||
IEEE80211_DISCARD_IE(vap,
|
||||
IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH,
|
||||
wh, "CSA", "invalid mode %u", csa->csa_mode);
|
||||
return;
|
||||
}
|
||||
IEEE80211_LOCK(ic);
|
||||
if ((ic->ic_flags & IEEE80211_F_CSAPENDING) == 0) {
|
||||
/*
|
||||
* Convert the channel number to a channel reference. We
|
||||
* try first to preserve turbo attribute of the current
|
||||
* channel then fallback. Note this will not work if the
|
||||
* CSA specifies a channel that requires a band switch (e.g.
|
||||
* 11a => 11g). This is intentional as 11h is defined only
|
||||
* for 5GHz/11a and because the switch does not involve a
|
||||
* reassociation, protocol state (capabilities, negotated
|
||||
* rates, etc) may/will be wrong.
|
||||
*/
|
||||
struct ieee80211_channel *c =
|
||||
ieee80211_find_channel_byieee(ic, csa->csa_newchan,
|
||||
(ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALLTURBO));
|
||||
if (c == NULL) {
|
||||
c = ieee80211_find_channel_byieee(ic,
|
||||
csa->csa_newchan,
|
||||
(ic->ic_bsschan->ic_flags & IEEE80211_CHAN_ALL));
|
||||
if (c == NULL) {
|
||||
IEEE80211_DISCARD_IE(vap,
|
||||
IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH,
|
||||
wh, "CSA", "invalid channel %u",
|
||||
csa->csa_newchan);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
#if IEEE80211_CSA_COUNT_MIN > 0
|
||||
if (csa->csa_count < IEEE80211_CSA_COUNT_MIN) {
|
||||
/*
|
||||
* Require at least IEEE80211_CSA_COUNT_MIN count to
|
||||
* reduce the risk of being redirected by a fabricated
|
||||
* CSA. If a valid CSA is dropped we'll still get a
|
||||
* beacon miss when the AP leaves the channel so we'll
|
||||
* eventually follow to the new channel.
|
||||
*
|
||||
* NOTE: this violates the 11h spec that states that
|
||||
* count may be any value and if 0 then a switch
|
||||
* should happen asap.
|
||||
*/
|
||||
IEEE80211_DISCARD_IE(vap,
|
||||
IEEE80211_MSG_ELEMID | IEEE80211_MSG_DOTH,
|
||||
wh, "CSA", "count %u too small, must be >= %u",
|
||||
csa->csa_count, IEEE80211_CSA_COUNT_MIN);
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
ieee80211_csa_startswitch(ic, c, csa->csa_mode, csa->csa_count);
|
||||
} else {
|
||||
/*
|
||||
* Validate this ie against the initial CSA. We require
|
||||
* mode and channel not change and the count must be
|
||||
* monotonically decreasing. This may be pointless and
|
||||
* canceling the switch as a result may be too paranoid but
|
||||
* in the worst case if we drop out of CSA because of this
|
||||
* and the AP does move then we'll just end up taking a
|
||||
* beacon miss and scan to find the AP.
|
||||
*
|
||||
* XXX may want <= on count as we also process ProbeResp
|
||||
* frames and those may come in w/ the same count as the
|
||||
* previous beacon; but doing so leaves us open to a stuck
|
||||
* count until we add a dead-man timer
|
||||
*/
|
||||
if (!(csa->csa_count < ic->ic_csa_count &&
|
||||
csa->csa_mode == ic->ic_csa_mode &&
|
||||
csa->csa_newchan == ieee80211_chan2ieee(ic, ic->ic_csa_newchan))) {
|
||||
IEEE80211_NOTE_FRAME(vap, IEEE80211_MSG_DOTH, wh,
|
||||
"CSA ie mismatch, initial ie <%d,%d,%d>, "
|
||||
"this ie <%d,%d,%d>", ic->ic_csa_mode,
|
||||
ic->ic_csa_newchan, ic->ic_csa_count,
|
||||
csa->csa_mode, csa->csa_newchan, csa->csa_count);
|
||||
ieee80211_csa_cancelswitch(ic);
|
||||
} else {
|
||||
if (csa->csa_count <= 1)
|
||||
ieee80211_csa_completeswitch(ic);
|
||||
else
|
||||
ic->ic_csa_count = csa->csa_count;
|
||||
}
|
||||
}
|
||||
done:
|
||||
IEEE80211_UNLOCK(ic);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return non-zero if a background scan may be continued:
|
||||
* o bg scan is active
|
||||
@ -1245,6 +1369,20 @@ sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0,
|
||||
ni->ni_dtim_count = tim->tim_count;
|
||||
ni->ni_dtim_period = tim->tim_period;
|
||||
}
|
||||
if (scan.csa != NULL &&
|
||||
(vap->iv_flags & IEEE80211_F_DOTH))
|
||||
ieee80211_parse_csaparams(vap, scan.csa, wh);
|
||||
else if (ic->ic_flags & IEEE80211_F_CSAPENDING) {
|
||||
/*
|
||||
* No CSA ie or 11h disabled, but a channel
|
||||
* switch is pending; drop out so we aren't
|
||||
* stuck in CSA state. If the AP really is
|
||||
* moving we'll get a beacon miss and scan.
|
||||
*/
|
||||
IEEE80211_LOCK(ic);
|
||||
ieee80211_csa_cancelswitch(ic);
|
||||
IEEE80211_UNLOCK(ic);
|
||||
}
|
||||
/*
|
||||
* If scanning, pass the info to the scan module.
|
||||
* Otherwise, check if it's the right time to do
|
||||
|
@ -181,7 +181,8 @@ struct ieee80211com {
|
||||
|
||||
/* 802.11h/DFS state */
|
||||
struct ieee80211_channel *ic_csa_newchan;/* channel for doing CSA */
|
||||
int ic_csa_count; /* count for doing CSA */
|
||||
short ic_csa_mode; /* mode for doing CSA */
|
||||
short ic_csa_count; /* count for doing CSA */
|
||||
struct ieee80211_dfs_state ic_dfs; /* DFS state */
|
||||
|
||||
struct ieee80211_scan_state *ic_scan; /* scan state */
|
||||
|
Loading…
Reference in New Issue
Block a user