Add support for background scanning to net80211 and iwm(4).
authorstsp <stsp@openbsd.org>
Fri, 8 Dec 2017 21:16:01 +0000 (21:16 +0000)
committerstsp <stsp@openbsd.org>
Fri, 8 Dec 2017 21:16:01 +0000 (21:16 +0000)
The iwm(4) driver will now roam between access points which share an SSID.
Use 'ifconfig iwm0 debug' and 'tail -f /var/log/messages' to watch it do so.

Tested by several people in various iterations.
As usual, let me know if you run into issues.

ok phessler deraadt

sys/dev/pci/if_iwm.c
sys/dev/pci/if_iwmvar.h
sys/net80211/ieee80211.c
sys/net80211/ieee80211_input.c
sys/net80211/ieee80211_node.c
sys/net80211/ieee80211_node.h
sys/net80211/ieee80211_proto.c
sys/net80211/ieee80211_var.h

index 47905c0..ead82d7 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwm.c,v 1.220 2017/12/08 20:55:46 stsp Exp $       */
+/*     $OpenBSD: if_iwm.c,v 1.221 2017/12/08 21:16:01 stsp Exp $       */
 
 /*
  * Copyright (c) 2014, 2016 genua gmbh <info@genua.de>
@@ -420,9 +420,9 @@ uint32_t iwm_scan_rate_n_flags(struct iwm_softc *, int, int);
 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,10 @@ int        iwm_update_quotas(struct iwm_softc *, struct iwm_node *, int);
 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_umac_scan_abort(struct iwm_softc *);
+int    iwm_lmac_scan_abort(struct iwm_softc *);
+int    iwm_scan_abort(struct iwm_softc *);
 int    iwm_auth(struct iwm_softc *);
 int    iwm_deauth(struct iwm_softc *);
 int    iwm_assoc(struct iwm_softc *);
@@ -4879,7 +4883,7 @@ iwm_fill_probe_req(struct iwm_softc *sc, struct iwm_scan_probe_req *preq)
 }
 
 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 = {
@@ -4890,28 +4894,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);
@@ -5059,7 +5069,7 @@ iwm_config_umac_scan(struct iwm_softc *sc)
 }
 
 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 = {
@@ -5071,7 +5081,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) *
@@ -5079,20 +5089,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);
@@ -5472,13 +5489,19 @@ iwm_scan(struct iwm_softc *sc)
        struct ieee80211com *ic = &sc->sc_ic;
        int err;
 
-       if (sc->sc_flags & IWM_FLAG_SCANNING)
-               return 0;
+       if (sc->sc_flags & IWM_FLAG_BGSCAN) {
+               err = iwm_scan_abort(sc);
+               if (err) {
+                       printf("%s: could not abort background scan\n",
+                           DEVNAME(sc));
+                       return err;
+               }
+       }
 
        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;
@@ -5492,6 +5515,79 @@ iwm_scan(struct iwm_softc *sc)
        return 0;
 }
 
+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_BGSCAN;
+       return 0;
+}
+
+int
+iwm_umac_scan_abort(struct iwm_softc *sc)
+{
+       struct iwm_umac_scan_abort cmd = { 0 };
+
+       return iwm_send_cmd_pdu(sc,
+           IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_ABORT_UMAC),
+           0, sizeof(cmd), &cmd);
+}
+
+int
+iwm_lmac_scan_abort(struct iwm_softc *sc)
+{
+       struct iwm_host_cmd cmd = {
+               .id = IWM_SCAN_OFFLOAD_ABORT_CMD,
+       };
+       int err, status;
+
+       err = iwm_send_cmd_status(sc, &cmd, &status);
+       if (err)
+               return err;
+
+       if (status != IWM_CAN_ABORT_STATUS) {
+               /*
+                * The scan abort will return 1 for success or
+                * 2 for "failure".  A failure condition can be
+                * due to simply not being in an active scan which
+                * can occur if we send the scan abort before the
+                * microcode has notified us that a scan is completed.
+                */
+               return EBUSY;
+       }
+
+       return 0;
+}
+
+int
+iwm_scan_abort(struct iwm_softc *sc)
+{
+       int err;
+
+       if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN))
+               err = iwm_umac_scan_abort(sc);
+       else
+               err = iwm_lmac_scan_abort(sc);
+
+       if (err == 0)
+               sc->sc_flags &= ~(IWM_FLAG_SCANNING | IWM_FLAG_BGSCAN);
+       return err;
+}
+
 int
 iwm_auth(struct iwm_softc *sc)
 {
@@ -6116,10 +6212,10 @@ iwm_endscan(struct iwm_softc *sc)
 {
        struct ieee80211com *ic = &sc->sc_ic;
 
-       if ((sc->sc_flags & IWM_FLAG_SCANNING) == 0)
+       if ((sc->sc_flags & (IWM_FLAG_SCANNING | IWM_FLAG_BGSCAN)) == 0)
                return;
 
-       sc->sc_flags &= ~IWM_FLAG_SCANNING;
+       sc->sc_flags &= ~(IWM_FLAG_SCANNING | IWM_FLAG_BGSCAN);
        ieee80211_end_scan(&ic->ic_if);
 }
 
@@ -6550,9 +6646,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)
@@ -6632,7 +6729,7 @@ iwm_stop(struct ifnet *ifp)
        if (ic->ic_state == IEEE80211_S_RUN)
                ieee80211_mira_cancel_timeouts(&in->in_mn); /* XXX refcount? */
 
-       sc->sc_flags &= ~IWM_FLAG_SCANNING;
+       sc->sc_flags &= ~(IWM_FLAG_SCANNING | IWM_FLAG_BGSCAN);
        sc->sc_flags &= ~IWM_FLAG_MAC_ACTIVE;
        sc->sc_flags &= ~IWM_FLAG_BINDING_ACTIVE;
        sc->sc_flags &= ~IWM_FLAG_STA_ACTIVE;
@@ -7132,7 +7229,9 @@ iwm_notif_intr(struct iwm_softc *sc)
                case IWM_BINDING_CONTEXT_CMD:
                case IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_CFG_CMD):
                case IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_REQ_UMAC):
+               case IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_ABORT_UMAC):
                case IWM_SCAN_OFFLOAD_REQUEST_CMD:
+               case IWM_SCAN_OFFLOAD_ABORT_CMD:
                case IWM_REPLY_BEACON_FILTERING_CMD:
                case IWM_MAC_PM_POWER_TABLE:
                case IWM_TIME_QUOTA_CMD:
@@ -7798,6 +7897,7 @@ iwm_attach(struct device *parent, struct device *self, void *aux)
        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 3896d8d..7e35820 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwmvar.h,v 1.36 2017/10/22 09:51:58 stsp Exp $     */
+/*     $OpenBSD: if_iwmvar.h,v 1.37 2017/12/08 21:16:01 stsp Exp $     */
 
 /*
  * Copyright (c) 2014 genua mbh <info@genua.de>
@@ -288,6 +288,7 @@ struct iwm_rx_ring {
 #define IWM_FLAG_TE_ACTIVE     0x40    /* time event is scheduled */
 #define IWM_FLAG_HW_ERR                0x80    /* hardware error occurred */
 #define IWM_FLAG_SHUTDOWN      0x100   /* shutting down; new tasks forbidden */
+#define IWM_FLAG_BGSCAN                0x200   /* background scan in progress */
 
 struct iwm_ucode_status {
        uint32_t uc_error_event_table;
index ff78831..9e92fbb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211.c,v 1.63 2017/09/05 12:02:21 stsp Exp $     */
+/*     $OpenBSD: ieee80211.c,v 1.64 2017/12/08 21:16:01 stsp Exp $     */
 /*     $NetBSD: ieee80211.c,v 1.19 2004/06/06 05:45:29 dyoung Exp $    */
 
 /*-
@@ -71,6 +71,32 @@ struct ieee80211com_head ieee80211com_head =
 void ieee80211_setbasicrates(struct ieee80211com *);
 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) ||
+           ic->ic_state != IEEE80211_S_RUN)
+               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)
 {
@@ -158,6 +184,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 e7add1e..be86924 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_input.c,v 1.196 2017/09/04 09:11:46 stsp Exp $      */
+/*     $OpenBSD: ieee80211_input.c,v 1.197 2017/12/08 21:16:01 stsp Exp $      */
 
 /*-
  * Copyright (c) 2001 Atsushi Onoe
@@ -267,6 +267,14 @@ ieee80211_input(struct ifnet *ifp, struct mbuf *m, struct ieee80211_node *ni,
                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
@@ -1504,7 +1512,8 @@ ieee80211_recv_probe_resp(struct ieee80211com *ic, struct mbuf *m,
 
 #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 +1598,8 @@ ieee80211_recv_probe_resp(struct ieee80211com *ic, struct mbuf *m,
                 * 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 +1616,8 @@ ieee80211_recv_probe_resp(struct ieee80211com *ic, struct mbuf *m,
                        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 +2372,13 @@ ieee80211_recv_deauth(struct ieee80211com *ic, struct mbuf *m,
 
        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 +2422,13 @@ ieee80211_recv_disassoc(struct ieee80211com *ic, struct mbuf *m,
 
        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 aac5308..c0675b1 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_node.c,v 1.121 2017/09/05 14:56:59 stsp Exp $       */
+/*     $OpenBSD: ieee80211_node.c,v 1.122 2017/12/08 21:16:01 stsp Exp $       */
 /*     $NetBSD: ieee80211_node.c,v 1.14 2004/05/09 09:18:47 dyoung Exp $       */
 
 /*-
@@ -65,12 +65,16 @@ void ieee80211_node_copy(struct ieee80211com *, struct ieee80211_node *,
 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 *ic, struct ieee80211_node *ni)
        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);
+
+               timeout_del(&ic->ic_bgscan_timeout);
+               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 (bgscan && ieee80211_node_cmp(ic->ic_bss, ni) == 0)
+                       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 ieee80211com *ic, struct ieee80211_node *ni)
                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 ieee80211com *ic,
        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 ieee80211com *ic, struct ieee80211_node *ni)
        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 fc088b3..2a5b0f1 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_node.h,v 1.69 2017/08/17 06:01:05 stsp Exp $        */
+/*     $OpenBSD: ieee80211_node.h,v 1.70 2017/12/08 21:16:01 stsp Exp $        */
 /*     $NetBSD: ieee80211_node.h,v 1.9 2004/04/30 22:57:32 dyoung Exp $        */
 
 /*-
@@ -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 c0978ac..8f6d804 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_proto.c,v 1.80 2017/08/18 17:30:12 stsp Exp $       */
+/*     $OpenBSD: ieee80211_proto.c,v 1.81 2017/12/08 21:16:01 stsp Exp $       */
 /*     $NetBSD: ieee80211_proto.c,v 1.8 2004/04/30 23:58:20 dyoung Exp $       */
 
 /*-
@@ -853,6 +853,7 @@ ieee80211_newstate(struct ieee80211com *ic, enum ieee80211_state nstate,
        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 5938608..51c3c56 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ieee80211_var.h,v 1.81 2017/11/06 11:34:29 phessler Exp $     */
+/*     $OpenBSD: ieee80211_var.h,v 1.82 2017/12/08 21:16:01 stsp Exp $ */
 /*     $NetBSD: ieee80211_var.h,v 1.7 2004/05/06 03:07:10 dyoung Exp $ */
 
 /*-
 #define        IEEE80211_TXPOWER_MAX   100     /* max power */
 #define        IEEE80211_TXPOWER_MIN   -50     /* kill radio (if possible) */
 
+#define IEEE80211_RSSI_THRES_2GHZ              (-60)   /* in dBm */
+#define IEEE80211_RSSI_THRES_5GHZ              (-70)   /* 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,8 +365,12 @@ extern struct ieee80211com_head ieee80211com_head;
 #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 */
 #define        IEEE80211_C_IBSS        0x00000002      /* CAPABILITY: IBSS available */