Some access points advertise BSS load information in beacons in order to help clients make informed roaming decisions.
BSS load information includes the number of associated stations, the channel utilization (this takes other networks on the same channel into account), and current admission capacity (interesting for clients which use QoS, which we do not). We currently ignore BSS load information and only use RSSI to tell APs apart. With this patch we store bss load information for APs during scans if available, and take it into account when chosing an access point. Unfortunately, I do not have a suitable AP available to test with. tcpdump can be used to check whether an access point supports this feature. Run this command while associated to the access point: tcpdump -n -i iwm0 -y IEEE802_11_RADIO -s 4096 -v If the AP reports BSS load information you should see something like this among the information printed by tcpdump: 64 stations, 100% utilization, admission capacity 0us/s It would help to get this patch tested in an environment where access points advertise BSS load information, with a roaming test between different access points. I wrote down information about how roaming can be tested here: https://marc.info/?l=openbsd-tech&m=163329420019842&w=2 Thanks! diff 8c2348c4b5d36dddcf1de4644b5bb2c5dd715299 187113184307352db702dff07a73873bbd1f087c blob - 888a39c43dd0bbe0629998a8bb1e24a66466c092 blob + 5bd87ce70a265cd354e29b105cfe48ead88b93a6 --- sys/net80211/ieee80211_input.c +++ sys/net80211/ieee80211_input.c @@ -1606,7 +1606,7 @@ ieee80211_recv_probe_resp(struct ieee80211com *ic, str const struct ieee80211_frame *wh; const u_int8_t *frm, *efrm; const u_int8_t *tstamp, *ssid, *rates, *xrates, *edcaie, *wmmie; - const u_int8_t *rsnie, *wpaie, *htcaps, *htop; + const u_int8_t *rsnie, *wpaie, *htcaps, *htop, *bssload; u_int16_t capinfo, bintval; u_int8_t chan, bchan, erp, dtim_count, dtim_period; int is_new; @@ -1647,7 +1647,7 @@ ieee80211_recv_probe_resp(struct ieee80211com *ic, str capinfo = LE_READ_2(frm); frm += 2; ssid = rates = xrates = edcaie = wmmie = rsnie = wpaie = NULL; - htcaps = htop = NULL; + htcaps = htop = bssload = NULL; bchan = ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan); chan = bchan; erp = 0; @@ -1712,6 +1712,13 @@ ieee80211_recv_probe_resp(struct ieee80211com *ic, str wmmie = frm; } break; + case IEEE80211_ELEMID_QBSS_LOAD: + if (frm[1] < 5) { + ic->ic_stats.is_rx_elem_toosmall++; + break; + } + bssload = frm; + break; } frm += 2 + frm[1]; } @@ -1975,6 +1982,18 @@ ieee80211_recv_probe_resp(struct ieee80211com *ic, str ni->ni_rsncaps = wpa.rsn_caps; } } + + if (bssload != NULL) { + ni->ni_stacnt = bssload[2] | (bssload[3] << 8); + ni->ni_chanutil = (bssload[4] * 100) / 255; + ni->ni_admcap = (bssload[5] | (bssload[6] << 8)) / 32; + ni->ni_flags |= IEEE80211_NODE_BSSLOAD; + } else { + ni->ni_stacnt = 0; + ni->ni_chanutil = 0; + ni->ni_admcap = 0; + ni->ni_flags &= ~IEEE80211_NODE_BSSLOAD; + } } if (ssid[1] != 0 && ni->ni_essid[0] == '\0') { blob - 2fe4984654e67cc638ea1f7b3e48cbeeae24ecca blob + f09c34dca9ffcfc1fca60c1db984c22d6f1c482d --- sys/net80211/ieee80211_node.c +++ sys/net80211/ieee80211_node.c @@ -74,6 +74,7 @@ void ieee80211_setup_node(struct ieee80211com *, struc const u_int8_t *); struct ieee80211_node *ieee80211_alloc_node_helper(struct ieee80211com *); void ieee80211_node_switch_bss(struct ieee80211com *, struct ieee80211_node *); +int ieee80211_node_prefer_bss(struct ieee80211_node *, struct ieee80211_node *); void ieee80211_node_addba_request(struct ieee80211_node *, int); void ieee80211_node_addba_request_ac_be_to(void *); void ieee80211_node_addba_request_ac_bk_to(void *); @@ -1286,6 +1287,27 @@ ieee80211_node_join_bss(struct ieee80211com *ic, struc } } +int +ieee80211_node_prefer_bss(struct ieee80211_node *selbs, + struct ieee80211_node *ni) +{ + if (selbs == NULL) + return 1; + + if ((ni->ni_flags & IEEE80211_NODE_BSSLOAD) && + (selbs->ni_flags & IEEE80211_NODE_BSSLOAD)) { + if (ni->ni_chanutil < selbs->ni_chanutil) + return 1; + if (ni->ni_stacnt < selbs->ni_stacnt) + return 1; + } + + if (ni->ni_rssi > selbs->ni_rssi) + return 1; + + return 0; +} + struct ieee80211_node * ieee80211_node_choose_bss(struct ieee80211com *ic, int bgscan, struct ieee80211_node **curbs) @@ -1317,12 +1339,12 @@ ieee80211_node_choose_bss(struct ieee80211com *ic, int if (ic->ic_caps & IEEE80211_C_SCANALLBAND) { if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan) && - (selbs2 == NULL || ni->ni_rssi > selbs2->ni_rssi)) + ieee80211_node_prefer_bss(selbs2, ni)) selbs2 = ni; else if (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan) && - (selbs5 == NULL || ni->ni_rssi > selbs5->ni_rssi)) + ieee80211_node_prefer_bss(selbs5, ni)) selbs5 = ni; - } else if (selbs == NULL || ni->ni_rssi > selbs->ni_rssi) + } else if (ieee80211_node_prefer_bss(selbs, ni)) selbs = ni; } @@ -1334,13 +1356,25 @@ ieee80211_node_choose_bss(struct ieee80211com *ic, int /* * Prefer a 5Ghz AP even if its RSSI is weaker than the best 2Ghz AP * (as long as it meets the minimum RSSI threshold) since the 5Ghz band - * is usually less saturated. + * is usually less saturated. If we found APs on both bands and BSS + * load information is available then use it to pick the least busy AP. */ if (selbs5 && selbs5->ni_rssi > min_5ghz_rssi) selbs = selbs5; - else if (selbs5 && selbs2) - selbs = (selbs5->ni_rssi >= selbs2->ni_rssi ? selbs5 : selbs2); - else if (selbs2) + else if (selbs5 && selbs2) { + if ((selbs5->ni_flags & IEEE80211_NODE_BSSLOAD) && + (selbs2->ni_flags & IEEE80211_NODE_BSSLOAD)) { + if (selbs5->ni_chanutil > selbs2->ni_chanutil) + selbs = selbs2; + else if (selbs5->ni_stacnt > selbs2->ni_stacnt) + selbs = selbs2; + else + selbs = selbs5; + } else { + selbs = (selbs5->ni_rssi >= selbs2->ni_rssi ? + selbs5 : selbs2); + } + } else if (selbs2) selbs = selbs2; else if (selbs5) selbs = selbs5; blob - d8fce4ca1f545d4f5d85fbf0ecd32e6959ad3003 blob + 14b5fbede6f1b9b2b6a1f048f5241a4ded9ec880 --- sys/net80211/ieee80211_node.h +++ sys/net80211/ieee80211_node.h @@ -287,6 +287,11 @@ struct ieee80211_node { struct ieee80211_channel *ni_chan; u_int8_t ni_erp; /* 11g only */ + /* bss load information (valid if IEEE80211_NODE_BSSLOAD flag is set) */ + uint16_t ni_stacnt; /* number of associated stations */ + uint8_t ni_chanutil; /* channel utilization */ + uint16_t ni_admcap; /* admission capacity */ + /* DTIM and contention free period (CFP) */ u_int8_t ni_dtimcount; u_int8_t ni_dtimperiod; @@ -406,6 +411,7 @@ struct ieee80211_node { #define IEEE80211_NODE_HT_SGI40 0x8000 /* SGI on 40 MHz negotiated */ #define IEEE80211_NODE_VHT 0x10000 /* VHT negotiated */ #define IEEE80211_NODE_HTCAP 0x20000 /* claims to support HT */ +#define IEEE80211_NODE_BSSLOAD 0x40000 /* reports BSS load info */ /* If not NULL, this function gets called when ni_refcnt hits zero. */ void (*ni_unref_cb)(struct ieee80211com *,
