3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2024-10-28 07:39:23 +01:00
iwd/src/ie.c
James Prestwood 8bcb8e870e ie: fix incorrect length check for hotspot indication
The length check was incorrectly assuming that PPS MO ID or
ANQP Domain ID would be present in the IE. Both these are optional
and without then the minimum length is 5 bytes, not 7.
2019-07-12 18:51:57 -05:00

2486 lines
54 KiB
C

/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2013-2014 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <ell/ell.h>
#include "src/util.h"
#include "src/crypto.h"
#include "src/ie.h"
static const uint8_t ieee_oui[3] = { 0x00, 0x0f, 0xac };
static const uint8_t microsoft_oui[3] = { 0x00, 0x50, 0xf2 };
static const uint8_t wifi_alliance_oui[3] = { 0x50, 0x6f, 0x9a };
void ie_tlv_iter_init(struct ie_tlv_iter *iter, const unsigned char *tlv,
unsigned int len)
{
iter->tlv = tlv;
iter->max = len;
iter->pos = 0;
}
void ie_tlv_iter_recurse(struct ie_tlv_iter *iter,
struct ie_tlv_iter *recurse)
{
recurse->tlv = iter->data;
recurse->max = iter->len;
recurse->pos = 0;
}
bool ie_tlv_iter_next(struct ie_tlv_iter *iter)
{
const unsigned char *tlv = iter->tlv + iter->pos;
const unsigned char *end = iter->tlv + iter->max;
unsigned int tag;
unsigned int len;
if (iter->pos + 1 >= iter->max)
return false;
tag = *tlv++;
len = *tlv++;
if (tag == IE_TYPE_EXTENSION) {
if (iter->pos + 2 >= iter->max || len < 1)
return false;
tag = 256 + *tlv++;
len--;
}
if (tlv + len > end)
return false;
iter->tag = tag;
iter->len = len;
iter->data = tlv;
iter->pos = tlv + len - iter->tlv;
return true;
}
/*
* Concatenate all vendor IEs with a given OUI + type.
*
* Returns a newly allocated buffer with the contents of the matching ies
* copied into it. @out_len is set to the overall size of the contents.
* If no matching elements were found, NULL is returned and @out_len is
* set to -ENOENT.
*/
static void *ie_tlv_vendor_ie_concat(const unsigned char oui[],
unsigned char type,
const unsigned char *ies,
unsigned int len,
bool empty_ok,
ssize_t *out_len)
{
struct ie_tlv_iter iter;
const unsigned char *data;
unsigned int ie_len;
unsigned int concat_len = 0;
unsigned char *ret;
bool ie_found = false;
ie_tlv_iter_init(&iter, ies, len);
while (ie_tlv_iter_next(&iter)) {
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC)
continue;
ie_len = ie_tlv_iter_get_length(&iter);
if (ie_len < 4)
continue;
data = ie_tlv_iter_get_data(&iter);
if (memcmp(data, oui, 3))
continue;
if (data[3] != type)
continue;
concat_len += ie_len - 4;
ie_found = true;
}
if (concat_len == 0) {
if (out_len)
*out_len = (ie_found && empty_ok) ? 0 : -ENOENT;
return NULL;
}
ie_tlv_iter_init(&iter, ies, len);
ret = l_malloc(concat_len);
concat_len = 0;
while (ie_tlv_iter_next(&iter)) {
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC)
continue;
ie_len = ie_tlv_iter_get_length(&iter);
if (ie_len < 4)
continue;
data = ie_tlv_iter_get_data(&iter);
if (memcmp(data, oui, 3))
continue;
if (data[3] != type)
continue;
memcpy(ret + concat_len, data + 4, ie_len - 4);
concat_len += ie_len - 4;
}
if (out_len)
*out_len = concat_len;
return ret;
}
/*
* Wi-Fi Simple Configuration v2.0.5, Section 8.2:
* "There may be more than one instance of the Wi-Fi Simple Configuration
* Information Element in a single 802.11 management frame. If multiple
* Information Elements are present, the Wi-Fi Simple Configuration data
* consists of the concatenation of the Data components of those Information
* Elements (the order of these elements in the original packet shall be
* preserved when concatenating Data components)."
*/
void *ie_tlv_extract_wsc_payload(const unsigned char *ies, size_t len,
ssize_t *out_len)
{
return ie_tlv_vendor_ie_concat(microsoft_oui, 0x04,
ies, len, false, out_len);
}
/*
* Wi-Fi P2P Technical Specification v1.7, Section 8.2:
* "More than one P2P IE may be included in a single frame. If multiple P2P
* IEs are present, the complete P2P attribute data consists of the
* concatenation of the P2P Attribute fields of the P2P IEs. The P2P
* Attributes field of each P2P IE may be any length up to the maximum
* (251 octets). The order of the concatenated P2P attribute data shall be
* preserved in the ordering of the P2P IEs in the frame. All of the P2P IEs
* shall fit within a single frame and shall be adjacent in the frame."
*/
void *ie_tlv_extract_p2p_payload(const unsigned char *ies, size_t len,
ssize_t *out_len)
{
return ie_tlv_vendor_ie_concat(wifi_alliance_oui, 0x09,
ies, len, true, out_len);
}
/*
* Encapsulate & Fragment data into Vendor IE with a given OUI + type
*
* Returns a newly allocated buffer with the contents of encapsulated into
* multiple vendor IE. @out_len is set to the overall size of the contents.
*/
static void *ie_tlv_vendor_ie_encapsulate(const unsigned char oui[],
uint8_t type,
const void *data, size_t len,
bool build_empty,
size_t *out_len)
{
size_t overhead;
size_t ie_len;
size_t offset;
uint8_t *ret;
/*
* Each Vendor IE can contain up to 251 bytes of data.
* 255 byte maximum length - 3 for oui and 1 for type
*/
overhead = (len + 250) / 251 * 6;
if (len == 0 && build_empty)
overhead = 6;
ret = l_malloc(len + overhead);
if (out_len)
*out_len = len + overhead;
offset = 0;
while (overhead) {
ie_len = len <= 251 ? len : 251;
ret[offset++] = IE_TYPE_VENDOR_SPECIFIC;
ret[offset++] = ie_len + 4;
memcpy(ret + offset, oui, 3);
offset += 3;
ret[offset++] = type;
memcpy(ret + offset, data, ie_len);
data += ie_len;
len -= ie_len;
overhead -= 6;
}
return ret;
}
void *ie_tlv_encapsulate_wsc_payload(const uint8_t *data, size_t len,
size_t *out_len)
{
return ie_tlv_vendor_ie_encapsulate(microsoft_oui, 0x04,
data, len, false, out_len);
}
void *ie_tlv_encapsulate_p2p_payload(const uint8_t *data, size_t len,
size_t *out_len)
{
return ie_tlv_vendor_ie_encapsulate(wifi_alliance_oui, 0x09,
data, len, true, out_len);
}
#define TLV_HEADER_LEN 2
static bool ie_tlv_builder_init_recurse(struct ie_tlv_builder *builder,
unsigned char *tlv, unsigned int size)
{
if (!builder)
return false;
if (!tlv) {
memset(builder->buf, 0, MAX_BUILDER_SIZE);
builder->tlv = builder->buf;
builder->max = MAX_BUILDER_SIZE;
} else {
builder->tlv = tlv;
builder->max = size;
}
builder->pos = 0;
builder->parent = NULL;
builder->tag = 0xffff;
builder->len = 0;
return true;
}
bool ie_tlv_builder_init(struct ie_tlv_builder *builder, unsigned char *buf,
size_t len)
{
return ie_tlv_builder_init_recurse(builder, buf, len);
}
static void ie_tlv_builder_write_header(struct ie_tlv_builder *builder)
{
unsigned char *tlv = builder->tlv + builder->pos;
if (builder->tag < 256) {
tlv[0] = builder->tag;
tlv[1] = builder->len;
} else {
tlv[0] = IE_TYPE_EXTENSION;
tlv[1] = builder->len + 1;
tlv[2] = builder->tag - 256;
}
}
bool ie_tlv_builder_set_length(struct ie_tlv_builder *builder,
unsigned int new_len)
{
unsigned int new_pos = builder->pos + TLV_HEADER_LEN + new_len;
if (builder->tag >= 256)
new_pos += 1;
if (new_pos > builder->max)
return false;
if (builder->parent)
ie_tlv_builder_set_length(builder->parent, new_pos);
builder->len = new_len;
return true;
}
bool ie_tlv_builder_next(struct ie_tlv_builder *builder, unsigned int new_tag)
{
if (new_tag > 0x1ff)
return false;
if (builder->tag != 0xffff) {
ie_tlv_builder_write_header(builder);
builder->pos += TLV_HEADER_LEN + builder->tlv[builder->pos + 1];
}
builder->tag = new_tag;
return ie_tlv_builder_set_length(builder, 0);
}
unsigned char *ie_tlv_builder_get_data(struct ie_tlv_builder *builder)
{
return builder->tlv + TLV_HEADER_LEN + builder->pos +
(builder->tag >= 256 ? 1 : 0);
}
bool ie_tlv_builder_set_data(struct ie_tlv_builder *builder,
const void *data, size_t len)
{
if (!ie_tlv_builder_set_length(builder, len))
return false;
memcpy(ie_tlv_builder_get_data(builder), data, len);
return true;
}
bool ie_tlv_builder_recurse(struct ie_tlv_builder *builder,
struct ie_tlv_builder *recurse)
{
unsigned char *end = builder->buf + builder->max;
unsigned char *data = ie_tlv_builder_get_data(builder);
if (!ie_tlv_builder_init_recurse(recurse, data, end - data))
return false;
recurse->parent = builder;
return true;
}
unsigned char *ie_tlv_builder_finalize(struct ie_tlv_builder *builder,
unsigned int *out_len)
{
unsigned int len = 0;
if (builder->tag != 0xffff) {
ie_tlv_builder_write_header(builder);
len = builder->pos + TLV_HEADER_LEN +
builder->tlv[builder->pos + 1];
}
if (out_len)
*out_len = len;
return builder->tlv;
}
/*
* Converts RSN cipher suite into an unsigned integer suitable to be used
* by nl80211. The enumeration is the same as found in crypto.h
*
* If the suite value is invalid, this function returns 0.
*/
uint32_t ie_rsn_cipher_suite_to_cipher(enum ie_rsn_cipher_suite suite)
{
switch (suite) {
case IE_RSN_CIPHER_SUITE_CCMP:
return CRYPTO_CIPHER_CCMP;
case IE_RSN_CIPHER_SUITE_TKIP:
return CRYPTO_CIPHER_TKIP;
case IE_RSN_CIPHER_SUITE_WEP40:
return CRYPTO_CIPHER_WEP40;
case IE_RSN_CIPHER_SUITE_WEP104:
return CRYPTO_CIPHER_WEP104;
case IE_RSN_CIPHER_SUITE_BIP:
return CRYPTO_CIPHER_BIP;
default:
return 0;
}
}
/* 802.11, Section 8.4.2.27.2 */
static bool ie_parse_cipher_suite(const uint8_t *data,
enum ie_rsn_cipher_suite *out)
{
/*
* Compare the OUI to the ones we know. OUI Format is found in
* Figure 8-187 of 802.11
*/
if (!memcmp(data, ieee_oui, 3)) {
/* Suite type from Table 8-99 */
switch (data[3]) {
case 0:
*out = IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER;
return true;
case 1:
*out = IE_RSN_CIPHER_SUITE_WEP40;
return true;
case 2:
*out = IE_RSN_CIPHER_SUITE_TKIP;
return true;
case 4:
*out = IE_RSN_CIPHER_SUITE_CCMP;
return true;
case 5:
*out = IE_RSN_CIPHER_SUITE_WEP104;
return true;
case 6:
*out = IE_RSN_CIPHER_SUITE_BIP;
return true;
case 7:
*out = IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC;
return true;
default:
return false;
}
}
return false;
}
/* 802.11, Section 8.4.2.27.2 */
static int ie_parse_rsn_akm_suite(const uint8_t *data,
enum ie_rsn_akm_suite *out)
{
/*
* Compare the OUI to the ones we know. OUI Format is found in
* Figure 8-187 of 802.11
*/
if (!memcmp(data, ieee_oui, 3)) {
/* Suite type from Table 8-101 */
switch (data[3]) {
case 0:
return -EINVAL;
case 1:
*out = IE_RSN_AKM_SUITE_8021X;
return 0;
case 2:
*out = IE_RSN_AKM_SUITE_PSK;
return 0;
case 3:
*out = IE_RSN_AKM_SUITE_FT_OVER_8021X;
return 0;
case 4:
*out = IE_RSN_AKM_SUITE_FT_USING_PSK;
return 0;
case 5:
*out = IE_RSN_AKM_SUITE_8021X_SHA256;
return 0;
case 6:
*out = IE_RSN_AKM_SUITE_PSK_SHA256;
return 0;
case 7:
*out = IE_RSN_AKM_SUITE_TDLS;
return 0;
case 8:
*out = IE_RSN_AKM_SUITE_SAE_SHA256;
return 0;
case 9:
*out = IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256;
return 0;
case 10:
*out = IE_RSN_AKM_SUITE_AP_PEER_KEY_SHA256;
return 0;
case 11:
*out = IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA256;
return 0;
case 12:
*out = IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA384;
return 0;
case 13:
*out = IE_RSN_AKM_SUITE_FT_OVER_8021X_SHA384;
return 0;
case 14:
*out = IE_RSN_AKM_SUITE_FILS_SHA256;
return 0;
case 15:
*out = IE_RSN_AKM_SUITE_FILS_SHA384;
return 0;
case 16:
*out = IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256;
return 0;
case 17:
*out = IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384;
return 0;
case 18:
*out = IE_RSN_AKM_SUITE_OWE;
return 0;
default:
return -ENOENT;
}
}
return -ENOENT;
}
static int ie_parse_osen_akm_suite(const uint8_t *data,
enum ie_rsn_akm_suite *out)
{
if (memcmp(data, wifi_alliance_oui, 3))
return -ENOENT;
if (data[3] != 1)
return -ENOENT;
*out = IE_RSN_AKM_SUITE_OSEN;
return 0;
}
static bool ie_parse_group_cipher(const uint8_t *data,
enum ie_rsn_cipher_suite *out)
{
enum ie_rsn_cipher_suite tmp;
bool r = ie_parse_cipher_suite(data, &tmp);
if (!r)
return r;
switch (tmp) {
case IE_RSN_CIPHER_SUITE_CCMP:
case IE_RSN_CIPHER_SUITE_TKIP:
case IE_RSN_CIPHER_SUITE_WEP104:
case IE_RSN_CIPHER_SUITE_WEP40:
case IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC:
break;
default:
return false;
}
*out = tmp;
return true;
}
static bool ie_parse_pairwise_cipher(const uint8_t *data,
enum ie_rsn_cipher_suite *out)
{
enum ie_rsn_cipher_suite tmp;
bool r = ie_parse_cipher_suite(data, &tmp);
if (!r)
return r;
switch (tmp) {
case IE_RSN_CIPHER_SUITE_CCMP:
case IE_RSN_CIPHER_SUITE_TKIP:
case IE_RSN_CIPHER_SUITE_WEP104:
case IE_RSN_CIPHER_SUITE_WEP40:
case IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER:
break;
default:
return false;
}
*out = tmp;
return true;
}
static bool ie_parse_group_management_cipher(const uint8_t *data,
enum ie_rsn_cipher_suite *out)
{
enum ie_rsn_cipher_suite tmp;
bool r = ie_parse_cipher_suite(data, &tmp);
if (!r)
return r;
switch (tmp) {
case IE_RSN_CIPHER_SUITE_BIP:
case IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC:
break;
default:
return false;
}
*out = tmp;
return true;
}
#define RSNE_ADVANCE(data, len, step) \
data += step; \
len -= step; \
\
if (len == 0) \
goto done \
static int parse_ciphers(const uint8_t *data, size_t len,
int (*akm_parse)(const uint8_t *data,
enum ie_rsn_akm_suite *out),
struct ie_rsn_info *out_info)
{
uint16_t count;
uint16_t i;
/* Parse Group Cipher Suite field */
if (len < 4)
return -EBADMSG;
if (!ie_parse_group_cipher(data, &out_info->group_cipher))
return -ERANGE;
RSNE_ADVANCE(data, len, 4);
/* Parse Pairwise Cipher Suite Count field */
if (len < 2)
return -EBADMSG;
count = l_get_le16(data);
/*
* The spec doesn't seem to explicitly say what to do in this case,
* so we assume this situation is invalid.
*/
if (count == 0)
return -EINVAL;
data += 2;
len -= 2;
if (len < 4 * count)
return -EBADMSG;
/* Parse Pairwise Cipher Suite List field */
for (i = 0, out_info->pairwise_ciphers = 0; i < count; i++) {
enum ie_rsn_cipher_suite suite;
if (!ie_parse_pairwise_cipher(data + i * 4, &suite))
return -ERANGE;
out_info->pairwise_ciphers |= suite;
}
RSNE_ADVANCE(data, len, count * 4);
/* Parse AKM Suite Count field */
if (len < 2)
return -EBADMSG;
count = l_get_le16(data);
if (count == 0)
return -EINVAL;
data += 2;
len -= 2;
if (len < 4 * count)
return -EBADMSG;
/* Parse AKM Suite List field */
for (i = 0, out_info->akm_suites = 0; i < count; i++) {
enum ie_rsn_akm_suite suite;
int ret;
ret = akm_parse(data + i * 4, &suite);
switch (ret) {
case 0:
out_info->akm_suites |= suite;
break;
case -ENOENT:
/* Skip unknown or vendor specific AKMs */
break;
default:
return -EBADMSG;
}
}
RSNE_ADVANCE(data, len, count * 4);
if (len < 2)
return -EBADMSG;
out_info->preauthentication = util_is_bit_set(data[0], 0);
out_info->no_pairwise = util_is_bit_set(data[0], 1);
out_info->ptksa_replay_counter = util_bit_field(data[0], 2, 2);
out_info->gtksa_replay_counter = util_bit_field(data[0], 4, 2);
out_info->mfpr = util_is_bit_set(data[0], 6);
out_info->mfpc = util_is_bit_set(data[0], 7);
out_info->peerkey_enabled = util_is_bit_set(data[1], 1);
out_info->spp_a_msdu_capable = util_is_bit_set(data[1], 2);
out_info->spp_a_msdu_required = util_is_bit_set(data[1], 3);
out_info->pbac = util_is_bit_set(data[1], 4);
out_info->extended_key_id = util_is_bit_set(data[1], 5);
/*
* BIP—default group management cipher suite in an RSNA with
* management frame protection enabled
*/
if (out_info->mfpc)
out_info->group_management_cipher = IE_RSN_CIPHER_SUITE_BIP;
RSNE_ADVANCE(data, len, 2);
/* Parse PMKID Count field */
if (len < 2)
return -EBADMSG;
out_info->num_pmkids = l_get_le16(data);
RSNE_ADVANCE(data, len, 2);
if (out_info->num_pmkids > 0) {
if (len < 16 * out_info->num_pmkids)
return -EBADMSG;
/*
* Parse PMKID List field.
*
* We simply assign the pointer to the PMKIDs to the structure.
* The PMKIDs are fixed size, 16 bytes each.
*/
out_info->pmkids = data;
RSNE_ADVANCE(data, len, out_info->num_pmkids * 16);
}
/* Parse Group Management Cipher Suite field */
if (len < 4)
return -EBADMSG;
if (!ie_parse_group_management_cipher(data,
&out_info->group_management_cipher))
return -ERANGE;
RSNE_ADVANCE(data, len, 4);
return -EBADMSG;
done:
return 0;
}
int ie_parse_rsne(struct ie_tlv_iter *iter, struct ie_rsn_info *out_info)
{
const uint8_t *data = iter->data;
size_t len = iter->len;
uint16_t version;
struct ie_rsn_info info;
memset(&info, 0, sizeof(info));
info.group_cipher = IE_RSN_CIPHER_SUITE_CCMP;
info.pairwise_ciphers = IE_RSN_CIPHER_SUITE_CCMP;
info.akm_suites = IE_RSN_AKM_SUITE_8021X;
/* Parse Version field */
if (len < 2)
return -EMSGSIZE;
version = l_get_le16(data);
if (version != 0x01)
return -EBADMSG;
RSNE_ADVANCE(data, len, 2);
if (parse_ciphers(data, len, ie_parse_rsn_akm_suite, &info) < 0)
return -EBADMSG;
done:
if (out_info)
memcpy(out_info, &info, sizeof(info));
return 0;
}
int ie_parse_rsne_from_data(const uint8_t *data, size_t len,
struct ie_rsn_info *info)
{
struct ie_tlv_iter iter;
ie_tlv_iter_init(&iter, data, len);
if (!ie_tlv_iter_next(&iter))
return -EMSGSIZE;
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_RSN)
return -EPROTOTYPE;
return ie_parse_rsne(&iter, info);
}
int ie_parse_osen(struct ie_tlv_iter *iter, struct ie_rsn_info *out_info)
{
const uint8_t *data = iter->data;
size_t len = iter->len;
struct ie_rsn_info info;
if (ie_tlv_iter_get_tag(iter) != IE_TYPE_VENDOR_SPECIFIC)
return -EPROTOTYPE;
if (!is_ie_wfa_ie(iter->data, iter->len, IE_WFA_OI_OSEN))
return -EPROTOTYPE;
RSNE_ADVANCE(data, len, 4);
memset(&info, 0, sizeof(info));
info.group_cipher = IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC;
info.pairwise_ciphers = IE_RSN_CIPHER_SUITE_CCMP;
info.akm_suites = IE_RSN_AKM_SUITE_8021X;
if (parse_ciphers(data, len, ie_parse_osen_akm_suite, &info) < 0)
return -EBADMSG;
done:
if (out_info)
memcpy(out_info, &info, sizeof(info));
return 0;
}
int ie_parse_osen_from_data(const uint8_t *data, size_t len,
struct ie_rsn_info *info)
{
struct ie_tlv_iter iter;
ie_tlv_iter_init(&iter, data, len);
if (!ie_tlv_iter_next(&iter))
return -EMSGSIZE;
return ie_parse_osen(&iter, info);
}
/*
* 802.11, Section 8.4.2.27.2
* 802.11i, Section 7.3.2.25.1 and WPA_80211_v3_1 Section 2.1
*/
static bool ie_build_cipher_suite(uint8_t *data, const uint8_t *oui,
const enum ie_rsn_cipher_suite suite)
{
switch (suite) {
case IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER:
memcpy(data, oui, 3);
data[3] = 0;
return true;
case IE_RSN_CIPHER_SUITE_WEP40:
memcpy(data, oui, 3);
data[3] = 1;
return true;
case IE_RSN_CIPHER_SUITE_TKIP:
memcpy(data, oui, 3);
data[3] = 2;
return true;
case IE_RSN_CIPHER_SUITE_CCMP:
memcpy(data, oui, 3);
data[3] = 4;
return true;
case IE_RSN_CIPHER_SUITE_WEP104:
memcpy(data, oui, 3);
data[3] = 5;
return true;
case IE_RSN_CIPHER_SUITE_BIP:
memcpy(data, oui, 3);
data[3] = 6;
return true;
case IE_RSN_CIPHER_SUITE_NO_GROUP_TRAFFIC:
memcpy(data, oui, 3);
data[3] = 7;
return true;
}
return false;
}
#define RETURN_AKM(data, oui, id) \
memcpy((data), (oui), 3); \
(data)[3] = (id); \
return true;
/* 802.11-2016, Section 9.4.2.25.3 */
static bool ie_build_rsn_akm_suite(uint8_t *data, enum ie_rsn_akm_suite suite)
{
switch (suite) {
case IE_RSN_AKM_SUITE_8021X:
RETURN_AKM(data, ieee_oui, 1);
case IE_RSN_AKM_SUITE_PSK:
RETURN_AKM(data, ieee_oui, 2);
case IE_RSN_AKM_SUITE_FT_OVER_8021X:
RETURN_AKM(data, ieee_oui, 3);
case IE_RSN_AKM_SUITE_FT_USING_PSK:
RETURN_AKM(data, ieee_oui, 4);
case IE_RSN_AKM_SUITE_8021X_SHA256:
RETURN_AKM(data, ieee_oui, 5);
case IE_RSN_AKM_SUITE_PSK_SHA256:
RETURN_AKM(data, ieee_oui, 6);
case IE_RSN_AKM_SUITE_TDLS:
RETURN_AKM(data, ieee_oui, 7);
case IE_RSN_AKM_SUITE_SAE_SHA256:
RETURN_AKM(data, ieee_oui, 8);
case IE_RSN_AKM_SUITE_FT_OVER_SAE_SHA256:
RETURN_AKM(data, ieee_oui, 9);
case IE_RSN_AKM_SUITE_AP_PEER_KEY_SHA256:
RETURN_AKM(data, ieee_oui, 10);
case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA256:
RETURN_AKM(data, ieee_oui, 11);
case IE_RSN_AKM_SUITE_8021X_SUITE_B_SHA384:
RETURN_AKM(data, ieee_oui, 12);
case IE_RSN_AKM_SUITE_FT_OVER_8021X_SHA384:
RETURN_AKM(data, ieee_oui, 13);
case IE_RSN_AKM_SUITE_FILS_SHA256:
RETURN_AKM(data, ieee_oui, 14);
case IE_RSN_AKM_SUITE_FILS_SHA384:
RETURN_AKM(data, ieee_oui, 15);
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256:
RETURN_AKM(data, ieee_oui, 16);
case IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384:
RETURN_AKM(data, ieee_oui, 17);
case IE_RSN_AKM_SUITE_OWE:
RETURN_AKM(data, ieee_oui, 18);
case IE_RSN_AKM_SUITE_OSEN:
RETURN_AKM(data, wifi_alliance_oui, 1);
}
return false;
}
/* 802.11i, Section 7.3.2.25.2 and WPA_80211_v3_1 Section 2.1 */
static bool ie_build_wpa_akm_suite(uint8_t *data, enum ie_rsn_akm_suite suite)
{
switch (suite) {
case IE_RSN_AKM_SUITE_8021X:
RETURN_AKM(data, microsoft_oui, 1);
case IE_RSN_AKM_SUITE_PSK:
RETURN_AKM(data, microsoft_oui, 2);
default:
break;
}
return false;
}
static int build_ciphers_common(const struct ie_rsn_info *info, uint8_t *to,
uint8_t max_len, bool force_group_mgmt_cipher)
{
/* These are the only valid pairwise suites */
static enum ie_rsn_cipher_suite pairwise_suites[] = {
IE_RSN_CIPHER_SUITE_CCMP,
IE_RSN_CIPHER_SUITE_TKIP,
IE_RSN_CIPHER_SUITE_WEP104,
IE_RSN_CIPHER_SUITE_WEP40,
IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER,
};
unsigned int pos = 0;
unsigned int i;
uint8_t *countptr;
uint16_t count;
enum ie_rsn_akm_suite akm_suite;
/* Group Data Cipher Suite */
if (!ie_build_cipher_suite(to + pos, ieee_oui, info->group_cipher))
return -EINVAL;
pos += 4;
/* Save position for Pairwise Cipher Suite Count field */
countptr = to + pos;
pos += 2;
for (i = 0, count = 0; i < L_ARRAY_SIZE(pairwise_suites); i++) {
enum ie_rsn_cipher_suite suite = pairwise_suites[i];
if (!(info->pairwise_ciphers & suite))
continue;
if (pos + 4 > max_len)
return -EBADMSG;
if (!ie_build_cipher_suite(to + pos, ieee_oui, suite))
return -EINVAL;
pos += 4;
count += 1;
}
l_put_le16(count, countptr);
/* Save position for AKM Suite Count field */
countptr = to + pos;
pos += 2;
akm_suite = IE_RSN_AKM_SUITE_8021X;
count = 0;
for (count = 0, akm_suite = IE_RSN_AKM_SUITE_8021X;
akm_suite <= IE_RSN_AKM_SUITE_OSEN;
akm_suite <<= 1) {
if (!(info->akm_suites & akm_suite))
continue;
if (pos + 4 > max_len)
return -EBADMSG;
if (!ie_build_rsn_akm_suite(to + pos, akm_suite))
return -EINVAL;
pos += 4;
count += 1;
}
l_put_le16(count, countptr);
/* Bits 0 - 7 of RSNE Capabilities field */
to[pos] = 0;
if (info->preauthentication)
to[pos] |= 0x1;
if (info->no_pairwise)
to[pos] |= 0x2;
to[pos] |= info->ptksa_replay_counter << 2;
to[pos] |= info->gtksa_replay_counter << 4;
if (info->mfpr)
to[pos] |= 0x40;
if (info->mfpc)
to[pos] |= 0x80;
pos += 1;
/* Bits 8 - 15 of RSNE Capabilities field */
to[pos] = 0;
if (info->peerkey_enabled)
to[pos] |= 0x2;
if (info->spp_a_msdu_capable)
to[pos] |= 0x4;
if (info->spp_a_msdu_required)
to[pos] |= 0x8;
if (info->pbac)
to[pos] |= 0x10;
if (info->extended_key_id)
to[pos] |= 0x20;
pos += 1;
/* Short hand the generated RSNE if possible */
if (info->num_pmkids == 0 && !force_group_mgmt_cipher) {
/* No Group Management Cipher Suite */
if (to[pos - 2] == 0 && to[pos - 1] == 0) {
pos -= 2;
goto done;
} else if (!info->mfpc)
goto done;
else if (info->group_management_cipher ==
IE_RSN_CIPHER_SUITE_BIP)
goto done;
}
/* PMKID Count */
l_put_le16(info->num_pmkids, to + pos);
pos += 2;
if (pos + info->num_pmkids * 16 > max_len)
return -EINVAL;
/* PMKID List */
if (info->num_pmkids) {
memcpy(to + pos, info->pmkids, 16 * info->num_pmkids);
pos += 16 * info->num_pmkids;
}
if (!force_group_mgmt_cipher && !info->mfpc)
goto done;
if (!force_group_mgmt_cipher && info->group_management_cipher ==
IE_RSN_CIPHER_SUITE_BIP)
goto done;
/* Group Management Cipher Suite */
if (!ie_build_cipher_suite(to + pos, ieee_oui,
info->group_management_cipher))
return -EINVAL;
pos += 4;
done:
return pos;
}
/*
* Generate an RSNE IE based on the information found in info.
* The to array must be 256 bytes in size
*
* In theory it is possible to generate 257 byte IE RSNs (1 byte for IE Type,
* 1 byte for Length and 255 bytes of data) but we don't support this
* possibility.
*/
bool ie_build_rsne(const struct ie_rsn_info *info, uint8_t *to)
{
unsigned int pos;
int ret;
to[0] = IE_TYPE_RSN;
/* Version field, always 1 */
pos = 2;
l_put_le16(1, to + pos);
pos += 2;
ret = build_ciphers_common(info, to + 4, 252, false);
if (ret < 0)
return false;
pos += ret;
to[1] = pos - 2;
return true;
}
bool ie_build_osen(const struct ie_rsn_info *info, uint8_t *to)
{
unsigned int pos;
int ret;
to[0] = IE_TYPE_VENDOR_SPECIFIC;
pos = 2;
memcpy(to + pos, wifi_alliance_oui, 3);
pos += 3;
to[pos++] = 0x12;
ret = build_ciphers_common(info, to + 6, 250, true);
if (ret < 0)
return false;
pos += ret;
to[1] = pos - 2;
return true;
}
/* 802.11i-2004, Section 7.3.2.25.1 and WPA_80211_v3_1 Section 2.1 */
static bool ie_parse_wpa_cipher_suite(const uint8_t *data,
enum ie_rsn_cipher_suite *out)
{
/*
* Compare the OUI to the ones we know. OUI Format is found in
* Figure 8-187 of 802.11
*/
if (!memcmp(data, microsoft_oui, 3)) {
/* Suite type from 802.11i-2004, Table 20da */
switch (data[3]) {
case 0:
*out = IE_RSN_CIPHER_SUITE_USE_GROUP_CIPHER;
return true;
case 1:
*out = IE_RSN_CIPHER_SUITE_WEP40;
return true;
case 2:
*out = IE_RSN_CIPHER_SUITE_TKIP;
return true;
case 4:
*out = IE_RSN_CIPHER_SUITE_CCMP;
return true;
case 5:
*out = IE_RSN_CIPHER_SUITE_WEP104;
return true;
default:
return false;
}
}
return false;
}
/* 802.11i-2004, Section 7.3.2.25.2 and WPA_80211_v3_1 Section 2.1 */
static bool ie_parse_wpa_akm_suite(const uint8_t *data,
enum ie_rsn_akm_suite *out)
{
/*
* Compare the OUI to the ones we know. OUI Format is found in
* Figure 8-187 of 802.11
*/
if (!memcmp(data, microsoft_oui, 3)) {
/* Suite type from 802.11i-2004, Table 20dc */
switch (data[3]) {
case 1:
*out = IE_RSN_AKM_SUITE_8021X;
return true;
case 2:
*out = IE_RSN_AKM_SUITE_PSK;
return true;
default:
return false;
}
}
return false;
}
static bool ie_parse_wpa_group_cipher(const uint8_t *data,
enum ie_rsn_cipher_suite *out)
{
enum ie_rsn_cipher_suite tmp;
bool r = ie_parse_wpa_cipher_suite(data, &tmp);
if (!r)
return r;
switch (tmp) {
case IE_RSN_CIPHER_SUITE_CCMP:
case IE_RSN_CIPHER_SUITE_TKIP:
case IE_RSN_CIPHER_SUITE_WEP104:
case IE_RSN_CIPHER_SUITE_WEP40:
break;
default:
return false;
}
*out = tmp;
return true;
}
static bool ie_parse_wpa_pairwise_cipher(const uint8_t *data,
enum ie_rsn_cipher_suite *out)
{
enum ie_rsn_cipher_suite tmp;
bool r = ie_parse_wpa_cipher_suite(data, &tmp);
if (!r)
return r;
switch (tmp) {
case IE_RSN_CIPHER_SUITE_CCMP:
case IE_RSN_CIPHER_SUITE_TKIP:
case IE_RSN_CIPHER_SUITE_WEP104:
case IE_RSN_CIPHER_SUITE_WEP40:
/* TODO : not sure about GROUP_CIPHER */
break;
default:
return false;
}
*out = tmp;
return true;
}
bool is_ie_wfa_ie(const uint8_t *data, uint8_t len, uint8_t oi_type)
{
if (!data)
return false;
if (oi_type == IE_WFA_OI_OSEN && len < 22)
return false;
else if (oi_type == IE_WFA_OI_HS20_INDICATION && len < 5)
return false;
else if (len < 4) /* OI not handled, but at least check length */
return false;
if (!memcmp(data, wifi_alliance_oui, 3) && data[3] == oi_type)
return true;
return false;
}
bool is_ie_wpa_ie(const uint8_t *data, uint8_t len)
{
if (!data || len < 6)
return false;
if ((!memcmp(data, microsoft_oui, 3) && data[3] == 1 &&
l_get_le16(data + 4) == 1))
return true;
return false;
}
int ie_parse_wpa(struct ie_tlv_iter *iter, struct ie_rsn_info *out_info)
{
const uint8_t *data = iter->data;
size_t len = iter->len;
struct ie_rsn_info info;
uint16_t count;
uint16_t i;
if (!is_ie_wpa_ie(iter->data, iter->len))
return -EINVAL;
info.group_cipher = IE_RSN_CIPHER_SUITE_TKIP;
info.pairwise_ciphers = IE_RSN_CIPHER_SUITE_TKIP;
info.akm_suites = IE_RSN_AKM_SUITE_PSK;
memset(&info, 0, sizeof(info));
RSNE_ADVANCE(data, len, 6);
/* Parse Group Cipher Suite field */
if (len < 4)
return -EBADMSG;
if (!ie_parse_wpa_group_cipher(data, &info.group_cipher))
return -ERANGE;
RSNE_ADVANCE(data, len, 4);
/* Parse Pairwise Cipher Suite Count field */
if (len < 2)
return -EBADMSG;
count = l_get_le16(data);
/*
* The spec doesn't seem to explicitly say what to do in this case,
* so we assume this situation is invalid.
*/
if (count == 0)
return -EINVAL;
data += 2;
len -= 2;
if (len < 4 * count)
return -EBADMSG;
/* Parse Pairwise Cipher Suite List field */
for (i = 0, info.pairwise_ciphers = 0; i < count; i++) {
enum ie_rsn_cipher_suite suite;
if (!ie_parse_wpa_pairwise_cipher(data + i * 4, &suite))
return -ERANGE;
info.pairwise_ciphers |= suite;
}
RSNE_ADVANCE(data, len, count * 4);
/* Parse AKM Suite Count field */
if (len < 2)
return -EBADMSG;
count = l_get_le16(data);
if (count == 0)
return -EINVAL;
data += 2;
len -= 2;
if (len < 4 * count)
return -EBADMSG;
/* Parse AKM Suite List field */
for (i = 0, info.akm_suites = 0; i < count; i++) {
enum ie_rsn_akm_suite suite;
if (!ie_parse_wpa_akm_suite(data + i * 4, &suite))
return -ERANGE;
info.akm_suites |= suite;
}
RSNE_ADVANCE(data, len, count * 4);
return -EBADMSG;
done:
/*
* 802.11i, Section 7.3.2.25.1
* Use of CCMP as the group cipher suite with TKIP as the
* pairwise cipher suite shall not be supported.
*/
if (info.group_cipher & IE_RSN_CIPHER_SUITE_CCMP &&
info.pairwise_ciphers & IE_RSN_CIPHER_SUITE_TKIP)
return -EBADMSG;
if (out_info)
memcpy(out_info, &info, sizeof(info));
return 0;
}
int ie_parse_wpa_from_data(const uint8_t *data, size_t len,
struct ie_rsn_info *info)
{
struct ie_tlv_iter iter;
ie_tlv_iter_init(&iter, data, len);
if (!ie_tlv_iter_next(&iter))
return -EMSGSIZE;
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_VENDOR_SPECIFIC)
return -EPROTOTYPE;
return ie_parse_wpa(&iter, info);
}
/*
* Generate an WPA IE based on the information found in info.
* The to array must be minimum of 19 bytes in size
*/
bool ie_build_wpa(const struct ie_rsn_info *info, uint8_t *to)
{
/* These are the only valid pairwise suites */
static enum ie_rsn_cipher_suite pairwise_suites[] = {
IE_RSN_CIPHER_SUITE_CCMP,
IE_RSN_CIPHER_SUITE_TKIP,
IE_RSN_CIPHER_SUITE_WEP104,
IE_RSN_CIPHER_SUITE_WEP40,
/* TODO: not sure about USE_GROUP_CIPHER,*/
};
/* These are the only valid AKM suites */
static enum ie_rsn_akm_suite akm_suites[] = {
IE_RSN_AKM_SUITE_8021X,
IE_RSN_AKM_SUITE_PSK,
};
unsigned int pos;
unsigned int i;
uint8_t *countptr;
uint16_t count;
/*
* 802.11i, Section 7.3.2.25.1
* Use of CCMP as the group cipher suite with TKIP as the
* pairwise cipher suite shall not be supported.
*/
if (info->group_cipher == IE_RSN_CIPHER_SUITE_CCMP &&
info->pairwise_ciphers & IE_RSN_CIPHER_SUITE_TKIP)
return false;
to[0] = IE_TYPE_VENDOR_SPECIFIC;
/* Vendor OUI and Type */
pos = 2;
memcpy(to + pos, microsoft_oui, 3);
pos += 3;
to[pos] = 1; /* OUI type 1 means WPA element */
pos++;
/* Version field, always 1 */
l_put_le16(1, to + pos);
pos += 2;
/* Group Data Cipher Suite */
if (!ie_build_cipher_suite(to + pos, microsoft_oui,
info->group_cipher))
return false;
pos += 4;
/* Save position for Pairwise Cipher Suite Count field */
countptr = to + pos;
pos += 2;
for (i = 0, count = 0; i < L_ARRAY_SIZE(pairwise_suites); i++) {
enum ie_rsn_cipher_suite suite = pairwise_suites[i];
if (!(info->pairwise_ciphers & suite))
continue;
if (!ie_build_cipher_suite(to + pos, microsoft_oui, suite))
return false;
pos += 4;
count += 1;
}
l_put_le16(count, countptr);
/* Save position for AKM Suite Count field */
countptr = to + pos;
pos += 2;
for (i = 0, count = 0; i < L_ARRAY_SIZE(akm_suites); i++) {
enum ie_rsn_akm_suite suite = akm_suites[i];
if (!(info->akm_suites & suite))
continue;
if (!ie_build_wpa_akm_suite(to + pos, suite))
return false;
pos += 4;
count += 1;
}
l_put_le16(count, countptr);
to[1] = pos - 2;
return true;
}
int ie_parse_bss_load(struct ie_tlv_iter *iter, uint16_t *out_sta_count,
uint8_t *out_channel_utilization,
uint16_t *out_admission_capacity)
{
const uint8_t *data;
if (ie_tlv_iter_get_length(iter) != 5)
return -EINVAL;
data = ie_tlv_iter_get_data(iter);
if (out_sta_count)
*out_sta_count = data[0] | data[1] << 8;
if (out_channel_utilization)
*out_channel_utilization = data[2];
if (out_admission_capacity)
*out_admission_capacity = data[3] | data[4] << 8;
return 0;
}
int ie_parse_bss_load_from_data(const uint8_t *data, uint8_t len,
uint16_t *out_sta_count,
uint8_t *out_channel_utilization,
uint16_t *out_admission_capacity)
{
struct ie_tlv_iter iter;
ie_tlv_iter_init(&iter, data, len);
if (!ie_tlv_iter_next(&iter))
return -EMSGSIZE;
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_BSS_LOAD)
return -EPROTOTYPE;
return ie_parse_bss_load(&iter, out_sta_count,
out_channel_utilization, out_admission_capacity);
}
/*
* We have to store this mapping since basic rates dont come with a convenient
* MCS index. Rates are stored as they are encoded in the Supported Rates IE.
* This does not include non 802.11g data rates, e.g. 1/2/4Mbps. This data was
* taken from 802.11 Section 17.3.10.2 and Table 10-7.
*
* Section 17.3.10.2 defines minimum RSSI for modulations, and Table
* 10-7 defines reference rates for the different modulations. Together we
* have minimum RSSI required for a given data rate.
*/
struct basic_rate_map {
int32_t rssi;
uint8_t rate;
};
/*
* Rates are stored in 500Kbps increments. This is how the IE encodes the data
* so its more convenient to match by this encoding. The actual data rate is
* converted to Mbps after we find a match
*/
static const struct basic_rate_map rate_rssi_map[] = {
{ -82, 12 },
{ -81, 18 },
{ -79, 24 },
{ -77, 36 },
{ -74, 48 },
{ -70, 72 },
{ -66, 96 },
{ -65, 108 },
};
static int ie_parse_supported_rates(struct ie_tlv_iter *supp_rates_iter,
struct ie_tlv_iter *ext_supp_rates_iter,
int32_t rssi,
uint64_t *data_rate)
{
uint8_t max_rate = 0;
uint8_t highest = 0;
const uint8_t *rates;
unsigned int len;
unsigned int i;
len = ie_tlv_iter_get_length(supp_rates_iter);
if (len == 0)
return -EINVAL;
/* Find highest rates possible with our RSSI */
for (i = 0; i < L_ARRAY_SIZE(rate_rssi_map); i++) {
const struct basic_rate_map *map = &rate_rssi_map[i];
if (rssi < map->rssi)
break;
max_rate = map->rate;
}
/* Find highest rate in Supported Rates IE */
rates = ie_tlv_iter_get_data(supp_rates_iter);
for (i = 0; i < len; i++) {
uint8_t r = rates[i] & 0x7f;
if (r <= max_rate && r > highest)
highest = r;
}
/* Find highest rate in Extended Supported Rates IE */
if (ext_supp_rates_iter) {
len = ie_tlv_iter_get_length(ext_supp_rates_iter);
rates = ie_tlv_iter_get_data(ext_supp_rates_iter);
for (i = 0; i < len; i++) {
uint8_t r = rates[i] & 0x7f;
if (r <= max_rate && r > highest)
highest = r;
}
}
*data_rate = (highest / 2) * 1000000;
return 0;
}
int ie_parse_supported_rates_from_data(const uint8_t *supp_rates_ie,
uint8_t supp_rates_len,
const uint8_t *ext_supp_rates_ie,
uint8_t ext_supp_rates_len,
int32_t rssi, uint64_t *data_rate)
{
struct ie_tlv_iter supp_rates_iter;
struct ie_tlv_iter ext_supp_rates_iter;
if (supp_rates_ie) {
ie_tlv_iter_init(&supp_rates_iter, supp_rates_ie,
supp_rates_len);
if (!ie_tlv_iter_next(&supp_rates_iter))
return -EMSGSIZE;
if (ie_tlv_iter_get_tag(&supp_rates_iter) !=
IE_TYPE_SUPPORTED_RATES)
return -EPROTOTYPE;
}
if (ext_supp_rates_ie) {
ie_tlv_iter_init(&ext_supp_rates_iter, ext_supp_rates_ie,
ext_supp_rates_len);
if (!ie_tlv_iter_next(&ext_supp_rates_iter))
return -EMSGSIZE;
if (ie_tlv_iter_get_tag(&ext_supp_rates_iter) !=
IE_TYPE_EXTENDED_SUPPORTED_RATES)
return -EPROTOTYPE;
}
return ie_parse_supported_rates(
(supp_rates_ie) ? &supp_rates_iter : NULL,
(ext_supp_rates_ie) ? &ext_supp_rates_iter : NULL,
rssi, data_rate);
}
enum ht_vht_channel_width {
HT_VHT_CHANNEL_WIDTH_20MHZ = 0,
HT_VHT_CHANNEL_WIDTH_40MHZ,
HT_VHT_CHANNEL_WIDTH_80MHZ,
HT_VHT_CHANNEL_WIDTH_160MHZ,
};
/*
* Base RSSI values for 20MHz (both HT and VHT) channel. These values can be
* used to calculate the minimum RSSI values for all other channel widths. HT
* MCS indexes are grouped into ranges of 8 (per spatial stream) where VHT are
* grouped in chunks of 10. This just means HT will not use the last two
* index's of this array.
*/
static const int32_t ht_vht_base_rssi[] = {
-82, -79, -77, -74, -70, -66, -65, -64, -59, -57
};
struct ht_vht_rate {
uint64_t rate;
uint64_t sgi_rate;
};
static const struct ht_vht_rate ht_vht_rates[] = {
[HT_VHT_CHANNEL_WIDTH_20MHZ] = { .rate = 6500000,
.sgi_rate = 7200000 },
[HT_VHT_CHANNEL_WIDTH_40MHZ] = { .rate = 13500000,
.sgi_rate = 15000000 },
[HT_VHT_CHANNEL_WIDTH_80MHZ] = { .rate = 29300000,
.sgi_rate = 32500000 },
[HT_VHT_CHANNEL_WIDTH_160MHZ] = { .rate = 58500000,
.sgi_rate = 65000000 },
};
/*
* Both HT and VHT rates are calculated in the same fashion. The only difference
* is a relative MCS index is used for HT since, for each NSS, the formula
* is the same with relative index's. This is why this is called with index % 8
* for HT, but not VHT.
*/
static bool calculate_ht_vht_data_rate(uint8_t index,
enum ht_vht_channel_width width,
int32_t rssi, uint8_t nss, bool sgi,
uint64_t *data_rate)
{
const struct ht_vht_rate *rate = &ht_vht_rates[width];
int32_t width_adjust = width * 3;
if (rssi < ht_vht_base_rssi[index] + width_adjust)
return false;
if (sgi)
*data_rate = rate->sgi_rate;
else
*data_rate = rate->rate;
/* adjust base for spatial streams */
*data_rate *= nss;
/*
* As with HT, the VHT rates multiplier jumps up
* by 2 after MCS index 4
*/
if (index < 4)
*data_rate *= index + 1;
else
*data_rate *= index + 3;
return true;
}
static int ie_parse_ht_capability(struct ie_tlv_iter *iter, int32_t rssi,
uint64_t *data_rate)
{
unsigned int len;
const uint8_t *data;
uint8_t ht_cap;
int i;
uint64_t highest_rate = 0;
bool support_40mhz;
bool short_gi_20mhz;
bool short_gi_40mhz;
len = ie_tlv_iter_get_length(iter);
if (len < 26)
return -EINVAL;
if (ie_tlv_iter_get_tag(iter) != IE_TYPE_HT_CAPABILITIES)
return -EINVAL;
data = ie_tlv_iter_get_data(iter);
/* Parse out channel width set and short GI */
ht_cap = l_get_u8(data++);
support_40mhz = util_is_bit_set(ht_cap, 1);
short_gi_20mhz = util_is_bit_set(ht_cap, 5);
short_gi_40mhz = util_is_bit_set(ht_cap, 6);
data += 2;
/*
* TODO: Support MCS values 32 - 76
*
* The MCS values > 31 do not follow the same pattern since they use
* unequal modulation per spatial stream. These higher MCS values
* actually don't follow a pattern at all, since each stream can have a
* different modulation a higher MCS value does not mean higher
* throughput. For this reason these MCS indexes are left out.
*/
for (i = 31; i >= 0; i--) {
uint64_t drate;
uint8_t byte = i / 8;
uint8_t bit = i % 8;
if (!util_is_bit_set(data[byte], bit))
continue;
if (!support_40mhz)
goto check_20;
if (calculate_ht_vht_data_rate(i % 8,
HT_VHT_CHANNEL_WIDTH_40MHZ,
rssi, (i / 8) + 1,
short_gi_40mhz, &drate)) {
*data_rate = drate;
return 0;
}
check_20:
if (!calculate_ht_vht_data_rate(i % 8,
HT_VHT_CHANNEL_WIDTH_20MHZ,
rssi, (i / 8) + 1,
short_gi_20mhz, &drate))
continue;
if (!support_40mhz) {
*data_rate = drate;
return 0;
}
if (drate > highest_rate)
highest_rate = drate;
}
if (!highest_rate)
return -ENOTSUP;
*data_rate = highest_rate;
return 0;
}
static int ie_parse_ht_capability_from_data(const uint8_t *data, uint8_t len,
int32_t rssi, uint64_t *data_rate)
{
struct ie_tlv_iter iter;
uint8_t tag;
ie_tlv_iter_init(&iter, data, len);
if (!ie_tlv_iter_next(&iter))
return -EMSGSIZE;
tag = ie_tlv_iter_get_tag(&iter);
if (tag != IE_TYPE_HT_CAPABILITIES)
return -EPROTOTYPE;
return ie_parse_ht_capability(&iter, rssi, data_rate);
}
/*
* IEEE 802.11 - Table 9-250
*
* For simplicity, we are ignoring the Extended BSS BW support, per NOTE 11:
*
* NOTE 11—A receiving STA in which dot11VHTExtendedNSSCapable is false will
* ignore the Extended NSS BW Support subfield and effectively evaluate this
* table only at the entries where Extended NSS BW Support is 0.
*
* This also allows us to group the 160/80+80 widths together, since they are
* the same when Extended NSS BW is zero.
*/
static const uint8_t vht_width_map[3][4] = {
[0] = { 1, 1, 1, 0 },
[1] = { 1, 1, 1, 1 },
[2] = { 1, 1, 1, 1 },
};
static int ie_parse_vht_capability(struct ie_tlv_iter *vht_iter,
struct ie_tlv_iter *ht_iter, int32_t rssi,
uint64_t *data_rate)
{
int width;
int mcs;
unsigned int nss;
unsigned int len;
const uint8_t *data;
uint8_t channel_width_set;
uint8_t rx_mcs_map[2];
uint8_t tx_mcs_map[2];
unsigned int max_rx_mcs = 0;
unsigned int rx_nss;
unsigned int max_tx_mcs = 0;
unsigned int tx_nss;
uint8_t ht_cap;
bool short_gi_20mhz;
bool short_gi_40mhz;
bool short_gi_80mhz;
bool short_gi_160mhz;
uint64_t highest_rate = 0;
/* grab the short GI bits from the HT IE */
len = ie_tlv_iter_get_length(ht_iter);
if (len != 26)
return -EINVAL;
data = ie_tlv_iter_get_data(ht_iter);
ht_cap = l_get_u8(data);
short_gi_20mhz = util_is_bit_set(ht_cap, 5);
short_gi_40mhz = util_is_bit_set(ht_cap, 6);
/* now move onto VHT */
len = ie_tlv_iter_get_length(vht_iter);
if (len != 12)
return -EINVAL;
data = ie_tlv_iter_get_data(vht_iter);
channel_width_set = util_bit_field(*data, 2, 2);
short_gi_80mhz = util_bit_field(*data, 5, 1);
short_gi_160mhz = util_bit_field(*data, 6, 1);
data += 4;
rx_mcs_map[0] = *data++;
rx_mcs_map[1] = *data++;
data += 2;
tx_mcs_map[0] = *data++;
tx_mcs_map[1] = *data++;
/* NSS->MCS map values are grouped in 2-bit values */
for (mcs = 15; mcs >= 0; mcs -= 2) {
uint8_t rx_val = util_bit_field(rx_mcs_map[mcs / 8],
mcs % 8, 2);
uint8_t tx_val = util_bit_field(tx_mcs_map[mcs / 8],
mcs % 8, 2);
/*
* 0 indicates support for MCS 0-7
* 1 indicates support for MCS 0-8
* 2 indicates support for MCS 0-9
*
* Therefore 7 + rx/tx_val gives us our max MCS index.
*/
if (!max_rx_mcs && rx_val < 3) {
max_rx_mcs = 7 + rx_val;
rx_nss = (mcs / 2) + 1;
}
if (!max_tx_mcs && tx_val < 3) {
max_tx_mcs = 7 + tx_val;
tx_nss = (mcs / 2) + 1;
}
if (max_rx_mcs && max_tx_mcs)
break;
}
if (!max_rx_mcs && !max_tx_mcs)
return -EINVAL;
/*
* Now, using channel width, MCS index, and NSS we can determine the
* theoretical maximum data rate. We iterate through all possible
* combinations (width, MCS, NSS), saving the highest data rate we find.
*
* We could calculate a maximum data rate separately for TX/RX, but
* since this is only used for BSS ranking, the minumum between the
* two should be good enough.
*/
for (width = sizeof(vht_width_map[0]) - 1; width >= 0; width--) {
bool sgi = false;
if (!vht_width_map[channel_width_set][width])
continue;
/*
* Consolidate short GI support into a single boolean, dependent
* on the channel width for this iteration.
*/
switch (width) {
case HT_VHT_CHANNEL_WIDTH_20MHZ:
sgi = short_gi_20mhz;
break;
case HT_VHT_CHANNEL_WIDTH_40MHZ:
sgi = short_gi_40mhz;
break;
case HT_VHT_CHANNEL_WIDTH_80MHZ:
sgi = short_gi_80mhz;
break;
case HT_VHT_CHANNEL_WIDTH_160MHZ:
sgi = short_gi_160mhz;
break;
}
for (nss = minsize(rx_nss, tx_nss); nss > 0; nss--) {
/* NSS > 4 does not apply to 20/40MHz */
if (width <= HT_VHT_CHANNEL_WIDTH_40MHZ && nss > 4)
continue;
for (mcs = minsize(max_rx_mcs, max_tx_mcs);
mcs >= 0; mcs--) {
uint64_t drate;
if (!calculate_ht_vht_data_rate(mcs, width,
rssi, nss, sgi, &drate))
continue;
if (drate > highest_rate)
highest_rate = drate;
/* Lower MCS index will only have lower rates */
goto next_chanwidth;
}
}
next_chanwidth: ; /* empty statement */
}
if (highest_rate == 0)
return -ENOTSUP;
*data_rate = highest_rate;
return 0;
}
static int ie_parse_vht_capability_from_data(const uint8_t *vht_ie,
size_t vht_len, const uint8_t *ht_ie,
size_t ht_len, int32_t rssi,
uint64_t *data_rate)
{
struct ie_tlv_iter vht_iter;
struct ie_tlv_iter ht_iter;
uint8_t tag;
ie_tlv_iter_init(&vht_iter, vht_ie, vht_len);
if (!ie_tlv_iter_next(&vht_iter))
return -EMSGSIZE;
tag = ie_tlv_iter_get_tag(&vht_iter);
if (tag != IE_TYPE_VHT_CAPABILITIES)
return -EPROTOTYPE;
ie_tlv_iter_init(&ht_iter, ht_ie, ht_len);
if (!ie_tlv_iter_next(&ht_iter))
return -EMSGSIZE;
tag = ie_tlv_iter_get_tag(&ht_iter);
if (tag != IE_TYPE_HT_CAPABILITIES)
return -EPROTOTYPE;
return ie_parse_vht_capability(&vht_iter, &ht_iter, rssi, data_rate);
}
/*
* Calculates the theoretical maximum data rates out of the provided
* supported rates IE, HT IE, and VHT IE. All 3 parsing functions are allowed
* to return -ENOTSUP, which indicates that a data rate was not found given
* the provided data. This is not fatal, it most likely means our RSSI was too
* low.
*/
int ie_parse_data_rates(const uint8_t *supp_rates_ie,
const uint8_t *ext_supp_rates_ie,
const uint8_t *ht_ie,
const uint8_t *vht_ie,
int32_t rssi,
uint64_t *data_rate)
{
int ret = -ENOTSUP;
uint64_t rate = 0;
/* An RSSI this low will not yield any rate results */
if (rssi < -82)
return -ENOTSUP;
if (ht_ie && vht_ie) {
ret = ie_parse_vht_capability_from_data(vht_ie, IE_LEN(vht_ie),
ht_ie, IE_LEN(ht_ie),
rssi, &rate);
if (ret == 0)
goto done;
}
if (ht_ie) {
ret = ie_parse_ht_capability_from_data(ht_ie, IE_LEN(ht_ie),
rssi, &rate);
if (ret == 0)
goto done;
}
if (supp_rates_ie || ext_supp_rates_ie) {
ret = ie_parse_supported_rates_from_data(supp_rates_ie,
IE_LEN(supp_rates_ie),
ext_supp_rates_ie,
IE_LEN(supp_rates_ie),
rssi, &rate);
if (ret == 0)
goto done;
}
return ret;
done:
*data_rate = rate;
return 0;
}
int ie_parse_mobility_domain(struct ie_tlv_iter *iter, uint16_t *mdid,
bool *ft_over_ds, bool *resource_req)
{
const uint8_t *data;
if (ie_tlv_iter_get_length(iter) != 3)
return -EINVAL;
data = ie_tlv_iter_get_data(iter);
if (mdid)
*mdid = l_get_le16(data);
if (ft_over_ds)
*ft_over_ds = (data[2] & 0x01) > 0;
if (resource_req)
*resource_req = (data[2] & 0x02) > 0;
return 0;
}
int ie_parse_mobility_domain_from_data(const uint8_t *data, uint8_t len,
uint16_t *mdid,
bool *ft_over_ds, bool *resource_req)
{
struct ie_tlv_iter iter;
ie_tlv_iter_init(&iter, data, len);
if (!ie_tlv_iter_next(&iter))
return -EMSGSIZE;
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_MOBILITY_DOMAIN)
return -EPROTOTYPE;
return ie_parse_mobility_domain(&iter, mdid, ft_over_ds, resource_req);
}
bool ie_build_mobility_domain(uint16_t mdid, bool ft_over_ds, bool resource_req,
uint8_t *to)
{
*to++ = IE_TYPE_MOBILITY_DOMAIN;
*to++ = 3;
l_put_le16(mdid, to);
to[2] =
(ft_over_ds ? 0x01 : 0) |
(resource_req ? 0x02 : 0);
return true;
}
int ie_parse_fast_bss_transition(struct ie_tlv_iter *iter, uint32_t mic_len,
struct ie_ft_info *info)
{
const uint8_t *data;
uint8_t len, subelem_id, subelem_len;
len = ie_tlv_iter_get_length(iter);
if (len < 66 + mic_len)
return -EINVAL;
data = ie_tlv_iter_get_data(iter);
memset(info, 0, sizeof(*info));
info->mic_element_count = data[1];
memcpy(info->mic, data + 2, mic_len);
memcpy(info->anonce, data + mic_len + 2, 32);
memcpy(info->snonce, data + mic_len + 34, 32);
len -= 66 + mic_len;
data += 66 + mic_len;
while (len >= 2) {
subelem_id = *data++;
subelem_len = *data++;
switch (subelem_id) {
case 1:
if (subelem_len != 6)
return -EINVAL;
memcpy(info->r1khid, data, 6);
info->r1khid_present = true;
break;
case 2:
if (subelem_len < 35 || subelem_len > 51)
return -EINVAL;
info->gtk_key_id = util_bit_field(data[0], 0, 2);
info->gtk_len = data[2];
/*
* Check Wrapped Key field length is Key Length plus
* padding (0 - 7 bytes) plus 8 bytes for AES key wrap.
*/
if (align_len(info->gtk_len, 8) + 8 != subelem_len - 11)
return -EINVAL;
memcpy(info->gtk_rsc, data + 3, 8);
memcpy(info->gtk, data + 11, subelem_len - 11);
break;
case 3:
if (subelem_len < 1 || subelem_len > 48)
return -EINVAL;
memcpy(info->r0khid, data, subelem_len);
info->r0khid_len = subelem_len;
break;
case 4:
if (subelem_len != 33)
return -EINVAL;
info->igtk_key_id = l_get_le16(data);
memcpy(info->igtk_ipn, data + 2, 6);
info->igtk_len = data[8];
if (info->igtk_len > 16)
return -EINVAL;
memcpy(info->igtk, data + 9, subelem_len - 9);
break;
}
data += subelem_len;
len -= subelem_len + 2;
}
if (len)
return -EINVAL;
return 0;
}
int ie_parse_fast_bss_transition_from_data(const uint8_t *data, uint8_t len,
uint32_t mic_len,
struct ie_ft_info *info)
{
struct ie_tlv_iter iter;
ie_tlv_iter_init(&iter, data, len);
if (!ie_tlv_iter_next(&iter))
return -EMSGSIZE;
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_FAST_BSS_TRANSITION)
return -EPROTOTYPE;
return ie_parse_fast_bss_transition(&iter, mic_len, info);
}
bool ie_build_fast_bss_transition(const struct ie_ft_info *info,
uint32_t mic_len, uint8_t *to)
{
uint8_t *len;
*to++ = IE_TYPE_FAST_BSS_TRANSITION;
len = to++;
*len = (mic_len == 16) ? 82 : 90;
to[0] = 0x00;
to[1] = info->mic_element_count;
memcpy(to + 2, info->mic, mic_len);
memcpy(to + mic_len + 2, info->anonce, 32);
memcpy(to + mic_len + 34, info->snonce, 32);
to += (mic_len == 16) ? 82 : 90;
if (info->r1khid_present) {
to[0] = 1;
to[1] = 6;
memcpy(to + 2, info->r1khid, 6);
to += 8;
*len += 8;
}
L_WARN_ON(info->gtk_len); /* Not implemented */
if (info->r0khid_len) {
to[0] = 3;
to[1] = info->r0khid_len;
memcpy(to + 2, info->r0khid, info->r0khid_len);
to += 2 + info->r0khid_len;
*len += 2 + info->r0khid_len;
}
L_WARN_ON(info->igtk_len); /* Not implemented */
return true;
}
enum nr_subelem_id {
NR_SUBELEM_ID_TSF_INFO = 1,
NR_SUBELEM_ID_CONDENSED_COUNTRY_STR = 2,
NR_SUBELEM_ID_BSS_TRANSITION_PREF = 3,
NR_SUBELEM_ID_BSS_TERMINATION_DURATION = 4,
NR_SUBELEM_ID_BEARING = 5,
NR_SUBELEM_ID_WIDE_BW_CHANNEL = 6,
/* Remaining defined subelements use the IE_TYPE_* ID values */
};
int ie_parse_neighbor_report(struct ie_tlv_iter *iter,
struct ie_neighbor_report_info *info)
{
unsigned int len = ie_tlv_iter_get_length(iter);
const uint8_t *data = ie_tlv_iter_get_data(iter);
struct ie_tlv_iter opt_iter;
if (len < 13)
return -EINVAL;
memset(info, 0, sizeof(*info));
memcpy(info->addr, data + 0, 6);
info->ht = util_is_bit_set(data[8], 3);
info->md = util_is_bit_set(data[8], 2);
info->immediate_block_ack = util_is_bit_set(data[8], 1);
info->delayed_block_ack = util_is_bit_set(data[8], 0);
info->rm = util_is_bit_set(data[9], 7);
info->apsd = util_is_bit_set(data[9], 6);
info->qos = util_is_bit_set(data[9], 5);
info->spectrum_mgmt = util_is_bit_set(data[9], 4);
info->key_scope = util_is_bit_set(data[9], 3);
info->security = util_is_bit_set(data[9], 2);
info->reachable = util_bit_field(data[9], 0, 2);
info->oper_class = data[10];
info->channel_num = data[11];
info->phy_type = data[12];
ie_tlv_iter_init(&opt_iter, data + 13, len - 13);
while (ie_tlv_iter_next(&opt_iter)) {
if (ie_tlv_iter_get_tag(&opt_iter) !=
NR_SUBELEM_ID_BSS_TRANSITION_PREF)
continue;
if (ie_tlv_iter_get_length(&opt_iter) != 1)
continue;
info->bss_transition_pref = ie_tlv_iter_get_data(&opt_iter)[0];
info->bss_transition_pref_present = true;
}
return 0;
}
int ie_parse_roaming_consortium(struct ie_tlv_iter *iter, size_t *num_anqp_out,
const uint8_t **oi1_out, size_t *oi1_len_out,
const uint8_t **oi2_out, size_t *oi2_len_out,
const uint8_t **oi3_out, size_t *oi3_len_out)
{
unsigned int len = ie_tlv_iter_get_length(iter);
const uint8_t *data = ie_tlv_iter_get_data(iter);
size_t num_anqp;
size_t oi1_len;
size_t oi2_len;
size_t oi3_len;
if (len < 4)
return -EINVAL;
num_anqp = l_get_u8(data);
oi1_len = util_bit_field(l_get_u8(data + 1), 0, 4);
oi2_len = util_bit_field(l_get_u8(data + 1), 4, 4);
oi3_len = len - (2 + oi1_len + oi2_len);
if (!oi1_len)
return -EINVAL;
if (len < oi1_len + oi2_len + oi3_len + 2)
return -EINVAL;
if (num_anqp_out)
*num_anqp_out = num_anqp;
if (oi1_out)
*oi1_out = data + 2;
if (oi1_len_out)
*oi1_len_out = oi1_len;
/* OI2/3 are optional, explicitly set to NULL if not included */
if (oi2_len) {
if (oi2_out)
*oi2_out = data + 2 + oi1_len;
if (oi2_len_out)
*oi2_len_out = oi2_len;
} else if (oi2_out)
*oi2_out = NULL;
if (oi3_len) {
if (oi3_out)
*oi3_out = data + 2 + oi1_len + oi2_len;
if (oi3_len_out)
*oi3_len_out = oi3_len;
} else if (oi3_out)
*oi3_out = NULL;
return 0;
}
int ie_parse_roaming_consortium_from_data(const uint8_t *data, size_t len,
size_t *num_anqp_out, const uint8_t **oi1_out,
size_t *oi1_len_out, const uint8_t **oi2_out,
size_t *oi2_len_out, const uint8_t **oi3_out,
size_t *oi3_len_out)
{
struct ie_tlv_iter iter;
ie_tlv_iter_init(&iter, data, len);
if (!ie_tlv_iter_next(&iter))
return -EMSGSIZE;
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_ROAMING_CONSORTIUM)
return -EPROTOTYPE;
return ie_parse_roaming_consortium(&iter, num_anqp_out, oi1_out,
oi1_len_out, oi2_out,
oi2_len_out, oi3_out,
oi3_len_out);
}
int ie_build_roaming_consortium(const uint8_t *rc, size_t rc_len, uint8_t *to)
{
*to++ = IE_TYPE_VENDOR_SPECIFIC;
*to++ = rc_len + 4;
memcpy(to, wifi_alliance_oui, 3);
to += 3;
*to++ = 0x1d;
memcpy(to, rc, rc_len);
return 0;
}