-/* $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 $ */
/*-
return;
}
}
-
}
int
}
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)
{
/* 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) {