summaryrefslogtreecommitdiffstats
path: root/sys/net80211/ieee80211_sta.c
diff options
context:
space:
mode:
authorsam <sam@FreeBSD.org>2009-06-04 15:57:38 +0000
committersam <sam@FreeBSD.org>2009-06-04 15:57:38 +0000
commite673d3b5e90d6ec6fe0c6bca82c4b813b1af6e3d (patch)
tree0087ddfa0d20ea5694d61834bf30fbd8cf190b9f /sys/net80211/ieee80211_sta.c
parent981682577c61c5ffe81deb34e9b9b1f0658c7fb0 (diff)
downloadFreeBSD-src-e673d3b5e90d6ec6fe0c6bca82c4b813b1af6e3d.zip
FreeBSD-src-e673d3b5e90d6ec6fe0c6bca82c4b813b1af6e3d.tar.gz
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
Diffstat (limited to 'sys/net80211/ieee80211_sta.c')
-rw-r--r--sys/net80211/ieee80211_sta.c152
1 files changed, 145 insertions, 7 deletions
diff --git a/sys/net80211/ieee80211_sta.c b/sys/net80211/ieee80211_sta.c
index 0dee283..097d2f2 100644
--- a/sys/net80211/ieee80211_sta.c
+++ b/sys/net80211/ieee80211_sta.c
@@ -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;
+
+ 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 %u state %s\n",
- vap->iv_opmode, 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;
@@ -1080,6 +1098,112 @@ ieee80211_parse_wmeparams(struct ieee80211vap *vap, uint8_t *frm,
}
/*
+ * 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
* o no channel switch is pending
@@ -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
OpenPOWER on IntegriCloud