Add support for band-steering access points to net80211.
authorstsp <stsp@openbsd.org>
Mon, 13 Aug 2018 15:19:52 +0000 (15:19 +0000)
committerstsp <stsp@openbsd.org>
Mon, 13 Aug 2018 15:19:52 +0000 (15:19 +0000)
Some access points have a feature called "band steering" where they
will try to push clients from 2 GHz channels to 5 GHz channels.
If a client sends probe-requests on both 2 GHz and 5GHz channels, and
then attempts to authenticate on a 2 GHz channel, such APs will deny
authentication and hope that the client will come back on a 5 GHz channel.

So 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.

APs with support for this feature were provided by Mischa Peters.

ok phessler@ mpi@

sys/net80211/ieee80211_node.c
sys/net80211/ieee80211_node.h
sys/net80211/ieee80211_proto.c

index 6fcbf93..bd64aea 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_node.c,v 1.140 2018/08/11 10:58:39 stsp Exp $       */
+/*     $OpenBSD: ieee80211_node.c,v 1.141 2018/08/13 15:19:52 stsp Exp $       */
 /*     $NetBSD: ieee80211_node.c,v 1.14 2004/05/09 09:18:47 dyoung Exp $       */
 
 /*-
@@ -76,7 +76,6 @@ void ieee80211_ba_del(struct ieee80211_node *);
 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 ieee80211com *ic, struct ieee80211_node *selbs)
                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 ieee80211com *ic, struct ieee80211_node *selbs)
                 * 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 dd07921..334efcd 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_node.h,v 1.76 2018/08/07 18:13:14 stsp Exp $        */
+/*     $OpenBSD: ieee80211_node.h,v 1.77 2018/08/13 15:19:52 stsp Exp $        */
 /*     $NetBSD: ieee80211_node.h,v 1.9 2004/04/30 22:57:32 dyoung Exp $        */
 
 /*-
@@ -416,6 +416,9 @@ void ieee80211_node_leave(struct ieee80211com *,
                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 51f1d0c..c1832f7 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_proto.c,v 1.88 2018/08/06 14:28:13 stsp Exp $       */
+/*     $OpenBSD: ieee80211_proto.c,v 1.89 2018/08/13 15:19:52 stsp Exp $       */
 /*     $NetBSD: ieee80211_proto.c,v 1.8 2004/04/30 23:58:20 dyoung Exp $       */
 
 /*-
@@ -733,6 +733,47 @@ ieee80211_auth_open_confirm(struct ieee80211com *ic,
 }
 #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,
@@ -821,6 +862,8 @@ ieee80211_auth_open(struct ieee80211com *ic, const struct ieee80211_frame *wh,
                                    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 */