diff --git a/wpa_supplicant/README-HS20 b/wpa_supplicant/README-HS20 index ec576b921..57abdbf75 100644 --- a/wpa_supplicant/README-HS20 +++ b/wpa_supplicant/README-HS20 @@ -227,6 +227,16 @@ Credentials can be pre-configured for automatic network selection: # This optional field can be used to keep track of the SP that provisioned # the credential to find the PPS MO (./Wi-Fi/). # +# Minimum backhaul threshold (PPS//Policy/MinBackhauldThreshold/*) +# These fields can be used to specify minimum download/upload backhaul +# bandwidth that is preferred for the credential. This constraint is +# ignored if the AP does not advertise WAN Metrics information or if the +# limit would prevent any connection. Values are in kilobits per second. +# min_dl_bandwidth_home +# min_ul_bandwidth_home +# min_dl_bandwidth_roaming +# min_ul_bandwidth_roaming +# # for example: # #cred={ diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index b540b00cf..ab7a68071 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -2453,6 +2453,26 @@ int wpa_config_set_cred(struct wpa_cred *cred, const char *var, return 0; } + if (os_strcmp(var, "min_dl_bandwidth_home") == 0) { + cred->min_dl_bandwidth_home = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_ul_bandwidth_home") == 0) { + cred->min_ul_bandwidth_home = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_dl_bandwidth_roaming") == 0) { + cred->min_dl_bandwidth_roaming = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_ul_bandwidth_roaming") == 0) { + cred->min_ul_bandwidth_roaming = atoi(value); + return 0; + } + val = wpa_config_parse_string(value, &len); if (val == NULL) { wpa_printf(MSG_ERROR, "Line %d: invalid field '%s' string " diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h index 6c9b75380..b37a97495 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -251,6 +251,11 @@ struct wpa_cred { * provisioning_sp - FQDN of the SP that provisioned the credential */ char *provisioning_sp; + + unsigned int min_dl_bandwidth_home; + unsigned int min_ul_bandwidth_home; + unsigned int min_dl_bandwidth_roaming; + unsigned int min_ul_bandwidth_roaming; }; diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index d8bd3c959..e50c86abc 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -809,6 +809,19 @@ static void wpa_config_write_cred(FILE *f, struct wpa_cred *cred) if (cred->provisioning_sp) fprintf(f, "\tprovisioning_sp=%s\n", cred->provisioning_sp); + + if (cred->min_dl_bandwidth_home) + fprintf(f, "\tmin_dl_bandwidth_home=%u\n", + cred->min_dl_bandwidth_home); + if (cred->min_ul_bandwidth_home) + fprintf(f, "\tmin_ul_bandwidth_home=%u\n", + cred->min_ul_bandwidth_home); + if (cred->min_dl_bandwidth_roaming) + fprintf(f, "\tmin_dl_bandwidth_roaming=%u\n", + cred->min_dl_bandwidth_roaming); + if (cred->min_ul_bandwidth_roaming) + fprintf(f, "\tmin_ul_bandwidth_roaming=%u\n", + cred->min_ul_bandwidth_roaming); } diff --git a/wpa_supplicant/interworking.c b/wpa_supplicant/interworking.c index 3bc6fae04..7f42e7717 100644 --- a/wpa_supplicant/interworking.c +++ b/wpa_supplicant/interworking.c @@ -46,9 +46,9 @@ static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s); static struct wpa_cred * interworking_credentials_available_realm( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss); + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw); static struct wpa_cred * interworking_credentials_available_3gpp( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss); + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw); static void interworking_reconnect(struct wpa_supplicant *wpa_s) @@ -166,6 +166,23 @@ static int cred_with_domain(struct wpa_supplicant *wpa_s) } +#ifdef CONFIG_HS20 +static int cred_with_min_backhaul(struct wpa_supplicant *wpa_s) +{ + struct wpa_cred *cred; + + for (cred = wpa_s->conf->cred; cred; cred = cred->next) { + if (cred->min_dl_bandwidth_home || + cred->min_ul_bandwidth_home || + cred->min_dl_bandwidth_roaming || + cred->min_ul_bandwidth_roaming) + return 1; + } + return 0; +} +#endif /* CONFIG_HS20 */ + + static int additional_roaming_consortiums(struct wpa_bss *bss) { const u8 *ie; @@ -231,14 +248,17 @@ static int interworking_anqp_send_req(struct wpa_supplicant *wpa_s, wpabuf_put_u8(extra, HS20_STYPE_QUERY_LIST); wpabuf_put_u8(extra, 0); /* Reserved */ wpabuf_put_u8(extra, HS20_STYPE_CAPABILITY_LIST); - if (all) { + if (all) wpabuf_put_u8(extra, HS20_STYPE_OPERATOR_FRIENDLY_NAME); + if (all || cred_with_min_backhaul(wpa_s)) wpabuf_put_u8(extra, HS20_STYPE_WAN_METRICS); + if (all) wpabuf_put_u8(extra, HS20_STYPE_CONNECTION_CAPABILITY); + if (all) wpabuf_put_u8(extra, HS20_STYPE_OPERATING_CLASS); + if (all) wpabuf_put_u8(extra, HS20_STYPE_OSU_PROVIDERS_LIST); - } gas_anqp_set_element_len(extra, len_pos); } #endif /* CONFIG_HS20 */ @@ -1052,8 +1072,67 @@ static int cred_excluded_ssid(struct wpa_cred *cred, struct wpa_bss *bss) } +static int cred_below_min_backhaul(struct wpa_supplicant *wpa_s, + struct wpa_cred *cred, struct wpa_bss *bss) +{ + int res; + unsigned int dl_bandwidth, ul_bandwidth; + const u8 *wan; + u8 wan_info, dl_load, ul_load; + u16 lmd; + u32 ul_speed, dl_speed; + + if (!cred->min_dl_bandwidth_home && + !cred->min_ul_bandwidth_home && + !cred->min_dl_bandwidth_roaming && + !cred->min_ul_bandwidth_roaming) + return 0; /* No bandwidth constraint specified */ + + if (bss->anqp == NULL || bss->anqp->hs20_wan_metrics == NULL) + return 0; /* No WAN Metrics known - ignore constraint */ + + wan = wpabuf_head(bss->anqp->hs20_wan_metrics); + wan_info = wan[0]; + if (wan_info & BIT(3)) + return 1; /* WAN link at capacity */ + lmd = WPA_GET_LE16(wan + 11); + if (lmd == 0) + return 0; /* Downlink/Uplink Load was not measured */ + dl_speed = WPA_GET_LE32(wan + 1); + ul_speed = WPA_GET_LE32(wan + 5); + dl_load = wan[9]; + ul_load = wan[10]; + + if (dl_speed >= 0xffffff) + dl_bandwidth = dl_speed / 255 * (255 - dl_load); + else + dl_bandwidth = dl_speed * (255 - dl_load) / 255; + + if (ul_speed >= 0xffffff) + ul_bandwidth = ul_speed / 255 * (255 - ul_load); + else + ul_bandwidth = ul_speed * (255 - ul_load) / 255; + + res = interworking_home_sp_cred(wpa_s, cred, bss->anqp ? + bss->anqp->domain_name : NULL); + if (res > 0) { + if (cred->min_dl_bandwidth_home > dl_bandwidth) + return 1; + if (cred->min_ul_bandwidth_home > ul_bandwidth) + return 1; + } else { + if (cred->min_dl_bandwidth_roaming > dl_bandwidth) + return 1; + if (cred->min_ul_bandwidth_roaming > ul_bandwidth) + return 1; + } + + return 0; +} + + static struct wpa_cred * interworking_credentials_available_roaming_consortium( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw) { struct wpa_cred *cred, *selected = NULL; const u8 *ie; @@ -1083,6 +1162,8 @@ static struct wpa_cred * interworking_credentials_available_roaming_consortium( continue; if (cred_no_required_oi_match(cred, bss)) continue; + if (!ignore_bw && cred_below_min_backhaul(wpa_s, cred, bss)) + continue; if (selected == NULL || selected->priority < cred->priority) @@ -1289,26 +1370,52 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) } cred_rc = interworking_credentials_available_roaming_consortium(wpa_s, - bss); + bss, 0); if (cred_rc) { wpa_printf(MSG_DEBUG, "Interworking: Highest roaming " "consortium matching credential priority %d", cred_rc->priority); } - cred = interworking_credentials_available_realm(wpa_s, bss); + cred = interworking_credentials_available_realm(wpa_s, bss, 0); if (cred) { wpa_printf(MSG_DEBUG, "Interworking: Highest NAI Realm list " "matching credential priority %d", cred->priority); } - cred_3gpp = interworking_credentials_available_3gpp(wpa_s, bss); + cred_3gpp = interworking_credentials_available_3gpp(wpa_s, bss, 0); if (cred_3gpp) { wpa_printf(MSG_DEBUG, "Interworking: Highest 3GPP matching " "credential priority %d", cred_3gpp->priority); } + if (!cred_rc && !cred && !cred_3gpp) { + cred_rc = interworking_credentials_available_roaming_consortium( + wpa_s, bss, 1); + if (cred_rc) { + wpa_printf(MSG_DEBUG, "Interworking: Highest roaming " + "consortium matching credential priority %d " + "(ignore BW)", + cred_rc->priority); + } + + cred = interworking_credentials_available_realm(wpa_s, bss, 1); + if (cred) { + wpa_printf(MSG_DEBUG, "Interworking: Highest NAI Realm " + "list matching credential priority %d " + "(ignore BW)", cred->priority); + } + + cred_3gpp = interworking_credentials_available_3gpp(wpa_s, bss, + 1); + if (cred_3gpp) { + wpa_printf(MSG_DEBUG, "Interworking: Highest 3GPP " + "matching credential priority %d (ignore BW)", + cred_3gpp->priority); + } + } + if (cred_rc && (cred == NULL || cred_rc->priority >= cred->priority) && (cred_3gpp == NULL || cred_rc->priority >= cred_3gpp->priority)) @@ -1465,7 +1572,7 @@ fail: static struct wpa_cred * interworking_credentials_available_3gpp( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw) { struct wpa_cred *selected = NULL; #ifdef INTERWORKING_3GPP @@ -1546,6 +1653,9 @@ static struct wpa_cred * interworking_credentials_available_3gpp( continue; if (cred_no_required_oi_match(cred, bss)) continue; + if (!ignore_bw && + cred_below_min_backhaul(wpa_s, cred, bss)) + continue; if (selected == NULL || selected->priority < cred->priority) selected = cred; @@ -1557,7 +1667,7 @@ static struct wpa_cred * interworking_credentials_available_3gpp( static struct wpa_cred * interworking_credentials_available_realm( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw) { struct wpa_cred *cred, *selected = NULL; struct nai_realm *realm; @@ -1590,6 +1700,9 @@ static struct wpa_cred * interworking_credentials_available_realm( continue; if (cred_no_required_oi_match(cred, bss)) continue; + if (!ignore_bw && + cred_below_min_backhaul(wpa_s, cred, bss)) + continue; if (selected == NULL || selected->priority < cred->priority) selected = cred; @@ -1604,8 +1717,8 @@ static struct wpa_cred * interworking_credentials_available_realm( } -static struct wpa_cred * interworking_credentials_available( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) +static struct wpa_cred * interworking_credentials_available_helper( + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw) { struct wpa_cred *cred, *cred2; @@ -1616,15 +1729,16 @@ static struct wpa_cred * interworking_credentials_available( return NULL; } - cred = interworking_credentials_available_realm(wpa_s, bss); - cred2 = interworking_credentials_available_3gpp(wpa_s, bss); + cred = interworking_credentials_available_realm(wpa_s, bss, ignore_bw); + cred2 = interworking_credentials_available_3gpp(wpa_s, bss, ignore_bw); if (cred && cred2 && cred2->priority >= cred->priority) cred = cred2; if (!cred) cred = cred2; cred2 = interworking_credentials_available_roaming_consortium(wpa_s, - bss); + bss, + ignore_bw); if (cred && cred2 && cred2->priority >= cred->priority) cred = cred2; if (!cred) @@ -1634,6 +1748,18 @@ static struct wpa_cred * interworking_credentials_available( } +static struct wpa_cred * interworking_credentials_available( + struct wpa_supplicant *wpa_s, struct wpa_bss *bss) +{ + struct wpa_cred *cred; + + cred = interworking_credentials_available_helper(wpa_s, bss, 0); + if (cred) + return cred; + return interworking_credentials_available_helper(wpa_s, bss, 1); +} + + int domain_name_list_contains(struct wpabuf *domain_names, const char *domain, int exact_match) { @@ -1868,8 +1994,10 @@ static void interworking_select_network(struct wpa_supplicant *wpa_s) type = "roaming"; else type = "unknown"; - wpa_msg(wpa_s, MSG_INFO, INTERWORKING_AP MACSTR " type=%s", - MAC2STR(bss->bssid), type); + wpa_msg(wpa_s, MSG_INFO, INTERWORKING_AP MACSTR " type=%s%s", + MAC2STR(bss->bssid), type, + cred_below_min_backhaul(wpa_s, cred, bss) ? + " below_min_backhaul=1" : ""); if (wpa_s->auto_select || (wpa_s->conf->auto_interworking && wpa_s->auto_network_select)) { diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index 3c90362ff..42954e6ab 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -446,6 +446,16 @@ fast_reauth=1 # This optional field can be used to keep track of the SP that provisioned # the credential to find the PPS MO (./Wi-Fi/). # +# Minimum backhaul threshold (PPS//Policy/MinBackhauldThreshold/*) +# These fields can be used to specify minimum download/upload backhaul +# bandwidth that is preferred for the credential. This constraint is +# ignored if the AP does not advertise WAN Metrics information or if the +# limit would prevent any connection. Values are in kilobits per second. +# min_dl_bandwidth_home +# min_ul_bandwidth_home +# min_dl_bandwidth_roaming +# min_ul_bandwidth_roaming +# # for example: # #cred={