From c70761e6b54def6b51874d2967c0bd5a8d133db8 Mon Sep 17 00:00:00 2001 From: Sam Leffler Date: Thu, 4 Jun 2009 15:57:38 +0000 Subject: [PATCH] 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 --- sys/net80211/ieee80211.h | 10 ++- sys/net80211/ieee80211_input.c | 12 +++ sys/net80211/ieee80211_ioctl.c | 8 +- sys/net80211/ieee80211_output.c | 2 +- sys/net80211/ieee80211_proto.c | 37 ++++++-- sys/net80211/ieee80211_proto.h | 1 + sys/net80211/ieee80211_scan.h | 4 +- sys/net80211/ieee80211_sta.c | 152 ++++++++++++++++++++++++++++++-- sys/net80211/ieee80211_var.h | 3 +- 9 files changed, 207 insertions(+), 22 deletions(-) diff --git a/sys/net80211/ieee80211.h b/sys/net80211/ieee80211.h index d9b4c54409d..88e781c5733 100644 --- a/sys/net80211/ieee80211.h +++ b/sys/net80211/ieee80211.h @@ -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 diff --git a/sys/net80211/ieee80211_input.c b/sys/net80211/ieee80211_input.c index 53c2285592b..ad3fab46c42 100644 --- a/sys/net80211/ieee80211_input.c +++ b/sys/net80211/ieee80211_input.c @@ -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 diff --git a/sys/net80211/ieee80211_ioctl.c b/sys/net80211/ieee80211_ioctl.c index 36c6f4cf392..d88f81260a1 100644 --- a/sys/net80211/ieee80211_ioctl.c +++ b/sys/net80211/ieee80211_ioctl.c @@ -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); diff --git a/sys/net80211/ieee80211_output.c b/sys/net80211/ieee80211_output.c index a46b228af5f..602ab7a8de8 100644 --- a/sys/net80211/ieee80211_output.c +++ b/sys/net80211/ieee80211_output.c @@ -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); diff --git a/sys/net80211/ieee80211_proto.c b/sys/net80211/ieee80211_proto.c index 7e69929d6d7..9f80cdffd02 100644 --- a/sys/net80211/ieee80211_proto.c +++ b/sys/net80211/ieee80211_proto.c @@ -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); } /* diff --git a/sys/net80211/ieee80211_proto.h b/sys/net80211/ieee80211_proto.h index c9c3f07c9c6..3afb551d58e 100644 --- a/sys/net80211/ieee80211_proto.h +++ b/sys/net80211/ieee80211_proto.h @@ -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 *); /* diff --git a/sys/net80211/ieee80211_scan.h b/sys/net80211/ieee80211_scan.h index 9b4192e8c31..ac444742770 100644 --- a/sys/net80211/ieee80211_scan.h +++ b/sys/net80211/ieee80211_scan.h @@ -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]; }; /* diff --git a/sys/net80211/ieee80211_sta.c b/sys/net80211/ieee80211_sta.c index 0dee283c217..097d2f26d25 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; @@ -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 diff --git a/sys/net80211/ieee80211_var.h b/sys/net80211/ieee80211_var.h index 3e999c8da88..df7575f762d 100644 --- a/sys/net80211/ieee80211_var.h +++ b/sys/net80211/ieee80211_var.h @@ -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 */ -- 2.45.0