2016-02-22 06:03:28 -05:00
|
|
|
/*
|
|
|
|
* hostapd - MBO
|
|
|
|
* Copyright (c) 2016, Qualcomm Atheros, Inc.
|
|
|
|
*
|
|
|
|
* 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 "common/ieee802_11_common.h"
|
|
|
|
#include "hostapd.h"
|
|
|
|
#include "sta_info.h"
|
|
|
|
#include "mbo_ap.h"
|
|
|
|
|
|
|
|
|
2016-02-22 13:37:21 -05:00
|
|
|
void mbo_ap_sta_free(struct sta_info *sta)
|
|
|
|
{
|
|
|
|
struct mbo_non_pref_chan_info *info, *prev;
|
|
|
|
|
|
|
|
info = sta->non_pref_chan;
|
|
|
|
sta->non_pref_chan = NULL;
|
|
|
|
while (info) {
|
|
|
|
prev = info;
|
|
|
|
info = info->next;
|
|
|
|
os_free(prev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void mbo_ap_parse_non_pref_chan(struct sta_info *sta,
|
|
|
|
const u8 *buf, size_t len)
|
|
|
|
{
|
|
|
|
struct mbo_non_pref_chan_info *info, *tmp;
|
|
|
|
char channels[200], *pos, *end;
|
|
|
|
size_t num_chan, i;
|
|
|
|
int ret;
|
|
|
|
|
2016-09-23 08:54:13 -04:00
|
|
|
if (len <= 3)
|
2016-02-22 13:37:21 -05:00
|
|
|
return; /* Not enough room for any channels */
|
|
|
|
|
2016-09-23 08:54:13 -04:00
|
|
|
num_chan = len - 3;
|
2016-02-22 13:37:21 -05:00
|
|
|
info = os_zalloc(sizeof(*info) + num_chan);
|
|
|
|
if (!info)
|
|
|
|
return;
|
|
|
|
info->op_class = buf[0];
|
2016-09-23 08:54:13 -04:00
|
|
|
info->pref = buf[len - 2];
|
|
|
|
info->reason_code = buf[len - 1];
|
2016-02-22 13:37:21 -05:00
|
|
|
info->num_channels = num_chan;
|
|
|
|
buf++;
|
|
|
|
os_memcpy(info->channels, buf, num_chan);
|
|
|
|
if (!sta->non_pref_chan) {
|
|
|
|
sta->non_pref_chan = info;
|
|
|
|
} else {
|
|
|
|
tmp = sta->non_pref_chan;
|
|
|
|
while (tmp->next)
|
|
|
|
tmp = tmp->next;
|
|
|
|
tmp->next = info;
|
|
|
|
}
|
|
|
|
|
|
|
|
pos = channels;
|
|
|
|
end = pos + sizeof(channels);
|
|
|
|
*pos = '\0';
|
|
|
|
for (i = 0; i < num_chan; i++) {
|
|
|
|
ret = os_snprintf(pos, end - pos, "%s%u",
|
|
|
|
i == 0 ? "" : " ", buf[i]);
|
|
|
|
if (os_snprintf_error(end - pos, ret)) {
|
|
|
|
*pos = '\0';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pos += ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR
|
2016-09-23 08:54:13 -04:00
|
|
|
" non-preferred channel list (op class %u, pref %u, reason code %u, channels %s)",
|
2016-02-22 13:37:21 -05:00
|
|
|
MAC2STR(sta->addr), info->op_class, info->pref,
|
2016-09-23 08:54:13 -04:00
|
|
|
info->reason_code, channels);
|
2016-02-22 13:37:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-22 06:03:28 -05:00
|
|
|
void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta,
|
|
|
|
struct ieee802_11_elems *elems)
|
|
|
|
{
|
2016-02-22 13:37:21 -05:00
|
|
|
const u8 *pos, *attr, *end;
|
2016-02-22 06:03:28 -05:00
|
|
|
size_t len;
|
|
|
|
|
|
|
|
if (!hapd->conf->mbo_enabled || !elems->mbo)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pos = elems->mbo + 4;
|
|
|
|
len = elems->mbo_len - 4;
|
|
|
|
wpa_hexdump(MSG_DEBUG, "MBO: Association Request attributes", pos, len);
|
|
|
|
|
|
|
|
attr = get_ie(pos, len, MBO_ATTR_ID_CELL_DATA_CAPA);
|
|
|
|
if (attr && attr[1] >= 1)
|
|
|
|
sta->cell_capa = attr[2];
|
2016-02-22 13:37:21 -05:00
|
|
|
|
|
|
|
mbo_ap_sta_free(sta);
|
|
|
|
end = pos + len;
|
|
|
|
while (end - pos > 1) {
|
|
|
|
u8 ie_len = pos[1];
|
|
|
|
|
|
|
|
if (2 + ie_len > end - pos)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (pos[0] == MBO_ATTR_ID_NON_PREF_CHAN_REPORT)
|
|
|
|
mbo_ap_parse_non_pref_chan(sta, pos + 2, ie_len);
|
|
|
|
pos += 2 + pos[1];
|
|
|
|
}
|
2016-02-22 06:03:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen)
|
|
|
|
{
|
2016-02-22 13:37:21 -05:00
|
|
|
char *pos = buf, *end = buf + buflen;
|
2016-02-22 06:03:28 -05:00
|
|
|
int ret;
|
2016-02-22 13:37:21 -05:00
|
|
|
struct mbo_non_pref_chan_info *info;
|
|
|
|
u8 i;
|
|
|
|
unsigned int count = 0;
|
2016-02-22 06:03:28 -05:00
|
|
|
|
|
|
|
if (!sta->cell_capa)
|
|
|
|
return 0;
|
|
|
|
|
2016-02-22 13:37:21 -05:00
|
|
|
ret = os_snprintf(pos, end - pos, "mbo_cell_capa=%u\n", sta->cell_capa);
|
|
|
|
if (os_snprintf_error(end - pos, ret))
|
|
|
|
return pos - buf;
|
|
|
|
pos += ret;
|
|
|
|
|
|
|
|
for (info = sta->non_pref_chan; info; info = info->next) {
|
|
|
|
char *pos2 = pos;
|
|
|
|
|
|
|
|
ret = os_snprintf(pos2, end - pos2,
|
2016-09-23 08:54:13 -04:00
|
|
|
"non_pref_chan[%u]=%u:%u:%u:",
|
2016-02-22 13:37:21 -05:00
|
|
|
count, info->op_class, info->pref,
|
2016-09-23 08:54:13 -04:00
|
|
|
info->reason_code);
|
2016-02-22 13:37:21 -05:00
|
|
|
count++;
|
|
|
|
if (os_snprintf_error(end - pos2, ret))
|
|
|
|
break;
|
|
|
|
pos2 += ret;
|
|
|
|
|
|
|
|
for (i = 0; i < info->num_channels; i++) {
|
|
|
|
ret = os_snprintf(pos2, end - pos2, "%u%s",
|
|
|
|
info->channels[i],
|
|
|
|
i + 1 < info->num_channels ?
|
|
|
|
"," : "");
|
|
|
|
if (os_snprintf_error(end - pos2, ret)) {
|
|
|
|
pos2 = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pos2 += ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pos2)
|
|
|
|
break;
|
|
|
|
ret = os_snprintf(pos2, end - pos2, "\n");
|
|
|
|
if (os_snprintf_error(end - pos2, ret))
|
|
|
|
break;
|
|
|
|
pos2 += ret;
|
|
|
|
pos = pos2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pos - buf;
|
2016-02-22 06:03:28 -05:00
|
|
|
}
|
2016-02-22 06:24:21 -05:00
|
|
|
|
|
|
|
|
|
|
|
static void mbo_ap_wnm_notif_req_cell_capa(struct sta_info *sta,
|
|
|
|
const u8 *buf, size_t len)
|
|
|
|
{
|
|
|
|
if (len < 1)
|
|
|
|
return;
|
|
|
|
wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR
|
|
|
|
" updated cellular data capability: %u",
|
|
|
|
MAC2STR(sta->addr), buf[0]);
|
|
|
|
sta->cell_capa = buf[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void mbo_ap_wnm_notif_req_elem(struct sta_info *sta, u8 type,
|
2016-02-22 13:37:21 -05:00
|
|
|
const u8 *buf, size_t len,
|
|
|
|
int *first_non_pref_chan)
|
2016-02-22 06:24:21 -05:00
|
|
|
{
|
|
|
|
switch (type) {
|
2016-02-22 13:37:21 -05:00
|
|
|
case WFA_WNM_NOTIF_SUBELEM_NON_PREF_CHAN_REPORT:
|
|
|
|
if (*first_non_pref_chan) {
|
|
|
|
/*
|
|
|
|
* Need to free the previously stored entries now to
|
|
|
|
* allow the update to replace all entries.
|
|
|
|
*/
|
|
|
|
*first_non_pref_chan = 0;
|
|
|
|
mbo_ap_sta_free(sta);
|
|
|
|
}
|
|
|
|
mbo_ap_parse_non_pref_chan(sta, buf, len);
|
|
|
|
break;
|
2016-02-22 06:24:21 -05:00
|
|
|
case WFA_WNM_NOTIF_SUBELEM_CELL_DATA_CAPA:
|
|
|
|
mbo_ap_wnm_notif_req_cell_capa(sta, buf, len);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
wpa_printf(MSG_DEBUG,
|
|
|
|
"MBO: Ignore unknown WNM Notification WFA subelement %u",
|
|
|
|
type);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr,
|
|
|
|
const u8 *buf, size_t len)
|
|
|
|
{
|
|
|
|
const u8 *pos, *end;
|
|
|
|
u8 ie_len;
|
|
|
|
struct sta_info *sta;
|
2016-02-22 13:37:21 -05:00
|
|
|
int first_non_pref_chan = 1;
|
2016-02-22 06:24:21 -05:00
|
|
|
|
|
|
|
if (!hapd->conf->mbo_enabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
sta = ap_get_sta(hapd, addr);
|
|
|
|
if (!sta)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pos = buf;
|
|
|
|
end = buf + len;
|
|
|
|
|
|
|
|
while (end - pos > 1) {
|
|
|
|
ie_len = pos[1];
|
|
|
|
|
|
|
|
if (2 + ie_len > end - pos)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (pos[0] == WLAN_EID_VENDOR_SPECIFIC &&
|
|
|
|
ie_len >= 4 && WPA_GET_BE24(pos + 2) == OUI_WFA)
|
|
|
|
mbo_ap_wnm_notif_req_elem(sta, pos[5],
|
2016-02-22 13:37:21 -05:00
|
|
|
pos + 6, ie_len - 4,
|
|
|
|
&first_non_pref_chan);
|
2016-02-22 06:24:21 -05:00
|
|
|
else
|
|
|
|
wpa_printf(MSG_DEBUG,
|
|
|
|
"MBO: Ignore unknown WNM Notification element %u (len=%u)",
|
|
|
|
pos[0], pos[1]);
|
|
|
|
|
|
|
|
pos += 2 + pos[1];
|
|
|
|
}
|
|
|
|
}
|