diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c index 9af260691..34fa968cc 100644 --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -1205,3 +1205,27 @@ const u8 * get_ie(const u8 *ies, size_t len, u8 eid) return NULL; } + + +size_t mbo_add_ie(u8 *buf, size_t len, const u8 *attr, size_t attr_len) +{ + /* + * MBO IE requires 6 bytes without the attributes: EID (1), length (1), + * OUI (3), OUI type (1). + */ + if (len < 6 + attr_len) { + wpa_printf(MSG_DEBUG, + "MBO: Not enough room in buffer for MBO IE: buf len = %zu, attr_len = %zu", + len, attr_len); + return 0; + } + + *buf++ = WLAN_EID_VENDOR_SPECIFIC; + *buf++ = attr_len + 4; + WPA_PUT_BE24(buf, OUI_WFA); + buf += 3; + *buf++ = MBO_OUI_TYPE; + os_memcpy(buf, attr, attr_len); + + return 6 + attr_len; +} diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h index f1ee30204..b87eeec73 100644 --- a/src/common/ieee802_11_common.h +++ b/src/common/ieee802_11_common.h @@ -128,4 +128,6 @@ const char * fc2str(u16 fc); const u8 * get_ie(const u8 *ies, size_t len, u8 eid); +size_t mbo_add_ie(u8 *buf, size_t len, const u8 *attr, size_t attr_len); + #endif /* IEEE802_11_COMMON_H */ diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index ac62f41b2..c4e8c9f77 100644 --- a/wpa_supplicant/Android.mk +++ b/wpa_supplicant/Android.mk @@ -823,6 +823,7 @@ endif endif ifdef CONFIG_MBO +OBJS += mbo.c L_CFLAGS += -DCONFIG_MBO endif diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index bf2c0aaf6..3f6038a29 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -865,6 +865,7 @@ endif endif ifdef CONFIG_MBO +OBJS += mbo.o CFLAGS += -DCONFIG_MBO endif diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 1057a84f9..1960ebc25 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -490,6 +490,10 @@ static int wpa_supplicant_ctrl_iface_set(struct wpa_supplicant *wpa_s, #endif /* CONFIG_NO_CONFIG_BLOBS */ } else if (os_strcasecmp(cmd, "setband") == 0) { ret = wpas_ctrl_set_band(wpa_s, value); +#ifdef CONFIG_MBO + } else if (os_strcasecmp(cmd, "non_pref_chan") == 0) { + ret = wpas_mbo_update_non_pref_chan(wpa_s, value); +#endif /* CONFIG_MBO */ } else { value[-1] = '='; ret = wpa_config_process_global(wpa_s->conf, cmd, -1); diff --git a/wpa_supplicant/mbo.c b/wpa_supplicant/mbo.c new file mode 100644 index 000000000..369232e7e --- /dev/null +++ b/wpa_supplicant/mbo.c @@ -0,0 +1,262 @@ +/* + * wpa_supplicant - MBO + * + * Copyright(c) 2015 Intel Deutschland GmbH + * Contact Information: + * Intel Linux Wireless + * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "config.h" +#include "wpa_supplicant_i.h" +#include "driver_i.h" +#include "bss.h" + +/* type + length + oui + oui type */ +#define MBO_IE_HEADER 6 + + +static int wpas_mbo_validate_non_pref_chan(u8 oper_class, u8 chan, u8 reason) +{ + if (reason > MBO_NON_PREF_CHAN_REASON_INT_INTERFERENCE) + return -1; + + /* Only checking the validity of the channel and oper_class */ + if (ieee80211_chan_to_freq(NULL, oper_class, chan) == -1) + return -1; + + return 0; +} + + +static void wpas_mbo_non_pref_chan_attr_body(struct wpa_supplicant *wpa_s, + struct wpabuf *mbo, + u8 start, u8 end) +{ + u8 i; + + wpabuf_put_u8(mbo, wpa_s->non_pref_chan[start].oper_class); + + for (i = start; i < end; i++) + wpabuf_put_u8(mbo, wpa_s->non_pref_chan[i].chan); + + wpabuf_put_u8(mbo, wpa_s->non_pref_chan[start].preference); + wpabuf_put_u8(mbo, wpa_s->non_pref_chan[start].reason); + wpabuf_put_u8(mbo, wpa_s->non_pref_chan[start].reason_detail); +} + + +static void wpas_mbo_non_pref_chan_attr(struct wpa_supplicant *wpa_s, + struct wpabuf *mbo, u8 start, u8 end) +{ + size_t size = end - start + 4; + + if (size + 2 > wpabuf_tailroom(mbo)) + return; + + wpabuf_put_u8(mbo, MBO_ATTR_ID_NON_PREF_CHAN_REPORT); + wpabuf_put_u8(mbo, size); /* Length */ + + wpas_mbo_non_pref_chan_attr_body(wpa_s, mbo, start, end); +} + + +static void wpas_mbo_non_pref_chan_attrs(struct wpa_supplicant *wpa_s, + struct wpabuf *mbo) + +{ + u8 i, start = 0; + struct wpa_mbo_non_pref_channel *start_pref; + + if (!wpa_s->non_pref_chan || !wpa_s->non_pref_chan_num) + return; + start_pref = &wpa_s->non_pref_chan[0]; + + for (i = 1; i <= wpa_s->non_pref_chan_num; i++) { + struct wpa_mbo_non_pref_channel *non_pref = NULL; + + if (i < wpa_s->non_pref_chan_num) + non_pref = &wpa_s->non_pref_chan[i]; + if (!non_pref || + non_pref->oper_class != start_pref->oper_class || + non_pref->reason != start_pref->reason || + non_pref->reason_detail != start_pref->reason_detail || + non_pref->preference != start_pref->preference) { + wpas_mbo_non_pref_chan_attr(wpa_s, mbo, start, i); + + if (!non_pref) + return; + + start = i; + start_pref = non_pref; + } + } +} + + +int wpas_mbo_ie(struct wpa_supplicant *wpa_s, u8 *buf, size_t len) +{ + struct wpabuf *mbo; + int res; + + if (!wpa_s->non_pref_chan || !wpa_s->non_pref_chan_num || + len < MBO_IE_HEADER + 7) + return 0; + + /* Leave room for the MBO IE header */ + mbo = wpabuf_alloc(len - MBO_IE_HEADER); + if (!mbo) + return 0; + + /* Add non-preferred channels attribute */ + wpas_mbo_non_pref_chan_attrs(wpa_s, mbo); + + res = mbo_add_ie(buf, len, wpabuf_head_u8(mbo), wpabuf_len(mbo)); + if (!res) + wpa_printf(MSG_ERROR, "Failed to add MBO IE"); + + wpabuf_free(mbo); + return res; +} + + +static int wpa_non_pref_chan_is_eq(struct wpa_mbo_non_pref_channel *a, + struct wpa_mbo_non_pref_channel *b) +{ + return a->oper_class == b->oper_class && a->chan == b->chan; +} + + +/* + * wpa_non_pref_chan_cmp - Compare two channels for sorting + * + * In MBO IE non-preferred channel subelement we can put many channels in an + * attribute if they are in the same operating class and have the same + * preference, reason, and reason detail. To make it easy for the functions that + * build the IE attributes and WNM Request subelements, save the channels sorted + * by their oper_class, reason, and reason_detail. + */ +static int wpa_non_pref_chan_cmp(const void *_a, const void *_b) +{ + const struct wpa_mbo_non_pref_channel *a = _a, *b = _b; + + if (a->oper_class != b->oper_class) + return a->oper_class - b->oper_class; + if (a->reason != b->reason) + return a->reason - b->reason; + if (a->reason_detail != b->reason_detail) + return a->reason_detail - b->reason_detail; + return a->preference - b->preference; +} + + +int wpas_mbo_update_non_pref_chan(struct wpa_supplicant *wpa_s, + const char *non_pref_chan) +{ + char *cmd, *token, *context = NULL; + struct wpa_mbo_non_pref_channel *chans = NULL, *tmp_chans; + size_t num = 0, size = 0; + unsigned i; + + wpa_printf(MSG_DEBUG, "MBO: Update non-preferred channels, non_pref_chan=%s", + non_pref_chan ? non_pref_chan : "N/A"); + + /* + * The shortest channel configuration is 10 characters - commas, 3 + * colons, and 4 values that one of them (oper_class) is 2 digits or + * more. + */ + if (!non_pref_chan || os_strlen(non_pref_chan) < 10) + goto update; + + cmd = os_strdup(non_pref_chan); + if (!cmd) + return -1; + + while ((token = str_token(cmd, " ", &context))) { + struct wpa_mbo_non_pref_channel *chan; + int ret; + unsigned int _oper_class; + unsigned int _chan; + unsigned int _preference; + unsigned int _reason; + unsigned int _reason_detail; + + if (num == size) { + size = size ? size * 2 : 1; + tmp_chans = os_realloc_array(chans, size, + sizeof(*chans)); + if (!tmp_chans) { + wpa_printf(MSG_ERROR, + "Couldn't reallocate non_pref_chan"); + goto fail; + } + chans = tmp_chans; + } + + chan = &chans[num]; + + ret = sscanf(token, "%u:%u:%u:%u:%u", &_oper_class, + &_chan, &_preference, &_reason, + &_reason_detail); + if ((ret != 4 && ret != 5) || + _oper_class > 255 || _chan > 255 || + _preference > 255 || _reason > 65535 || + (ret == 5 && _reason_detail > 255)) { + wpa_printf(MSG_ERROR, "Invalid non-pref chan input %s", + token); + goto fail; + } + chan->oper_class = _oper_class; + chan->chan = _chan; + chan->preference = _preference; + chan->reason = _reason; + chan->reason_detail = ret == 4 ? 0 : _reason_detail; + + if (wpas_mbo_validate_non_pref_chan(chan->oper_class, + chan->chan, chan->reason)) { + wpa_printf(MSG_ERROR, + "Invalid non_pref_chan: oper class %d chan %d reason %d", + chan->oper_class, chan->chan, chan->reason); + goto fail; + } + + for (i = 0; i < num; i++) + if (wpa_non_pref_chan_is_eq(chan, &chans[i])) + break; + if (i != num) { + wpa_printf(MSG_ERROR, + "oper class %d chan %d is duplicated", + chan->oper_class, chan->chan); + goto fail; + } + + num++; + } + + os_free(cmd); + + if (chans) { + qsort(chans, num, sizeof(struct wpa_mbo_non_pref_channel), + wpa_non_pref_chan_cmp); + } + +update: + os_free(wpa_s->non_pref_chan); + wpa_s->non_pref_chan = chans; + wpa_s->non_pref_chan_num = num; + + return 0; + +fail: + os_free(chans); + os_free(cmd); + return -1; +} diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index c6b8a58f1..3aea6483b 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -482,6 +482,21 @@ static void sme_send_authentication(struct wpa_supplicant *wpa_s, sme_auth_handle_rrm(wpa_s, bss); +#ifdef CONFIG_MBO + if (wpa_bss_get_vendor_ie(bss, MBO_IE_VENDOR_TYPE)) { + u8 *pos; + size_t len; + int res; + + pos = wpa_s->sme.assoc_req_ie + wpa_s->sme.assoc_req_ie_len; + len = sizeof(wpa_s->sme.assoc_req_ie) - + wpa_s->sme.assoc_req_ie_len; + res = wpas_mbo_ie(wpa_s, pos, len); + if (res >= 0) + wpa_s->sme.assoc_req_ie_len += res; + } +#endif /* CONFIG_MBO */ + #ifdef CONFIG_SAE if (!skip_auth && params.auth_alg == WPA_AUTH_ALG_SAE && pmksa_cache_set_current(wpa_s->wpa, NULL, bss->bssid, ssid, 0) == 0) diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index ba82e50cc..afdfe6800 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -549,6 +549,12 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) wpa_s->sched_scan_plans_num = 0; os_free(wpa_s->sched_scan_plans); wpa_s->sched_scan_plans = NULL; + +#ifdef CONFIG_MBO + wpa_s->non_pref_chan_num = 0; + os_free(wpa_s->non_pref_chan); + wpa_s->non_pref_chan = NULL; +#endif /* CONFIG_MBO */ } @@ -1421,6 +1427,9 @@ static void wpas_ext_capab_byte(struct wpa_supplicant *wpa_s, u8 *pos, int idx) if (wpa_s->conf->hs20) *pos |= 0x40; /* Bit 46 - WNM-Notification */ #endif /* CONFIG_HS20 */ +#ifdef CONFIG_MBO + *pos |= 0x40; /* Bit 46 - WNM-Notification */ +#endif /* CONFIG_MBO */ break; case 6: /* Bits 48-55 */ break; @@ -2296,6 +2305,20 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) } #endif /* CONFIG_FST */ +#ifdef CONFIG_MBO + if (wpa_bss_get_vendor_ie(bss, MBO_IE_VENDOR_TYPE)) { + u8 *pos; + size_t len; + int res; + + pos = wpa_ie + wpa_ie_len; + len = sizeof(wpa_ie) - wpa_ie_len; + res = wpas_mbo_ie(wpa_s, pos, len); + if (res >= 0) + wpa_ie_len += res; + } +#endif /* CONFIG_MBO */ + wpa_clear_keys(wpa_s, bss ? bss->bssid : NULL); use_crypt = 1; cipher_pairwise = wpa_s->pairwise_cipher; @@ -4764,6 +4787,9 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, #ifdef CONFIG_HS20 hs20_init(wpa_s); #endif /* CONFIG_HS20 */ +#ifdef CONFIG_MBO + wpas_mbo_update_non_pref_chan(wpa_s, wpa_s->conf->non_pref_chan); +#endif /* CONFIG_MBO */ return 0; } diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 874299300..c14a61a87 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -1016,6 +1016,18 @@ struct wpa_supplicant { const struct wpabuf *fst_ies; struct wpabuf *received_mb_ies; #endif /* CONFIG_FST */ + +#ifdef CONFIG_MBO + /* Multiband operation non-preferred channel */ + struct wpa_mbo_non_pref_channel { + enum mbo_non_pref_chan_reason reason; + u8 oper_class; + u8 chan; + u8 reason_detail; + u8 preference; + } *non_pref_chan; + size_t non_pref_chan_num; +#endif /* CONFIG_MBO */ }; @@ -1128,6 +1140,12 @@ void wpas_rrm_handle_link_measurement_request(struct wpa_supplicant *wpa_s, const u8 *frame, size_t len, int rssi); + +/* MBO functions */ +int wpas_mbo_ie(struct wpa_supplicant *wpa_s, u8 *buf, size_t len); +int wpas_mbo_update_non_pref_chan(struct wpa_supplicant *wpa_s, + const char *non_pref_chan); + /** * wpa_supplicant_ctrl_iface_ctrl_rsp_handle - Handle a control response * @wpa_s: Pointer to wpa_supplicant data