Some access points have a feature called "band steering" where they
will try to push clients from 2 GHz channels to 5 GHz channels.
Such APs keep track of probe-requests from the same client on
different bands. If a client sends probe-requests on both 2 GHz
and 5GHz channels, and then attempts to authenticate on a 2 GHz
channel, the AP will deny authentication and hope that the client
will come back on a 5 GHz channel.
A problem with our current behaviour is that we will keep trying
to AUTH to the same AP as we've selected. The user perceives this
as not being able to get link. The loop becomes apparent when
debug mode is enabled with ifconfig. This message will appear
in dmesg over and over again: "open authentication failed"
As far as I know, band-steering is an out-of-spec trick. The Aruba APs
I have for testing are sending auth failure status "too many associated
clients". But I don't know if other APs behave in the same way, and
I don't know if they're all using the same status code for steering.
So this implementation is generic: If we fail to AUTH for any reason,
and if there is a different AP with the same ESSID that we haven't
tried yet, try that AP next. Keep trying until no APs are left,
and only then continue scanning.
A simple case with a single dual-band AP looks like this:
+ d8:c7:c8:11:bf:60 11 +58 54M ess privacy rsn "11kvr"
+ d8:c7:c8:11:bf:68 116 +50 54M ess privacy rsn "11kvr"
iwm0: SCAN -> AUTH
iwm0: sending auth to d8:c7:c8:11:bf:60 on channel 11 mode 11g
iwm0: open authentication failed (status 17) for d8:c7:c8:11:bf:60
+ d8:c7:c8:11:bf:68 116 +50 54M ess privacy rsn "11kvr"
iwm0: trying AP d8:c7:c8:11:bf:68 on channel 116 instead
iwm0: AUTH -> AUTH
iwm0: sending auth to d8:c7:c8:11:bf:68 on channel 116 mode 11a
iwm0: AUTH -> ASSOC
iwm0: sending assoc_req to d8:c7:c8:11:bf:68 on channel 116 mode 11a
iwm0: ASSOC -> RUN
iwm0: associated with d8:c7:c8:11:bf:68 ssid "11kvr" channel 116 start MCS 0
long preamble short slot time HT enabled
iwm0: missed beacon threshold set to 7 beacons, beacon interval is 100 TU
iwm0: received msg 1/4 of the 4-way handshake from d8:c7:c8:11:bf:68
iwm0: sending msg 2/4 of the 4-way handshake to d8:c7:c8:11:bf:68
iwm0: received msg 3/4 of the 4-way handshake from d8:c7:c8:11:bf:68
iwm0: sending msg 4/4 of the 4-way handshake to d8:c7:c8:11:bf:68
Note to reviewers:
This diff fixes a bug next to a /* ??? */ comment in ieee80211_newstate().
For AUTH->AUTH transitions in client mode, we want to send another AUTH
request (auth code 1, aka. IEEE80211_AUTH_OPEN_REQUEST) and not an AUTH
response (auth code 2, aka IEEE80211_AUTH_OPEN_RESPONSE).
I don't think AUTH->AUTH transitions make sense in AP mode, and in
any case we want the AP case to be handled by ieee80211_auth_open()
since it does some error checking before sending a response.
Index: ieee80211_node.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_node.c,v
retrieving revision 1.140
diff -u -p -r1.140 ieee80211_node.c
--- ieee80211_node.c 11 Aug 2018 10:58:39 -0000 1.140
+++ ieee80211_node.c 12 Aug 2018 10:14:03 -0000
@@ -76,7 +76,6 @@ void ieee80211_ba_del(struct ieee80211_n
struct ieee80211_node *ieee80211_alloc_node_helper(struct ieee80211com *);
void ieee80211_node_cleanup(struct ieee80211com *, struct ieee80211_node *);
void ieee80211_node_switch_bss(struct ieee80211com *, struct ieee80211_node *);
-void ieee80211_node_join_bss(struct ieee80211com *, struct ieee80211_node *);
void ieee80211_needs_auth(struct ieee80211com *, struct ieee80211_node *);
#ifndef IEEE80211_STA_ONLY
void ieee80211_node_join_ht(struct ieee80211com *, struct ieee80211_node *);
@@ -978,6 +977,9 @@ ieee80211_node_join_bss(struct ieee80211
int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
ic->ic_opmode == IEEE80211_M_STA &&
ic->ic_state == IEEE80211_S_RUN);
+ int auth_next = (ic->ic_opmode == IEEE80211_M_STA &&
+ ic->ic_state == IEEE80211_S_AUTH);
+ int mgt = -1;
timeout_del(&ic->ic_bgscan_timeout);
ic->ic_flags &= ~IEEE80211_F_BGSCAN;
@@ -988,9 +990,80 @@ ieee80211_node_join_bss(struct ieee80211
* ieee80211_new_state() try to re-auth and thus send
* an AUTH frame to our newly selected AP.
*/
- ieee80211_new_state(ic, IEEE80211_S_AUTH,
- bgscan ? IEEE80211_FC0_SUBTYPE_DEAUTH : -1);
+ if (bgscan)
+ mgt = IEEE80211_FC0_SUBTYPE_DEAUTH;
+ /*
+ * If we are trying another AP after the previous one
+ * failed (state transition AUTH->AUTH), ensure that
+ * ieee80211_new_state() tries to send another auth frame.
+ */
+ else if (auth_next)
+ mgt = IEEE80211_FC0_SUBTYPE_AUTH;
+
+ ieee80211_new_state(ic, IEEE80211_S_AUTH, mgt);
+ }
+}
+
+struct ieee80211_node *
+ieee80211_node_choose_bss(struct ieee80211com *ic, int bgscan,
+ struct ieee80211_node **curbs)
+{
+ struct ieee80211_node *ni, *nextbs, *selbs = NULL,
+ *selbs2 = NULL, *selbs5 = NULL;
+ uint8_t min_5ghz_rssi;
+
+ ni = RBT_MIN(ieee80211_tree, &ic->ic_tree);
+
+ for (; ni != NULL; ni = nextbs) {
+ nextbs = RBT_NEXT(ieee80211_tree, ni);
+ if (ni->ni_fails) {
+ /*
+ * The configuration of the access points may change
+ * during my scan. So delete the entry for the AP
+ * and retry to associate if there is another beacon.
+ */
+ if (ni->ni_fails++ > 2)
+ ieee80211_free_node(ic, ni);
+ continue;
+ }
+
+ if (curbs && ieee80211_node_cmp(ic->ic_bss, ni) == 0)
+ *curbs = ni;
+
+ if (ieee80211_match_bss(ic, ni) != 0)
+ continue;
+
+ if (ic->ic_caps & IEEE80211_C_SCANALLBAND) {
+ if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan) &&
+ (selbs2 == NULL || ni->ni_rssi > selbs2->ni_rssi))
+ selbs2 = ni;
+ else if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan) &&
+ (selbs5 == NULL || ni->ni_rssi > selbs5->ni_rssi))
+ selbs5 = ni;
+ } else if (selbs == NULL || ni->ni_rssi > selbs->ni_rssi)
+ selbs = ni;
}
+
+ if (ic->ic_max_rssi)
+ min_5ghz_rssi = IEEE80211_RSSI_THRES_RATIO_5GHZ;
+ else
+ min_5ghz_rssi = (uint8_t)IEEE80211_RSSI_THRES_5GHZ;
+
+ /*
+ * Prefer a 5Ghz AP even if its RSSI is weaker than the best 2Ghz AP
+ * (as long as it meets the minimum RSSI threshold) since the 5Ghz band
+ * is usually less saturated.
+ */
+ if (selbs5 && selbs5->ni_rssi > min_5ghz_rssi)
+ selbs = selbs5;
+ else if (selbs5 && selbs2)
+ selbs = (selbs5->ni_rssi >= selbs2->ni_rssi ? selbs5 : selbs2);
+ else if (selbs2)
+ selbs = selbs2;
+ else if (selbs5)
+ selbs = selbs5;
+
+ return selbs;
}
/*
@@ -1000,12 +1073,10 @@ void
ieee80211_end_scan(struct ifnet *ifp)
{
struct ieee80211com *ic = (void *)ifp;
- struct ieee80211_node *ni, *nextbs, *selbs = NULL, *curbs = NULL,
- *selbs2 = NULL, *selbs5 = NULL;
+ struct ieee80211_node *ni, *selbs = NULL, *curbs = NULL;
int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
ic->ic_opmode == IEEE80211_M_STA &&
ic->ic_state == IEEE80211_S_RUN);
- uint8_t min_5ghz_rssi;
if (ifp->if_flags & IFF_DEBUG)
printf("%s: end %s scan\n", ifp->if_xname,
@@ -1083,55 +1154,7 @@ ieee80211_end_scan(struct ifnet *ifp)
if (!bgscan && ic->ic_opmode == IEEE80211_M_STA)
ieee80211_match_ess(ic);
- for (; ni != NULL; ni = nextbs) {
- nextbs = RBT_NEXT(ieee80211_tree, ni);
- if (ni->ni_fails) {
- /*
- * The configuration of the access points may change
- * during my scan. So delete the entry for the AP
- * and retry to associate if there is another beacon.
- */
- if (ni->ni_fails++ > 2)
- ieee80211_free_node(ic, ni);
- continue;
- }
-
- if (bgscan && ieee80211_node_cmp(ic->ic_bss, ni) == 0)
- curbs = ni;
-
- if (ieee80211_match_bss(ic, ni) != 0)
- continue;
-
- if (ic->ic_caps & IEEE80211_C_SCANALLBAND) {
- if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan) &&
- (selbs2 == NULL || ni->ni_rssi > selbs2->ni_rssi))
- selbs2 = ni;
- else if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan) &&
- (selbs5 == NULL || ni->ni_rssi > selbs5->ni_rssi))
- selbs5 = ni;
- } else if (selbs == NULL || ni->ni_rssi > selbs->ni_rssi)
- selbs = ni;
- }
-
- if (ic->ic_max_rssi)
- min_5ghz_rssi = IEEE80211_RSSI_THRES_RATIO_5GHZ;
- else
- min_5ghz_rssi = (uint8_t)IEEE80211_RSSI_THRES_5GHZ;
-
- /*
- * Prefer a 5Ghz AP even if its RSSI is weaker than the best 2Ghz AP
- * (as long as it meets the minimum RSSI threshold) since the 5Ghz band
- * is usually less saturated.
- */
- if (selbs5 && selbs5->ni_rssi > min_5ghz_rssi)
- selbs = selbs5;
- else if (selbs5 && selbs2)
- selbs = (selbs5->ni_rssi >= selbs2->ni_rssi ? selbs5 : selbs2);
- else if (selbs2)
- selbs = selbs2;
- else if (selbs5)
- selbs = selbs5;
-
+ selbs = ieee80211_node_choose_bss(ic, bgscan, &curbs);
if (bgscan) {
struct ieee80211_node_switch_bss_arg *arg;
Index: ieee80211_node.h
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_node.h,v
retrieving revision 1.76
diff -u -p -r1.76 ieee80211_node.h
--- ieee80211_node.h 7 Aug 2018 18:13:14 -0000 1.76
+++ ieee80211_node.h 12 Aug 2018 09:16:32 -0000
@@ -416,6 +416,9 @@ void ieee80211_node_leave(struct ieee802
struct ieee80211_node *);
int ieee80211_match_bss(struct ieee80211com *,
struct ieee80211_node *);
+struct ieee80211_node *ieee80211_node_choose_bss(struct ieee80211com *, int,
+ struct ieee80211_node **);
+void ieee80211_node_join_bss(struct ieee80211com *, struct ieee80211_node *);
void ieee80211_create_ibss(struct ieee80211com* ,
struct ieee80211_channel *);
void ieee80211_notify_dtim(struct ieee80211com *);
Index: ieee80211_proto.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_proto.c,v
retrieving revision 1.88
diff -u -p -r1.88 ieee80211_proto.c
--- ieee80211_proto.c 6 Aug 2018 14:28:13 -0000 1.88
+++ ieee80211_proto.c 12 Aug 2018 10:32:53 -0000
@@ -734,6 +734,47 @@ ieee80211_auth_open_confirm(struct ieee8
#endif
void
+ieee80211_try_another_bss(struct ieee80211com *ic)
+{
+ struct ieee80211_node *curbs, *selbs;
+ struct ifnet *ifp = &ic->ic_if;
+
+ /* Don't select our current AP again. */
+ curbs = ieee80211_find_node(ic, ic->ic_bss->ni_macaddr);
+ if (curbs) {
+ curbs->ni_fails++;
+ ieee80211_node_newstate(curbs, IEEE80211_STA_CACHE);
+ }
+
+ /* Try a different AP from the same ESS if available. */
+ if (ic->ic_caps & IEEE80211_C_SCANALLBAND) {
+ /*
+ * Make sure we will consider APs on all bands during
+ * access point selection in ieee80211_node_choose_bss().
+ * During multi-band scans, our previous AP may be trying
+ * to steer us onto another band by denying authentication.
+ */
+ ieee80211_setmode(ic, IEEE80211_MODE_AUTO);
+ }
+ selbs = ieee80211_node_choose_bss(ic, 0, NULL);
+ if (selbs == NULL)
+ return;
+
+ /* Should not happen but seriously, don't try the same AP again. */
+ if (memcmp(selbs->ni_macaddr, ic->ic_bss->ni_macaddr,
+ IEEE80211_NWID_LEN) == 0)
+ return;
+
+ if (ifp->if_flags & IFF_DEBUG)
+ printf("%s: trying AP %s on channel %d instead\n",
+ ifp->if_xname, ether_sprintf(selbs->ni_macaddr),
+ ieee80211_chan2ieee(ic, selbs->ni_chan));
+
+ /* Triggers an AUTH->AUTH transition, avoiding another SCAN. */
+ ieee80211_node_join_bss(ic, selbs);
+}
+
+void
ieee80211_auth_open(struct ieee80211com *ic, const struct ieee80211_frame *wh,
struct ieee80211_node *ni, struct ieee80211_rxinfo *rxi, u_int16_t seq,
u_int16_t status)
@@ -821,6 +862,8 @@ ieee80211_auth_open(struct ieee80211com
ether_sprintf((u_int8_t *)wh->i_addr3));
if (ni != ic->ic_bss)
ni->ni_fails++;
+ else
+ ieee80211_try_another_bss(ic);
ic->ic_stats.is_rx_auth_fail++;
return;
}
@@ -1025,9 +1068,11 @@ justcleanup:
case IEEE80211_S_ASSOC:
switch (mgt) {
case IEEE80211_FC0_SUBTYPE_AUTH:
- /* ??? */
- IEEE80211_SEND_MGMT(ic, ni,
- IEEE80211_FC0_SUBTYPE_AUTH, 2);
+ if (ic->ic_opmode == IEEE80211_M_STA) {
+ IEEE80211_SEND_MGMT(ic, ni,
+ IEEE80211_FC0_SUBTYPE_AUTH,
+ IEEE80211_AUTH_OPEN_REQUEST);
+ }
break;
case IEEE80211_FC0_SUBTYPE_DEAUTH:
/* ignore and retry scan on timeout */