svn commit: r193439 - head/sys/net80211

Sam Leffler sam at FreeBSD.org
Thu Jun 4 15:57:39 UTC 2009


Author: sam
Date: Thu Jun  4 15:57:38 2009
New Revision: 193439
URL: http://svn.freebsd.org/changeset/base/193439

Log:
  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

Modified:
  head/sys/net80211/ieee80211.h
  head/sys/net80211/ieee80211_input.c
  head/sys/net80211/ieee80211_ioctl.c
  head/sys/net80211/ieee80211_output.c
  head/sys/net80211/ieee80211_proto.c
  head/sys/net80211/ieee80211_proto.h
  head/sys/net80211/ieee80211_scan.h
  head/sys/net80211/ieee80211_sta.c
  head/sys/net80211/ieee80211_var.h

Modified: head/sys/net80211/ieee80211.h
==============================================================================
--- head/sys/net80211/ieee80211.h	Thu Jun  4 15:10:29 2009	(r193438)
+++ head/sys/net80211/ieee80211.h	Thu Jun  4 15:57:38 2009	(r193439)
@@ -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

Modified: head/sys/net80211/ieee80211_input.c
==============================================================================
--- head/sys/net80211/ieee80211_input.c	Thu Jun  4 15:10:29 2009	(r193438)
+++ head/sys/net80211/ieee80211_input.c	Thu Jun  4 15:57:38 2009	(r193439)
@@ -475,6 +475,7 @@ ieee80211_parse_beacon(struct ieee80211_
 	 *	[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_
 		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_
 		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

Modified: head/sys/net80211/ieee80211_ioctl.c
==============================================================================
--- head/sys/net80211/ieee80211_ioctl.c	Thu Jun  4 15:10:29 2009	(r193438)
+++ head/sys/net80211/ieee80211_ioctl.c	Thu Jun  4 15:57:38 2009	(r193439)
@@ -2304,8 +2304,10 @@ ieee80211_ioctl_chanswitch(struct ieee80
 	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 ieee80
 	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);

Modified: head/sys/net80211/ieee80211_output.c
==============================================================================
--- head/sys/net80211/ieee80211_output.c	Thu Jun  4 15:10:29 2009	(r193438)
+++ head/sys/net80211/ieee80211_output.c	Thu Jun  4 15:57:38 2009	(r193439)
@@ -1468,7 +1468,7 @@ ieee80211_add_csa(uint8_t *frm, struct i
 	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);

Modified: head/sys/net80211/ieee80211_proto.c
==============================================================================
--- head/sys/net80211/ieee80211_proto.c	Thu Jun  4 15:10:29 2009	(r193438)
+++ head/sys/net80211/ieee80211_proto.c	Thu Jun  4 15:57:38 2009	(r193439)
@@ -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 ieee802
 	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 ieee802
 	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 ieee802
 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);
 }
 
 /*

Modified: head/sys/net80211/ieee80211_proto.h
==============================================================================
--- head/sys/net80211/ieee80211_proto.h	Thu Jun  4 15:10:29 2009	(r193438)
+++ head/sys/net80211/ieee80211_proto.h	Thu Jun  4 15:57:38 2009	(r193439)
@@ -340,6 +340,7 @@ int	ieee80211_beacon_update(struct ieee8
 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 *);
 
 /*

Modified: head/sys/net80211/ieee80211_scan.h
==============================================================================
--- head/sys/net80211/ieee80211_scan.h	Thu Jun  4 15:10:29 2009	(r193438)
+++ head/sys/net80211/ieee80211_scan.h	Thu Jun  4 15:57:38 2009	(r193439)
@@ -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];
 };
 
 /*

Modified: head/sys/net80211/ieee80211_sta.c
==============================================================================
--- head/sys/net80211/ieee80211_sta.c	Thu Jun  4 15:10:29 2009	(r193438)
+++ head/sys/net80211/ieee80211_sta.c	Thu Jun  4 15:57:38 2009	(r193439)
@@ -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));
-
-	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]);
+	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 %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, e
 		}
 		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, e
 		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 ieee802
 }
 
 /*
+ * 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,
 				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

Modified: head/sys/net80211/ieee80211_var.h
==============================================================================
--- head/sys/net80211/ieee80211_var.h	Thu Jun  4 15:10:29 2009	(r193438)
+++ head/sys/net80211/ieee80211_var.h	Thu Jun  4 15:57:38 2009	(r193439)
@@ -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 */


More information about the svn-src-all mailing list