This is an initial implementation of background scanning for iwm(4).

The intention is to transparently roam between access points with
the same SSID. This implementation is functionally complete, but it
may still need some tuning.

Background scans are triggered if the received signal strength
indicator for our current AP falls below a fixed threshold.
This is a simple heuristic. We could tweak this heuristic,
and add, or replace it with, other heuristics in the future.

If a better AP is found, we deauth from our current AP and then
try to associate with our new AP of choice. If we can successfully
associate, we roam with virtually no interruption of link.
If association fails, we drop into the usual scan loop as a fallback.

If no better AP is found, background scans will occur less and less
frequently over time. When a better AP is found, this backoff resets.
The longest interval is hard-coded to 3 minutes (if I got my math right).

Most changes are in the net80211 stack. Once this feature has proven
itself with iwm(4) we could add support for other drivers.

Slightly tested in the hack hut.

OK?

Index: dev/pci/if_iwm.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwm.c,v
retrieving revision 1.217
diff -u -p -r1.217 if_iwm.c
--- dev/pci/if_iwm.c    26 Oct 2017 15:00:28 -0000      1.217
+++ dev/pci/if_iwm.c    29 Nov 2017 18:53:27 -0000
@@ -420,9 +420,9 @@ uint32_t iwm_scan_rate_n_flags(struct iw
 uint8_t        iwm_lmac_scan_fill_channels(struct iwm_softc *,
            struct iwm_scan_channel_cfg_lmac *, int);
 int    iwm_fill_probe_req(struct iwm_softc *, struct iwm_scan_probe_req *);
-int    iwm_lmac_scan(struct iwm_softc *);
+int    iwm_lmac_scan(struct iwm_softc *, int);
 int    iwm_config_umac_scan(struct iwm_softc *);
-int    iwm_umac_scan(struct iwm_softc *);
+int    iwm_umac_scan(struct iwm_softc *, int);
 uint8_t        iwm_ridx2rate(struct ieee80211_rateset *, int);
 int    iwm_rval2ridx(int);
 void   iwm_ack_rates(struct iwm_softc *, struct iwm_node *, int *, int *);
@@ -435,6 +435,7 @@ int iwm_update_quotas(struct iwm_softc *
 void   iwm_add_task(struct iwm_softc *, struct taskq *, struct task *);
 void   iwm_del_task(struct iwm_softc *, struct taskq *, struct task *);
 int    iwm_scan(struct iwm_softc *);
+int    iwm_bgscan(struct ieee80211com *);
 int    iwm_auth(struct iwm_softc *);
 int    iwm_deauth(struct iwm_softc *);
 int    iwm_assoc(struct iwm_softc *);
@@ -4868,7 +4869,7 @@ iwm_fill_probe_req(struct iwm_softc *sc,
 }
 
 int
-iwm_lmac_scan(struct iwm_softc *sc)
+iwm_lmac_scan(struct iwm_softc *sc, int bgscan)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        struct iwm_host_cmd hcmd = {
@@ -4879,28 +4880,34 @@ iwm_lmac_scan(struct iwm_softc *sc)
        };
        struct iwm_scan_req_lmac *req;
        size_t req_len;
-       int err;
+       int err, async = bgscan;
 
        req_len = sizeof(struct iwm_scan_req_lmac) +
            (sizeof(struct iwm_scan_channel_cfg_lmac) *
            sc->sc_capa_n_scan_channels) + sizeof(struct iwm_scan_probe_req);
        if (req_len > IWM_MAX_CMD_PAYLOAD_SIZE)
                return ENOMEM;
-       req = malloc(req_len, M_DEVBUF, M_WAIT | M_CANFAIL | M_ZERO);
+       req = malloc(req_len, M_DEVBUF,
+           (async ? M_NOWAIT : M_WAIT) | M_CANFAIL | M_ZERO);
        if (req == NULL)
                return ENOMEM;
 
        hcmd.len[0] = (uint16_t)req_len;
        hcmd.data[0] = (void *)req;
+       hcmd.flags |= async ? IWM_CMD_ASYNC : 0;
 
        /* These timings correspond to iwlwifi's UNASSOC scan. */
        req->active_dwell = 10;
        req->passive_dwell = 110;
        req->fragmented_dwell = 44;
        req->extended_dwell = 90;
-       req->max_out_time = 0;
-       req->suspend_time = 0;
-
+       if (bgscan) {
+               req->max_out_time = htole32(120);
+               req->suspend_time = htole32(120);
+       } else {
+               req->max_out_time = htole32(0);
+               req->suspend_time = htole32(0);
+       }
        req->scan_prio = htole32(IWM_SCAN_PRIORITY_HIGH);
        req->rx_chain_select = iwm_scan_rx_chain(sc);
        req->iter_num = htole32(1);
@@ -5048,7 +5055,7 @@ iwm_config_umac_scan(struct iwm_softc *s
 }
 
 int
-iwm_umac_scan(struct iwm_softc *sc)
+iwm_umac_scan(struct iwm_softc *sc, int bgscan)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        struct iwm_host_cmd hcmd = {
@@ -5060,7 +5067,7 @@ iwm_umac_scan(struct iwm_softc *sc)
        struct iwm_scan_req_umac *req;
        struct iwm_scan_req_umac_tail *tail;
        size_t req_len;
-       int err;
+       int err, async = bgscan;
 
        req_len = sizeof(struct iwm_scan_req_umac) +
            (sizeof(struct iwm_scan_channel_cfg_umac) *
@@ -5068,20 +5075,27 @@ iwm_umac_scan(struct iwm_softc *sc)
            sizeof(struct iwm_scan_req_umac_tail);
        if (req_len > IWM_MAX_CMD_PAYLOAD_SIZE)
                return ENOMEM;
-       req = malloc(req_len, M_DEVBUF, M_WAIT | M_CANFAIL | M_ZERO);
+       req = malloc(req_len, M_DEVBUF,
+           (async ? M_NOWAIT : M_WAIT) | M_CANFAIL | M_ZERO);
        if (req == NULL)
                return ENOMEM;
 
        hcmd.len[0] = (uint16_t)req_len;
        hcmd.data[0] = (void *)req;
+       hcmd.flags |= async ? IWM_CMD_ASYNC : 0;
 
        /* These timings correspond to iwlwifi's UNASSOC scan. */
        req->active_dwell = 10;
        req->passive_dwell = 110;
        req->fragmented_dwell = 44;
        req->extended_dwell = 90;
-       req->max_out_time = 0;
-       req->suspend_time = 0;
+       if (bgscan) {
+               req->max_out_time = htole32(120);
+               req->suspend_time = htole32(120);
+       } else {
+               req->max_out_time = htole32(0);
+               req->suspend_time = htole32(0);
+       }
 
        req->scan_priority = htole32(IWM_SCAN_PRIORITY_HIGH);
        req->ooc_priority = htole32(IWM_SCAN_PRIORITY_HIGH);
@@ -5465,9 +5479,9 @@ iwm_scan(struct iwm_softc *sc)
                return 0;
 
        if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN))
-               err = iwm_umac_scan(sc);
+               err = iwm_umac_scan(sc, 0);
        else
-               err = iwm_lmac_scan(sc);
+               err = iwm_lmac_scan(sc, 0);
        if (err) {
                printf("%s: could not initiate scan\n", DEVNAME(sc));
                return err;
@@ -5482,6 +5496,28 @@ iwm_scan(struct iwm_softc *sc)
 }
 
 int
+iwm_bgscan(struct ieee80211com *ic) 
+{
+       struct iwm_softc *sc = IC2IFP(ic)->if_softc;
+       int err;
+
+       if (sc->sc_flags & IWM_FLAG_SCANNING)
+               return 0;
+
+       if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN))
+               err = iwm_umac_scan(sc, 1);
+       else
+               err = iwm_lmac_scan(sc, 1);
+       if (err) {
+               printf("%s: could not initiate scan\n", DEVNAME(sc));
+               return err;
+       }
+
+       sc->sc_flags |= IWM_FLAG_SCANNING;
+       return 0;
+}
+
+int
 iwm_auth(struct iwm_softc *sc)
 {
        struct ieee80211com *ic = &sc->sc_ic;
@@ -6524,9 +6560,10 @@ iwm_start(struct ifnet *ifp)
                        ni = m->m_pkthdr.ph_cookie;
                        goto sendit;
                }
-               if (ic->ic_state != IEEE80211_S_RUN) {
+
+               if (ic->ic_state != IEEE80211_S_RUN ||
+                   (ic->ic_xflags & IEEE80211_F_TX_MGMT_ONLY))
                        break;
-               }
 
                IFQ_DEQUEUE(&ifp->if_snd, m);
                if (!m)
@@ -7772,6 +7809,7 @@ iwm_attach(struct device *parent, struct
        task_set(&sc->htprot_task, iwm_htprot_task, sc);
 
        ic->ic_node_alloc = iwm_node_alloc;
+       ic->ic_bgscan_start = iwm_bgscan;
 
        /* Override 802.11 state transition machine. */
        sc->sc_newstate = ic->ic_newstate;
Index: net80211/ieee80211.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211.c,v
retrieving revision 1.63
diff -u -p -r1.63 ieee80211.c
--- net80211/ieee80211.c        5 Sep 2017 12:02:21 -0000       1.63
+++ net80211/ieee80211.c        28 Nov 2017 23:26:51 -0000
@@ -72,6 +72,31 @@ void ieee80211_setbasicrates(struct ieee
 int ieee80211_findrate(struct ieee80211com *, enum ieee80211_phymode, int);
 
 void
+ieee80211_begin_bgscan(struct ifnet *ifp)
+{
+       struct ieee80211com *ic = (void *)ifp;
+
+       if (ic->ic_flags & IEEE80211_F_BGSCAN)
+               return;
+
+       if (ic->ic_bgscan_start != NULL && ic->ic_bgscan_start(ic) == 0) {
+               ic->ic_flags |= IEEE80211_F_BGSCAN;
+               if (ifp->if_flags & IFF_DEBUG)
+                       printf("%s: begin background scan\n", ifp->if_xname);
+
+               /* Driver calls ieee80211_end_scan() when done. */
+       }
+}
+
+void
+ieee80211_bgscan_timeout(void *arg)
+{
+       struct ifnet *ifp = arg;
+
+       ieee80211_begin_bgscan(ifp);
+}
+
+void
 ieee80211_channel_init(struct ifnet *ifp)
 {
        struct ieee80211com *ic = (void *)ifp;
@@ -158,6 +183,8 @@ ieee80211_ifattach(struct ifnet *ifp)
        ifp->if_priority = IF_WIRELESS_DEFAULT_PRIORITY;
 
        ieee80211_set_link_state(ic, LINK_STATE_DOWN);
+
+       timeout_set(&ic->ic_bgscan_timeout, ieee80211_bgscan_timeout, ifp);
 }
 
 void
Index: net80211/ieee80211_input.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_input.c,v
retrieving revision 1.196
diff -u -p -r1.196 ieee80211_input.c
--- net80211/ieee80211_input.c  4 Sep 2017 09:11:46 -0000       1.196
+++ net80211/ieee80211_input.c  28 Nov 2017 18:54:19 -0000
@@ -267,6 +267,14 @@ ieee80211_input(struct ifnet *ifp, struc
                ni->ni_rssi = rxi->rxi_rssi;
                ni->ni_rstamp = rxi->rxi_tstamp;
                ni->ni_inact = 0;
+
+               /* Cancel or start background scan based on RSSI. */
+               if ((*ic->ic_node_checkrssi)(ic, ni))
+                       timeout_del(&ic->ic_bgscan_timeout);
+               else if (!timeout_pending(&ic->ic_bgscan_timeout) &&
+                   (ic->ic_flags & IEEE80211_F_BGSCAN) == 0)
+                       timeout_add_msec(&ic->ic_bgscan_timeout,
+                           500 * (ic->ic_bgscan_fail + 1));
        }
 
 #ifndef IEEE80211_STA_ONLY
@@ -1462,6 +1470,7 @@ ieee80211_recv_probe_resp(struct ieee802
        }
        if ((ic->ic_state != IEEE80211_S_SCAN ||
             !(ic->ic_caps & IEEE80211_C_SCANALL)) &&
+           (ic->ic_flags & IEEE80211_F_BGSCAN) == 0 &&
            chan != bchan) {
                /*
                 * Frame was received on a channel different from the
@@ -1504,7 +1513,8 @@ ieee80211_recv_probe_resp(struct ieee802
 
 #ifdef IEEE80211_DEBUG
        if (ieee80211_debug > 1 &&
-           (ni == NULL || ic->ic_state == IEEE80211_S_SCAN)) {
+           (ni == NULL || ic->ic_state == IEEE80211_S_SCAN ||
+           (ic->ic_flags & IEEE80211_F_BGSCAN))) {
                printf("%s: %s%s on chan %u (bss chan %u) ",
                    __func__, (ni == NULL ? "new " : ""),
                    isprobe ? "probe response" : "beacon",
@@ -1589,7 +1599,8 @@ ieee80211_recv_probe_resp(struct ieee802
                 * This probe response indicates the AP is still serving us
                 * so don't allow ieee80211_watchdog() to move us into SCAN.
                 */
-                ic->ic_mgt_timer = 0;
+                if ((ic->ic_flags & IEEE80211_F_BGSCAN) == 0)
+                       ic->ic_mgt_timer = 0;
        }
        /*
         * We do not try to update EDCA parameters if QoS was not negotiated
@@ -1606,7 +1617,8 @@ ieee80211_recv_probe_resp(struct ieee802
                        ni->ni_flags &= ~IEEE80211_NODE_QOS;
        }
 
-       if (ic->ic_state == IEEE80211_S_SCAN) {
+       if (ic->ic_state == IEEE80211_S_SCAN ||
+           (ic->ic_flags & IEEE80211_F_BGSCAN)) {
                struct ieee80211_rsnparams rsn, wpa;
 
                ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
@@ -2361,9 +2373,13 @@ ieee80211_recv_deauth(struct ieee80211co
 
        ic->ic_stats.is_rx_deauth++;
        switch (ic->ic_opmode) {
-       case IEEE80211_M_STA:
-               ieee80211_new_state(ic, IEEE80211_S_AUTH,
-                   IEEE80211_FC0_SUBTYPE_DEAUTH);
+       case IEEE80211_M_STA: {
+               int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
+                   ic->ic_state == IEEE80211_S_RUN);
+               if (!bgscan) /* ignore deauth during bgscan */
+                       ieee80211_new_state(ic, IEEE80211_S_AUTH,
+                           IEEE80211_FC0_SUBTYPE_DEAUTH);
+               }
                break;
 #ifndef IEEE80211_STA_ONLY
        case IEEE80211_M_HOSTAP:
@@ -2407,9 +2423,13 @@ ieee80211_recv_disassoc(struct ieee80211
 
        ic->ic_stats.is_rx_disassoc++;
        switch (ic->ic_opmode) {
-       case IEEE80211_M_STA:
-               ieee80211_new_state(ic, IEEE80211_S_ASSOC,
-                   IEEE80211_FC0_SUBTYPE_DISASSOC);
+       case IEEE80211_M_STA: {
+               int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
+                   ic->ic_state == IEEE80211_S_RUN);
+               if (!bgscan) /* ignore disassoc during bgscan */
+                       ieee80211_new_state(ic, IEEE80211_S_ASSOC,
+                           IEEE80211_FC0_SUBTYPE_DISASSOC);
+               }
                break;
 #ifndef IEEE80211_STA_ONLY
        case IEEE80211_M_HOSTAP:
Index: net80211/ieee80211_node.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_node.c,v
retrieving revision 1.121
diff -u -p -r1.121 ieee80211_node.c
--- net80211/ieee80211_node.c   5 Sep 2017 14:56:59 -0000       1.121
+++ net80211/ieee80211_node.c   29 Nov 2017 18:39:24 -0000
@@ -65,12 +65,16 @@ void ieee80211_node_copy(struct ieee8021
 void ieee80211_choose_rsnparams(struct ieee80211com *);
 u_int8_t ieee80211_node_getrssi(struct ieee80211com *,
     const struct ieee80211_node *);
+int ieee80211_node_checkrssi(struct ieee80211com *,
+    const struct ieee80211_node *);
 void ieee80211_setup_node(struct ieee80211com *, struct ieee80211_node *,
     const u_int8_t *);
 void ieee80211_free_node(struct ieee80211com *, struct ieee80211_node *);
 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 *);
@@ -128,6 +132,7 @@ ieee80211_node_attach(struct ifnet *ifp)
        ic->ic_node_free = ieee80211_node_free;
        ic->ic_node_copy = ieee80211_node_copy;
        ic->ic_node_getrssi = ieee80211_node_getrssi;
+       ic->ic_node_checkrssi = ieee80211_node_checkrssi;
        ic->ic_scangen = 1;
        ic->ic_max_nnodes = ieee80211_cache_size;
 
@@ -546,6 +551,113 @@ ieee80211_match_bss(struct ieee80211com 
        return fail;
 }
 
+struct ieee80211_node_switch_bss_arg {
+       u_int8_t cur_macaddr[IEEE80211_ADDR_LEN];
+       u_int8_t sel_macaddr[IEEE80211_ADDR_LEN];
+};
+
+/* Implements ni->ni_unref_cb(). */
+void
+ieee80211_node_switch_bss(struct ieee80211com *ic, struct ieee80211_node *ni)
+{
+       struct ifnet *ifp = &ic->ic_if;
+       struct ieee80211_node_switch_bss_arg *sba = ni->ni_unref_arg;
+       struct ieee80211_node *curbs, *selbs;
+
+       splassert(IPL_NET);
+
+       if ((ic->ic_flags & IEEE80211_F_BGSCAN) == 0) {
+               free(sba, M_DEVBUF, sizeof(*sba));
+               return;
+       }
+
+       ic->ic_xflags &= ~IEEE80211_F_TX_MGMT_ONLY;
+
+       selbs = ieee80211_find_node(ic, sba->sel_macaddr);
+       if (selbs == NULL) {
+               free(sba, M_DEVBUF, sizeof(*sba));
+               ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+               ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
+               return;
+       }
+
+       curbs = ieee80211_find_node(ic, sba->cur_macaddr);
+       if (curbs == NULL) {
+               free(sba, M_DEVBUF, sizeof(*sba));
+               ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+               ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
+               return;
+       }
+
+       if (ifp->if_flags & IFF_DEBUG) {
+               printf("%s: roaming from %s chan %d ",
+                   ifp->if_xname, ether_sprintf(curbs->ni_macaddr),
+                   ieee80211_chan2ieee(ic, curbs->ni_chan));
+               printf("to %s chan %d\n", ether_sprintf(selbs->ni_macaddr),
+                   ieee80211_chan2ieee(ic, selbs->ni_chan));
+       }
+       ieee80211_node_newstate(curbs, IEEE80211_STA_CACHE);
+       ieee80211_node_join_bss(ic, selbs); /* frees arg and ic->ic_bss */
+}
+
+void
+ieee80211_node_join_bss(struct ieee80211com *ic, struct ieee80211_node *selbs)
+{
+       enum ieee80211_phymode mode;
+       struct ieee80211_node *ni;
+
+       /* Reinitialize media mode and channels if needed. */
+       mode = ieee80211_chan2mode(ic, selbs->ni_chan);
+       if (mode != ic->ic_curmode)
+               ieee80211_setmode(ic, mode);
+
+       (*ic->ic_node_copy)(ic, ic->ic_bss, selbs);
+       ni = ic->ic_bss;
+
+       ic->ic_curmode = ieee80211_chan2mode(ic, ni->ni_chan);
+
+       /* Make sure we send valid rates in an association request. */
+       if (ic->ic_opmode == IEEE80211_M_STA)
+               ieee80211_fix_rate(ic, ni,
+                   IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE |
+                   IEEE80211_F_DONEGO | IEEE80211_F_DODEL);
+
+       if (ic->ic_flags & IEEE80211_F_RSNON)
+               ieee80211_choose_rsnparams(ic);
+       else if (ic->ic_flags & IEEE80211_F_WEPON)
+               ni->ni_rsncipher = IEEE80211_CIPHER_USEGROUP;
+
+       ieee80211_node_newstate(selbs, IEEE80211_STA_BSS);
+#ifndef IEEE80211_STA_ONLY
+       if (ic->ic_opmode == IEEE80211_M_IBSS) {
+               ieee80211_fix_rate(ic, ni, IEEE80211_F_DOFRATE |
+                   IEEE80211_F_DONEGO | IEEE80211_F_DODEL);
+               if (ni->ni_rates.rs_nrates == 0) {
+                       ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
+                       return;
+               }
+               ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
+       } else
+#endif
+       {
+               int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
+                   ic->ic_opmode == IEEE80211_M_STA &&
+                   ic->ic_state == IEEE80211_S_RUN);
+
+               if (bgscan)
+                       ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+
+               /* 
+                * After a background scan, we have now switched APs.
+                * Pretend we were just de-authed, which makes
+                * 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);
+       }
+}
+
 /*
  * Complete a scan of potential channels.
  */
@@ -553,12 +665,16 @@ void
 ieee80211_end_scan(struct ifnet *ifp)
 {
        struct ieee80211com *ic = (void *)ifp;
-       struct ieee80211_node *ni, *nextbs, *selbs;
+       struct ieee80211_node *ni, *nextbs, *selbs, *curbs;
+       int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
+           ic->ic_opmode == IEEE80211_M_STA &&
+           ic->ic_state == IEEE80211_S_RUN);
 
        if (ifp->if_flags & IFF_DEBUG)
                printf("%s: end %s scan\n", ifp->if_xname,
-                       (ic->ic_flags & IEEE80211_F_ASCAN) ?
-                               "active" : "passive");
+                   bgscan ? "background" :
+                   ((ic->ic_flags & IEEE80211_F_ASCAN) ?
+                   "active" : "passive"));
 
        if (ic->ic_scan_count)
                ic->ic_flags &= ~IEEE80211_F_ASCAN;
@@ -634,6 +750,7 @@ ieee80211_end_scan(struct ifnet *ifp)
                return;
        }
        selbs = NULL;
+       curbs = NULL;
 
        for (; ni != NULL; ni = nextbs) {
                nextbs = RBT_NEXT(ieee80211_tree, ni);
@@ -647,6 +764,10 @@ ieee80211_end_scan(struct ifnet *ifp)
                                ieee80211_free_node(ic, ni);
                        continue;
                }
+
+               if (ni->ni_state == IEEE80211_STA_BSS)
+                       curbs = ni;
+
                if (ieee80211_match_bss(ic, ni) != 0)
                        continue;
 
@@ -657,21 +778,17 @@ ieee80211_end_scan(struct ifnet *ifp)
                    IEEE80211_IS_CHAN_5GHZ(selbs->ni_chan) &&
                    IEEE80211_IS_CHAN_2GHZ(ni->ni_chan) &&
                    ni->ni_rssi > selbs->ni_rssi) {
-                       uint8_t min_rssi = 0, max_rssi = ic->ic_max_rssi;
+                       uint8_t min_rssi;
 
                        /* 
                         * Prefer 5GHz (with reasonable RSSI) over 2GHz since
                         * the 5GHz band is usually less saturated.
                         */
-                       if (max_rssi) {
-                               /* Driver reports RSSI relative to maximum. */
-                               if (ni->ni_rssi > max_rssi / 3)
-                                       min_rssi = ni->ni_rssi - (max_rssi / 3);
-                       } else {
-                               /* Driver reports RSSI value in dBm. */
-                               if (ni->ni_rssi > 10) /* XXX magic number */
-                                       min_rssi = ni->ni_rssi - 10;
-                       }
+                       if (ic->ic_max_rssi)
+                               min_rssi = IEEE80211_RSSI_THRES_RATIO_5GHZ;
+                       else
+                               min_rssi = (uint8_t)IEEE80211_RSSI_THRES_5GHZ;
+
                        if (selbs->ni_rssi >= min_rssi)
                                continue;
                }
@@ -679,35 +796,65 @@ ieee80211_end_scan(struct ifnet *ifp)
                if (ni->ni_rssi > selbs->ni_rssi)
                        selbs = ni;
        }
-       if (selbs == NULL)
-               goto notfound;
-       (*ic->ic_node_copy)(ic, ic->ic_bss, selbs);
-       ni = ic->ic_bss;
 
-       ic->ic_curmode = ieee80211_chan2mode(ic, ni->ni_chan);
+       if (bgscan) {
+               struct ieee80211_node_switch_bss_arg *arg;
 
-       /* Make sure we send valid rates in an association request. */
-       if (ic->ic_opmode == IEEE80211_M_STA)
-               ieee80211_fix_rate(ic, ni,
-                   IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE |
-                   IEEE80211_F_DONEGO | IEEE80211_F_DODEL);
+               /* AP disappeared? Should not happen. */
+               if (selbs == NULL || curbs == NULL) {
+                       ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+                       goto notfound;
+               }
 
-       if (ic->ic_flags & IEEE80211_F_RSNON)
-               ieee80211_choose_rsnparams(ic);
-       else if (ic->ic_flags & IEEE80211_F_WEPON)
-               ni->ni_rsncipher = IEEE80211_CIPHER_USEGROUP;
+               /* 
+                * After a background scan we might end up choosing the
+                * same AP again. Do not change ic->ic_bss in this case,
+                * and make background scans less frequent.
+                */
+               if (selbs == curbs) {
+                       if (ic->ic_bgscan_fail < IEEE80211_BGSCAN_FAIL_MAX)
+                               ic->ic_bgscan_fail++;
+                       ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+                       goto wakeup;
+               }
+       
+               arg = malloc(sizeof(*arg), M_DEVBUF, M_NOWAIT | M_ZERO);
+               if (arg == NULL) {
+                       ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+                       goto wakeup;
+               }
 
-       ieee80211_node_newstate(selbs, IEEE80211_STA_BSS);
-#ifndef IEEE80211_STA_ONLY
-       if (ic->ic_opmode == IEEE80211_M_IBSS) {
-               ieee80211_fix_rate(ic, ni, IEEE80211_F_DOFRATE |
-                   IEEE80211_F_DONEGO | IEEE80211_F_DODEL);
-               if (ni->ni_rates.rs_nrates == 0)
-                       goto notfound;
-               ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
-       } else
-#endif
-               ieee80211_new_state(ic, IEEE80211_S_AUTH, -1);
+               ic->ic_bgscan_fail = 0;
+
+               /* 
+                * We are going to switch APs.
+                * Queue a de-auth frame addressed to our current AP.
+                */
+               if (IEEE80211_SEND_MGMT(ic, ic->ic_bss,
+                   IEEE80211_FC0_SUBTYPE_DEAUTH,
+                   IEEE80211_REASON_AUTH_LEAVE) != 0) {
+                       ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+                       goto wakeup;
+               }
+
+               /* Prevent dispatch of additional data frames to hardware. */
+               ic->ic_xflags |= IEEE80211_F_TX_MGMT_ONLY;
+
+               /* 
+                * Install a callback which will switch us to the new AP once
+                * all dispatched frames have been processed by hardware.
+                */
+               IEEE80211_ADDR_COPY(arg->cur_macaddr, curbs->ni_macaddr);
+               IEEE80211_ADDR_COPY(arg->sel_macaddr, selbs->ni_macaddr);
+               ic->ic_bss->ni_unref_arg = arg;
+               ic->ic_bss->ni_unref_arg_size = sizeof(*arg);
+               ic->ic_bss->ni_unref_cb = ieee80211_node_switch_bss;
+               /* F_BGSCAN flag gets cleared in ieee80211_node_join_bss(). */
+               goto wakeup;
+       } else if (selbs == NULL)
+               goto notfound;
+
+       ieee80211_node_join_bss(ic, selbs);
 
  wakeup:
        if (ic->ic_scan_lock & IEEE80211_SCAN_REQUEST) {
@@ -808,6 +955,9 @@ ieee80211_node_cleanup(struct ieee80211c
                ni->ni_rsnie = NULL;
        }
        ieee80211_ba_del(ni);
+       free(ni->ni_unref_arg, M_DEVBUF, ni->ni_unref_arg_size);
+       ni->ni_unref_arg = NULL;
+       ni->ni_unref_arg_size = 0;
 }
 
 void
@@ -835,6 +985,25 @@ ieee80211_node_getrssi(struct ieee80211c
        return ni->ni_rssi;
 }
 
+int
+ieee80211_node_checkrssi(struct ieee80211com *ic,
+    const struct ieee80211_node *ni)
+{
+       uint8_t thres;
+
+       if (ic->ic_max_rssi) {
+               thres = (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) ?
+                   IEEE80211_RSSI_THRES_RATIO_2GHZ :
+                   IEEE80211_RSSI_THRES_RATIO_5GHZ;
+               return ((ni->ni_rssi * 100) / ic->ic_max_rssi >= thres);
+       }
+
+       thres = (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) ?
+           IEEE80211_RSSI_THRES_2GHZ :
+           IEEE80211_RSSI_THRES_5GHZ;
+       return (ni->ni_rssi >= (u_int8_t)thres);
+}
+
 void
 ieee80211_setup_node(struct ieee80211com *ic,
        struct ieee80211_node *ni, const u_int8_t *macaddr)
@@ -1177,9 +1346,16 @@ ieee80211_release_node(struct ieee80211c
        DPRINTF(("%s refcnt %u\n", ether_sprintf(ni->ni_macaddr),
            ni->ni_refcnt));
        s = splnet();
-       if (ieee80211_node_decref(ni) == 0 &&
-           ni->ni_state == IEEE80211_STA_COLLECT) {
-               ieee80211_free_node(ic, ni);
+       if (ieee80211_node_decref(ni) == 0) {
+               if (ni->ni_unref_cb) {
+                       (*ni->ni_unref_cb)(ic, ni);
+                       ni->ni_unref_cb = NULL;
+                       /* Freed by callback if necessary: */
+                       ni->ni_unref_arg = NULL;
+                       ni->ni_unref_arg_size = 0;
+               }
+               if (ni->ni_state == IEEE80211_STA_COLLECT)
+                       ieee80211_free_node(ic, ni);
        }
        splx(s);
 }
Index: net80211/ieee80211_node.h
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_node.h,v
retrieving revision 1.69
diff -u -p -r1.69 ieee80211_node.h
--- net80211/ieee80211_node.h   17 Aug 2017 06:01:05 -0000      1.69
+++ net80211/ieee80211_node.h   29 Nov 2017 17:35:35 -0000
@@ -297,6 +297,12 @@ struct ieee80211_node {
 #define IEEE80211_NODE_SA_QUERY                0x0800  /* SA Query in progress 
*/
 #define IEEE80211_NODE_SA_QUERY_FAILED 0x1000  /* last SA Query failed */
 #define IEEE80211_NODE_RSN_NEW_PTK     0x2000  /* expecting a new PTK */
+
+       /* If not NULL, this function gets called when ni_refcnt hits zero. */
+       void                    (*ni_unref_cb)(struct ieee80211com *,
+                                       struct ieee80211_node *);
+       void *                  ni_unref_arg;
+       size_t                  ni_unref_arg_size;
 };
 
 RBT_HEAD(ieee80211_tree, ieee80211_node);
Index: net80211/ieee80211_proto.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_proto.c,v
retrieving revision 1.80
diff -u -p -r1.80 ieee80211_proto.c
--- net80211/ieee80211_proto.c  18 Aug 2017 17:30:12 -0000      1.80
+++ net80211/ieee80211_proto.c  29 Nov 2017 18:07:38 -0000
@@ -853,6 +853,7 @@ ieee80211_newstate(struct ieee80211com *
        ic->ic_state = nstate;                  /* state transition */
        ni = ic->ic_bss;                        /* NB: no reference held */
        ieee80211_set_link_state(ic, LINK_STATE_DOWN);
+       ic->ic_xflags &= ~IEEE80211_F_TX_MGMT_ONLY;
        switch (nstate) {
        case IEEE80211_S_INIT:
                /*
@@ -919,6 +920,8 @@ justcleanup:
                        if (ic->ic_opmode == IEEE80211_M_HOSTAP)
                                timeout_del(&ic->ic_rsn_timeout);
 #endif
+                       timeout_del(&ic->ic_bgscan_timeout);
+                       ic->ic_bgscan_fail = 0;
                        ic->ic_mgt_timer = 0;
                        mq_purge(&ic->ic_mgtq);
                        mq_purge(&ic->ic_pwrsaveq);
@@ -968,6 +971,8 @@ justcleanup:
                                    " rescanning\n", ifp->if_xname,
                                    ether_sprintf(ic->ic_bss->ni_bssid));
                        }
+                       timeout_del(&ic->ic_bgscan_timeout);
+                       ic->ic_bgscan_fail = 0;
                        ieee80211_free_allnodes(ic);
                        /* FALLTHROUGH */
                case IEEE80211_S_AUTH:
@@ -1008,6 +1013,8 @@ justcleanup:
                        }
                        break;
                case IEEE80211_S_RUN:
+                       timeout_del(&ic->ic_bgscan_timeout);
+                       ic->ic_bgscan_fail = 0;
                        switch (mgt) {
                        case IEEE80211_FC0_SUBTYPE_AUTH:
                                IEEE80211_SEND_MGMT(ic, ni,
Index: net80211/ieee80211_var.h
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_var.h,v
retrieving revision 1.81
diff -u -p -r1.81 ieee80211_var.h
--- net80211/ieee80211_var.h    6 Nov 2017 11:34:29 -0000       1.81
+++ net80211/ieee80211_var.h    29 Nov 2017 18:06:56 -0000
@@ -57,6 +57,13 @@
 #define        IEEE80211_TXPOWER_MAX   100     /* max power */
 #define        IEEE80211_TXPOWER_MIN   -50     /* kill radio (if possible) */
 
+#define IEEE80211_RSSI_THRES_2GHZ              (-40)   /* in dBm */
+#define IEEE80211_RSSI_THRES_5GHZ              (-50)   /* in dBm */
+#define IEEE80211_RSSI_THRES_RATIO_2GHZ                60      /* in percent */
+#define IEEE80211_RSSI_THRES_RATIO_5GHZ                50      /* in percent */
+
+#define IEEE80211_BGSCAN_FAIL_MAX              360     /* units of 500 msec */
+
 enum ieee80211_phytype {
        IEEE80211_T_DS,                 /* direct sequence spread spectrum */
        IEEE80211_T_OFDM,               /* frequency division multiplexing */
@@ -220,6 +227,9 @@ struct ieee80211com {
                                    struct ieee80211_node *, u_int8_t);
        void                    (*ic_update_htprot)(struct ieee80211com *,
                                        struct ieee80211_node *);
+       int                     (*ic_bgscan_start)(struct ieee80211com *);
+       struct timeout          ic_bgscan_timeout;
+       uint32_t                ic_bgscan_fail;
        u_int8_t                ic_myaddr[IEEE80211_ADDR_LEN];
        struct ieee80211_rateset ic_sup_rates[IEEE80211_MODE_MAX];
        struct ieee80211_channel ic_channels[IEEE80211_CHAN_MAX+1];
@@ -231,6 +241,7 @@ struct ieee80211com {
        u_int                   ic_scan_lock;   /* user-initiated scan */
        u_int8_t                ic_scan_count;  /* count scans */
        u_int32_t               ic_flags;       /* state flags */
+       u_int32_t               ic_xflags;      /* more flags */
        u_int32_t               ic_caps;        /* capabilities */
        u_int16_t               ic_modecaps;    /* set of mode capabilities */
        u_int16_t               ic_curmode;     /* current mode */
@@ -256,6 +267,8 @@ struct ieee80211com {
                                        const struct ieee80211_node *);
        u_int8_t                (*ic_node_getrssi)(struct ieee80211com *,
                                        const struct ieee80211_node *);
+       int                     (*ic_node_checkrssi)(struct ieee80211com *,
+                                       const struct ieee80211_node *);
        u_int8_t                ic_max_rssi;
        struct ieee80211_tree   ic_tree;
        int                     ic_nnodes;      /* length of ic_nnodes */
@@ -352,7 +365,11 @@ extern struct ieee80211com_head ieee8021
 #define IEEE80211_F_MFPR       0x01000000      /* CONF: MFP required */
 #define        IEEE80211_F_HTON        0x02000000      /* CONF: HT enabled */
 #define        IEEE80211_F_PBAR        0x04000000      /* CONF: PBAC required 
*/
+#define        IEEE80211_F_BGSCAN      0x08000000      /* STATUS: background 
scan */
 #define IEEE80211_F_USERMASK   0xf0000000      /* CONF: ioctl flag mask */
+
+/* ic_xflags */
+#define        IEEE80211_F_TX_MGMT_ONLY 0x00000001     /* leave data frames on 
ifq */
 
 /* ic_caps */
 #define        IEEE80211_C_WEP         0x00000001      /* CAPABILITY: WEP 
available */

Reply via email to