wpa_supplicant: Implement time-based blacklisting

wpa_supplicant keeps a blacklist of BSSs in order to prevent repeated
associations to problematic APs*. Currently, this blacklist is
completely cleared whenever we successfully connect to any AP. This
causes problematic behavior when in the presence of both a bad AP and
a good AP. The device can repeatedly attempt to roam to the bad AP
because it is clearing the blacklist every time it connects to the good
AP. This results in the connection constantly ping-ponging between the
APs, leaving the user stuck without connection.

Instead of clearing the blacklist, implement timeout functionality which
allows association attempts to blacklisted APs after some time has
passed. Each time a BSS would be added to the blacklist, increase the
duration of this timeout exponentially, up to a cap of 1800 seconds.
This means that the device will no longer be able to immediately attempt
to roam back to a bad AP whenever it successfully connects to any other
AP.

Other details:
The algorithm for building up the blacklist count and timeout duration
on a given AP has been designed to be minimally obtrusive. Starting with
a fresh blacklist, the device may attempt to connect to a problematic AP
no more than 6 times in any ~45 minute period. Once an AP has reached a
blacklist count >= 6, the device may attempt to connect to it no more
than once every 30 minutes. The goal of these limits is to find an
ideal balance between minimizing connection attempts to bad APs while
still trying them out occasionally to see if the problems have stopped.

The only exception to the above limits is that the blacklist is still
completely cleared whenever there are no APs available in a scan. This
means that if all nearby APs have been blacklisted, all APs will be
completely exonerated regardless of their blacklist counts or how close
their blacklist entries are to expiring. When all nearby APs have been
blacklisted we know that every nearby AP is in some way problematic.
Once we know that every AP is causing problems, it doesn't really make
sense to sort them beyond that because the blacklist count and timeout
duration don't necessarily reflect the degree to which an AP is
problematic (i.e. they can be manipulated by external factors such as
the user physically moving around). Instead, its best to restart the
blacklist and let the normal roaming algorithm take over to maximize
our chance of getting the best possible connection quality.

As stated above, the time-based blacklisting algorithm is designed to
be minimally obtrusive to user experience, so occasionally restarting
the process is not too impactful on the user.

*problematic AP: rejects new clients, frequently de-auths clients, very
poor connection quality, etc.

Signed-off-by: Kevin Lund <kglund@google.com>
Signed-off-by: Brian Norris <briannorris@chromium.org>
This commit is contained in:
Kevin Lund 2020-06-11 14:11:16 -07:00 committed by Jouni Malinen
parent 2fd35d9857
commit d530110028
4 changed files with 63 additions and 19 deletions

View file

@ -56,16 +56,29 @@ struct wpa_blacklist * wpa_blacklist_get(struct wpa_supplicant *wpa_s,
int wpa_blacklist_add(struct wpa_supplicant *wpa_s, const u8 *bssid) int wpa_blacklist_add(struct wpa_supplicant *wpa_s, const u8 *bssid)
{ {
struct wpa_blacklist *e; struct wpa_blacklist *e;
struct os_reltime now;
if (wpa_s == NULL || bssid == NULL) if (wpa_s == NULL || bssid == NULL)
return -1; return -1;
e = wpa_blacklist_get(wpa_s, bssid); e = wpa_blacklist_get(wpa_s, bssid);
os_get_reltime(&now);
if (e) { if (e) {
e->blacklist_start = now;
e->count++; e->count++;
wpa_printf(MSG_DEBUG, "BSSID " MACSTR " blacklist count " if (e->count > 5)
"incremented to %d", e->timeout_secs = 1800;
MAC2STR(bssid), e->count); else if (e->count == 5)
e->timeout_secs = 600;
else if (e->count == 4)
e->timeout_secs = 120;
else if (e->count == 3)
e->timeout_secs = 60;
else
e->timeout_secs = 10;
wpa_printf(MSG_INFO, "BSSID " MACSTR
" blacklist count incremented to %d, blacklisting for %d seconds",
MAC2STR(bssid), e->count, e->timeout_secs);
return e->count; return e->count;
} }
@ -74,10 +87,13 @@ int wpa_blacklist_add(struct wpa_supplicant *wpa_s, const u8 *bssid)
return -1; return -1;
os_memcpy(e->bssid, bssid, ETH_ALEN); os_memcpy(e->bssid, bssid, ETH_ALEN);
e->count = 1; e->count = 1;
e->timeout_secs = 10;
e->blacklist_start = now;
e->next = wpa_s->blacklist; e->next = wpa_s->blacklist;
wpa_s->blacklist = e; wpa_s->blacklist = e;
wpa_printf(MSG_DEBUG, "Added BSSID " MACSTR " into blacklist", wpa_printf(MSG_DEBUG, "Added BSSID " MACSTR
MAC2STR(bssid)); " into blacklist, blacklisting for %d seconds",
MAC2STR(bssid), e->timeout_secs);
return e->count; return e->count;
} }
@ -116,6 +132,27 @@ int wpa_blacklist_del(struct wpa_supplicant *wpa_s, const u8 *bssid)
} }
/**
* wpa_blacklist_is_blacklisted - Check the blacklist status of a BSS
* @wpa_s: Pointer to wpa_supplicant data
* @bssid: BSSID to be checked
* Returns: count if BSS is currently considered to be blacklisted, 0 otherwise
*/
int wpa_blacklist_is_blacklisted(struct wpa_supplicant *wpa_s, const u8 *bssid)
{
struct wpa_blacklist *e;
struct os_reltime now;
e = wpa_blacklist_get(wpa_s, bssid);
if (!e)
return 0;
os_get_reltime(&now);
if (os_reltime_expired(&now, &e->blacklist_start, e->timeout_secs))
return 0;
return e->count;
}
/** /**
* wpa_blacklist_clear - Clear the blacklist of all entries * wpa_blacklist_clear - Clear the blacklist of all entries
* @wpa_s: Pointer to wpa_supplicant data * @wpa_s: Pointer to wpa_supplicant data

View file

@ -13,12 +13,20 @@ struct wpa_blacklist {
struct wpa_blacklist *next; struct wpa_blacklist *next;
u8 bssid[ETH_ALEN]; u8 bssid[ETH_ALEN];
int count; int count;
/* Time of most recent blacklist event. */
struct os_reltime blacklist_start;
/*
* Number of seconds after blacklist_start that the entry will be
* considered blacklisted.
*/
int timeout_secs;
}; };
struct wpa_blacklist * wpa_blacklist_get(struct wpa_supplicant *wpa_s, struct wpa_blacklist * wpa_blacklist_get(struct wpa_supplicant *wpa_s,
const u8 *bssid); const u8 *bssid);
int wpa_blacklist_add(struct wpa_supplicant *wpa_s, const u8 *bssid); int wpa_blacklist_add(struct wpa_supplicant *wpa_s, const u8 *bssid);
int wpa_blacklist_del(struct wpa_supplicant *wpa_s, const u8 *bssid); int wpa_blacklist_del(struct wpa_supplicant *wpa_s, const u8 *bssid);
int wpa_blacklist_is_blacklisted(struct wpa_supplicant *wpa_s, const u8 *bssid);
void wpa_blacklist_clear(struct wpa_supplicant *wpa_s); void wpa_blacklist_clear(struct wpa_supplicant *wpa_s);
#endif /* BLACKLIST_H */ #endif /* BLACKLIST_H */

View file

@ -1085,7 +1085,7 @@ static int disabled_freq(struct wpa_supplicant *wpa_s, int freq)
static bool wpa_scan_res_ok(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, static bool wpa_scan_res_ok(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
const u8 *match_ssid, size_t match_ssid_len, const u8 *match_ssid, size_t match_ssid_len,
struct wpa_bss *bss, struct wpa_blacklist *e, struct wpa_bss *bss, int blacklist_count,
bool debug_print); bool debug_print);
@ -1099,7 +1099,7 @@ static bool sae_pk_acceptable_bss_with_pk(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss; struct wpa_bss *bss;
dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
struct wpa_blacklist *e; int count;
const u8 *ie; const u8 *ie;
u8 rsnxe_capa = 0; u8 rsnxe_capa = 0;
@ -1117,9 +1117,9 @@ static bool sae_pk_acceptable_bss_with_pk(struct wpa_supplicant *wpa_s,
if (bss->est_throughput < 2000) if (bss->est_throughput < 2000)
return false; return false;
e = wpa_blacklist_get(wpa_s, bss->bssid); count = wpa_blacklist_is_blacklisted(wpa_s, bss->bssid);
if (wpa_scan_res_ok(wpa_s, ssid, match_ssid, match_ssid_len, if (wpa_scan_res_ok(wpa_s, ssid, match_ssid, match_ssid_len,
bss, e, 0)) bss, count, 0))
return true; return true;
} }
@ -1130,7 +1130,7 @@ static bool sae_pk_acceptable_bss_with_pk(struct wpa_supplicant *wpa_s,
static bool wpa_scan_res_ok(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, static bool wpa_scan_res_ok(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
const u8 *match_ssid, size_t match_ssid_len, const u8 *match_ssid, size_t match_ssid_len,
struct wpa_bss *bss, struct wpa_blacklist *e, struct wpa_bss *bss, int blacklist_count,
bool debug_print) bool debug_print)
{ {
int res; int res;
@ -1178,7 +1178,7 @@ static bool wpa_scan_res_ok(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
} }
#ifdef CONFIG_WPS #ifdef CONFIG_WPS
if ((ssid->key_mgmt & WPA_KEY_MGMT_WPS) && e && e->count > 0) { if ((ssid->key_mgmt & WPA_KEY_MGMT_WPS) && blacklist_count) {
if (debug_print) if (debug_print)
wpa_dbg(wpa_s, MSG_DEBUG, wpa_dbg(wpa_s, MSG_DEBUG,
" skip - blacklisted (WPS)"); " skip - blacklisted (WPS)");
@ -1472,12 +1472,12 @@ struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s,
int only_first_ssid, int debug_print) int only_first_ssid, int debug_print)
{ {
u8 wpa_ie_len, rsn_ie_len; u8 wpa_ie_len, rsn_ie_len;
struct wpa_blacklist *e;
const u8 *ie; const u8 *ie;
struct wpa_ssid *ssid; struct wpa_ssid *ssid;
int osen; int osen;
const u8 *match_ssid; const u8 *match_ssid;
size_t match_ssid_len; size_t match_ssid_len;
int blacklist_count;
ie = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE); ie = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE);
wpa_ie_len = ie ? ie[1] : 0; wpa_ie_len = ie ? ie[1] : 0;
@ -1503,8 +1503,8 @@ struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s,
osen ? " osen=1" : ""); osen ? " osen=1" : "");
} }
e = wpa_blacklist_get(wpa_s, bss->bssid); blacklist_count = wpa_blacklist_is_blacklisted(wpa_s, bss->bssid);
if (e) { if (blacklist_count) {
int limit = 1; int limit = 1;
if (wpa_supplicant_enabled_networks(wpa_s) == 1) { if (wpa_supplicant_enabled_networks(wpa_s) == 1) {
/* /*
@ -1517,11 +1517,11 @@ struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s,
*/ */
limit = 0; limit = 0;
} }
if (e->count > limit) { if (blacklist_count > limit) {
if (debug_print) { if (debug_print) {
wpa_dbg(wpa_s, MSG_DEBUG, wpa_dbg(wpa_s, MSG_DEBUG,
" skip - blacklisted (count=%d limit=%d)", " skip - blacklisted (count=%d limit=%d)",
e->count, limit); blacklist_count, limit);
} }
return NULL; return NULL;
} }
@ -1557,7 +1557,7 @@ struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s,
for (ssid = group; ssid; ssid = only_first_ssid ? NULL : ssid->pnext) { for (ssid = group; ssid; ssid = only_first_ssid ? NULL : ssid->pnext) {
if (wpa_scan_res_ok(wpa_s, ssid, match_ssid, match_ssid_len, if (wpa_scan_res_ok(wpa_s, ssid, match_ssid, match_ssid_len,
bss, e, debug_print)) bss, blacklist_count, debug_print))
return ssid; return ssid;
} }

View file

@ -984,7 +984,6 @@ void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s,
fils_hlp_sent ? " FILS_HLP_SENT" : ""); fils_hlp_sent ? " FILS_HLP_SENT" : "");
#endif /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */ #endif /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */
wpas_clear_temp_disabled(wpa_s, ssid, 1); wpas_clear_temp_disabled(wpa_s, ssid, 1);
wpa_blacklist_clear(wpa_s);
wpa_s->consecutive_conn_failures = 0; wpa_s->consecutive_conn_failures = 0;
wpa_s->new_connection = 0; wpa_s->new_connection = 0;
wpa_drv_set_operstate(wpa_s, 1); wpa_drv_set_operstate(wpa_s, 1);
@ -7324,7 +7323,7 @@ static int * get_bss_freqs_in_ess(struct wpa_supplicant *wpa_s)
continue; continue;
if (bss->ssid_len == cbss->ssid_len && if (bss->ssid_len == cbss->ssid_len &&
os_memcmp(bss->ssid, cbss->ssid, bss->ssid_len) == 0 && os_memcmp(bss->ssid, cbss->ssid, bss->ssid_len) == 0 &&
wpa_blacklist_get(wpa_s, bss->bssid) == NULL) { !wpa_blacklist_is_blacklisted(wpa_s, bss->bssid)) {
add_freq(freqs, &num_freqs, bss->freq); add_freq(freqs, &num_freqs, bss->freq);
if (num_freqs == max_freqs) if (num_freqs == max_freqs)
break; break;