From 7d35c1889bd67a4cf117c05efc99fb35e532b6dc Mon Sep 17 00:00:00 2001 From: stsp Date: Wed, 15 Aug 2018 18:45:43 +0000 Subject: [PATCH] Update AP selection heuristics for auto-join. We now prefer stronger crypto over weaker crypto over none, prefer 5 GHz band with reasonable RSSI, and use RSSI as a tie-breaker with a slight advantage for 5GHz. Candidate APs are now ranked by a score which is calculated based on these attributes. There is likely room for improvements to make these heuristics work well across many different environments, but it's a start. ok phessler@ --- sys/net80211/ieee80211_node.c | 254 ++++++++++++++++++++++++---------- 1 file changed, 178 insertions(+), 76 deletions(-) diff --git a/sys/net80211/ieee80211_node.c b/sys/net80211/ieee80211_node.c index bd64aea32b7..191fcfc877e 100644 --- a/sys/net80211/ieee80211_node.c +++ b/sys/net80211/ieee80211_node.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ieee80211_node.c,v 1.141 2018/08/13 15:19:52 stsp Exp $ */ +/* $OpenBSD: ieee80211_node.c,v 1.142 2018/08/15 18:45:43 stsp Exp $ */ /* $NetBSD: ieee80211_node.c,v 1.14 2004/05/09 09:18:47 dyoung Exp $ */ /*- @@ -191,7 +191,6 @@ ieee80211_del_ess(struct ieee80211com *ic, char *nwid, int all) return; } } - } int @@ -296,118 +295,221 @@ ieee80211_add_ess(struct ieee80211com *ic, int wpa, int wep) } int -ieee80211_ess_is_better(struct ieee80211com *ic, struct ieee80211_node *nicur, - struct ieee80211_node *selni) +ieee80211_ess_cmp_crypto(struct ieee80211_node *nicur, + struct ieee80211_node *nican) { - uint8_t min_5ghz_rssi; + if ((nican->ni_rsnprotos & IEEE80211_PROTO_RSN) && + (nicur->ni_rsnprotos & IEEE80211_PROTO_RSN) == 0) + return 1; + if ((nican->ni_rsnprotos & IEEE80211_PROTO_RSN) == 0 && + (nicur->ni_rsnprotos & IEEE80211_PROTO_RSN)) + return -1; + + if ((nican->ni_rsnprotos & IEEE80211_PROTO_WPA) && + (nicur->ni_rsnprotos & IEEE80211_PROTO_WPA) == 0) + return 1; + if ((nican->ni_rsnprotos & IEEE80211_PROTO_WPA) == 0 && + (nicur->ni_rsnprotos & IEEE80211_PROTO_WPA)) + return -1; - /* anything is better than nothing */ - if (selni == NULL) + if ((nican->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) && + (nicur->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) return 1; + if ((nican->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0 && + (nicur->ni_capinfo & IEEE80211_CAPINFO_PRIVACY)) + return -1; + + return 0; +} + +int +ieee80211_ess_cmp_band(struct ieee80211com *ic, + struct ieee80211_node *nicur, struct ieee80211_node *nican) +{ + uint8_t min_5ghz_rssi; if (ic->ic_max_rssi) min_5ghz_rssi = IEEE80211_RSSI_THRES_RATIO_5GHZ; else min_5ghz_rssi = (uint8_t)IEEE80211_RSSI_THRES_5GHZ; - /* First 5GHz with acceptable signal */ - if ((IEEE80211_IS_CHAN_5GHZ(nicur->ni_chan) && - !IEEE80211_IS_CHAN_5GHZ(selni->ni_chan)) && - nicur->ni_rssi > min_5ghz_rssi) + if (IEEE80211_IS_CHAN_5GHZ(nican->ni_chan) && + IEEE80211_IS_CHAN_2GHZ(nicur->ni_chan) && + nican->ni_rssi > min_5ghz_rssi) return 1; - /* Prefer 5GHz N over not-N */ - if ((IEEE80211_IS_CHAN_5GHZ(nicur->ni_chan) && - nicur->ni_rssi > min_5ghz_rssi) && - ieee80211_node_supports_ht(nicur)) - return 1; + if (IEEE80211_IS_CHAN_5GHZ(nicur->ni_chan) && + IEEE80211_IS_CHAN_5GHZ(nican->ni_chan) && + nicur->ni_rssi > min_5ghz_rssi) + return -1; - /* Settle for just N */ - if (ieee80211_node_supports_ht(nicur) && - !ieee80211_node_supports_ht(selni)) - return 1; + return 0; +} + +uint8_t +ieee80211_ess_adjust_rssi(struct ieee80211com *ic, struct ieee80211_node *ni) +{ + uint8_t rssi = ni->ni_rssi; + + /* + * Slightly punish 2 GHz RSSI values since they are usually + * stronger than 5 GHz RSSI values. + */ + if (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) { + if (ic->ic_max_rssi) { + uint8_t p = (5 * ic->ic_max_rssi) / 100; + if (rssi >= p) + rssi -= p; /* punish by 5% */ + } else { + if (rssi >= 8) + rssi -= 8; /* punish by 8 dBm */ + } + } - /* Last resort of best rssi */ - if (nicur->ni_rssi > selni->ni_rssi) + return rssi; +} + +int +ieee80211_ess_cmp_rssi(struct ieee80211com *ic, + struct ieee80211_node *nicur, struct ieee80211_node *nican) +{ + uint8_t cur_rssi = ieee80211_ess_adjust_rssi(ic, nicur); + uint8_t can_rssi = ieee80211_ess_adjust_rssi(ic, nican); + + if (can_rssi > cur_rssi) return 1; + if (cur_rssi > can_rssi) + return -1; return 0; } +/* + * Given two APs, determine the "better" one of the two. + * We compute a score based on the following attributes: + * + * crypto: wpa2 > wpa1 > wep > open + * band: 5 GHz > 2 GHz provided 5 GHz rssi is above threshold + * rssi: rssi1 > rssi2 as a numeric comparison with a slight + * disadvantage for 2 GHz APs + * + * Crypto carries most weight, followed by band, followed by rssi. + */ int -ieee80211_match_ess(struct ieee80211com *ic) +ieee80211_ess_is_better(struct ieee80211com *ic, + struct ieee80211_node *nicur, struct ieee80211_node *nican) +{ + int score_cur = 0, score_can = 0; + + switch (ieee80211_ess_cmp_crypto(nicur, nican)) { + case 1: + score_can += 4; + break; + case -1: + score_cur += 4; + break; + default: + break; + } + + switch (ieee80211_ess_cmp_band(ic, nicur, nican)) { + case 1: + score_can += 2; + break; + case -1: + score_cur += 2; + break; + default: + break; + } + + switch (ieee80211_ess_cmp_rssi(ic, nicur, nican)) { + case 1: + score_can += 1; + break; + case -1: + score_cur += 1; + break; + default: + break; + } + + return score_can > score_cur; +} + +/* Determine whether a candidate AP belongs to a given ESS. */ +int +ieee80211_match_ess(struct ieee80211_ess *ess, struct ieee80211_node *ni) +{ + if (ess->esslen != ni->ni_esslen) + return 0; + if (memcmp(ess->essid, ni->ni_essid, ess->esslen) != 0) + return 0; + + if (ess->flags & (IEEE80211_F_PSK | IEEE80211_F_RSNON)) { + /* Ensure same WPA version. */ + if ((ni->ni_rsnprotos & IEEE80211_PROTO_RSN) && + (ess->rsnprotos & IEEE80211_PROTO_RSN) == 0) + return 0; + if ((ni->ni_rsnprotos & IEEE80211_PROTO_WPA) && + (ess->rsnprotos & IEEE80211_PROTO_WPA) == 0) + return 0; + } else if (ess->flags & IEEE80211_F_WEPON) { + if ((ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) == 0) + return 0; + } + + return 1; +} + +void +ieee80211_switch_ess(struct ieee80211com *ic) { struct ifnet *ifp = &ic->ic_if; struct ieee80211_ess *ess, *seless = NULL; struct ieee80211_node *ni, *selni = NULL; if (!ISSET(ifp->if_flags, IFF_RUNNING)) - return (0); + return; - /* - * Apple's iOS uses the algorithm described at - * https://support.apple.com/en-us/HT202831 - * - * basically: - * last network recently joined - * then strongest security - * then rssi level - */ + /* Find the best AP matching an entry on our ESS join list. */ + RBT_FOREACH(ni, ieee80211_tree, &ic->ic_tree) { + if ((ic->ic_flags & IEEE80211_F_DESBSSID) && + !IEEE80211_ADDR_EQ(ic->ic_des_bssid, ni->ni_bssid)) + continue; - /* skip if it's the same network */ - TAILQ_FOREACH(ess, &ic->ic_ess, ess_next) { - if (LINK_STATE_IS_UP(ifp->if_link_state) && - ess->esslen == ic->ic_des_esslen && - (memcmp(ic->ic_des_essid, ess->essid, - IEEE80211_NWID_LEN) == 0)) { - if (ifp->if_flags & IFF_DEBUG) { - printf(" %s: staying on ", - ifp->if_xname); - ieee80211_print_essid(ess->essid, ess->esslen); - printf("\n"); - } - return (0); + TAILQ_FOREACH(ess, &ic->ic_ess, ess_next) { + if (ieee80211_match_ess(ess, ni)) + break; } - } - - TAILQ_FOREACH(ess, &ic->ic_ess, ess_next) { - RBT_FOREACH(ni, ieee80211_tree, &ic->ic_tree) { - if (memcmp(ess->essid, ni->ni_essid, - IEEE80211_NWID_LEN) != 0 || - ni->ni_fails != 0) - continue; - - /* make sure encryptions match */ - if (ess->flags & - (IEEE80211_F_PSK | IEEE80211_F_RSNON)) { - if ((ni->ni_capinfo & - IEEE80211_CAPINFO_PRIVACY) == 0) - continue; - } else { - if (ni->ni_capinfo & IEEE80211_CAPINFO_PRIVACY) - continue; - } + if (ess == NULL) + continue; - if ((ic->ic_flags & IEEE80211_F_DESBSSID) && - !IEEE80211_ADDR_EQ(ic->ic_des_bssid, ni->ni_bssid)) - continue; + if (selni == NULL) { + seless = ess; + selni = ni; + continue; + } - if (ieee80211_ess_is_better(ic, ni, selni) > 0) { - seless = ess; - selni = ni; - } + if (ieee80211_ess_is_better(ic, selni, ni)) { + seless = ess; + selni = ni; } } if (seless && !(seless->esslen == ic->ic_des_esslen && (memcmp(ic->ic_des_essid, seless->essid, IEEE80211_NWID_LEN) == 0))) { + if (ifp->if_flags & IFF_DEBUG) { + printf("%s: switching to network ", ifp->if_xname); + ieee80211_print_essid(seless->essid, seless->esslen); + printf("\n"); + + } ieee80211_set_ess(ic, seless->essid); - return (1); } - - return (0); } + void ieee80211_set_ess(struct ieee80211com *ic, char *nwid) { @@ -1152,7 +1254,7 @@ ieee80211_end_scan(struct ifnet *ifp) /* Possibly switch which ssid we are associated with */ if (!bgscan && ic->ic_opmode == IEEE80211_M_STA) - ieee80211_match_ess(ic); + ieee80211_switch_ess(ic); selbs = ieee80211_node_choose_bss(ic, bgscan, &curbs); if (bgscan) { -- 2.20.1