From 06e069b51f3238a02b320da0261e16e0fcc15178 Mon Sep 17 00:00:00 2001 From: stsp Date: Fri, 8 Dec 2017 21:16:01 +0000 Subject: [PATCH] Add support for background scanning to net80211 and iwm(4). 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 | 146 +++++++++++++++--- sys/dev/pci/if_iwmvar.h | 3 +- sys/net80211/ieee80211.c | 30 +++- sys/net80211/ieee80211_input.c | 39 +++-- sys/net80211/ieee80211_node.c | 260 +++++++++++++++++++++++++++------ sys/net80211/ieee80211_node.h | 8 +- sys/net80211/ieee80211_proto.c | 9 +- sys/net80211/ieee80211_var.h | 19 ++- 8 files changed, 434 insertions(+), 80 deletions(-) diff --git a/sys/dev/pci/if_iwm.c b/sys/dev/pci/if_iwm.c index 47905c03df7..ead82d7da64 100644 --- a/sys/dev/pci/if_iwm.c +++ b/sys/dev/pci/if_iwm.c @@ -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 @@ -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; diff --git a/sys/dev/pci/if_iwmvar.h b/sys/dev/pci/if_iwmvar.h index 3896d8dee93..7e35820c7ce 100644 --- a/sys/dev/pci/if_iwmvar.h +++ b/sys/dev/pci/if_iwmvar.h @@ -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 @@ -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; diff --git a/sys/net80211/ieee80211.c b/sys/net80211/ieee80211.c index ff788315911..9e92fbbae19 100644 --- a/sys/net80211/ieee80211.c +++ b/sys/net80211/ieee80211.c @@ -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 diff --git a/sys/net80211/ieee80211_input.c b/sys/net80211/ieee80211_input.c index e7add1e4a75..be869244e40 100644 --- a/sys/net80211/ieee80211_input.c +++ b/sys/net80211/ieee80211_input.c @@ -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: diff --git a/sys/net80211/ieee80211_node.c b/sys/net80211/ieee80211_node.c index aac5308668f..c0675b17958 100644 --- a/sys/net80211/ieee80211_node.c +++ b/sys/net80211/ieee80211_node.c @@ -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); } diff --git a/sys/net80211/ieee80211_node.h b/sys/net80211/ieee80211_node.h index fc088b3324f..2a5b0f1f0dc 100644 --- a/sys/net80211/ieee80211_node.h +++ b/sys/net80211/ieee80211_node.h @@ -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); diff --git a/sys/net80211/ieee80211_proto.c b/sys/net80211/ieee80211_proto.c index c0978acfc31..8f6d804ae77 100644 --- a/sys/net80211/ieee80211_proto.c +++ b/sys/net80211/ieee80211_proto.c @@ -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, diff --git a/sys/net80211/ieee80211_var.h b/sys/net80211/ieee80211_var.h index 593860863c1..51c3c56cbf0 100644 --- a/sys/net80211/ieee80211_var.h +++ b/sys/net80211/ieee80211_var.h @@ -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 $ */ /*- @@ -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 (-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 */ -- 2.20.1