Passive Client Taxonomy
Implement the signature mechanism described in the paper "Passive Taxonomy of Wifi Clients using MLME Frame Contents" published by Denton Gentry and Avery Pennarun. http://research.google.com/pubs/pub45429.html https://arxiv.org/abs/1608.01725 This involves: 1. Add a CONFIG_TAXONOMY compile option. Enabling taxonomy incurs a memory overhead of up to several kilobytes per associated station. 2. If enabled, store the Probe Request and (Re)Associate Request frame in struct sta_info. 3. Implement code to extract the ID of each Information Element, plus selected fields and bitmasks from certain IEs, into a descriptive text string. This is done in a new source file, src/ap/taxonomy.c. 4. Implement a "signature qq:rr:ss:tt:uu:vv" command in hostapd_cli to retrieve the signature. Signatures take the form of a text string. For example, a signature for the Nexus 5X is: wifi4|probe:0,1,127,45,191,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:338061b2, vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:00000a0201000040|assoc:0,1,48,45, 221(0050f2,2),191,127,htcap:01ef,htagg:03,htmcs:0000ffff,vhtcap:339071b2, vhtrxmcs:030cfffa,vhttxmcs:030cfffa,extcap:0000000000000040 Signed-off-by: dgentry@google.com (Denton Gentry) Signed-off-by: denny@geekhold.com (Denton Gentry) Signed-off-by: rofrankel@google.com (Richard Frankel) Signed-off-by: richard@frankel.tv (Richard Frankel)
This commit is contained in:
parent
5e993390f6
commit
04059ab844
12 changed files with 391 additions and 0 deletions
|
@ -100,6 +100,11 @@ NEED_SHA1=y
|
|||
OBJS += ../src/drivers/drivers.o
|
||||
CFLAGS += -DHOSTAPD
|
||||
|
||||
ifdef CONFIG_TAXONOMY
|
||||
CFLAGS += -DCONFIG_TAXONOMY
|
||||
OBJS += ../src/ap/taxonomy.o
|
||||
endif
|
||||
|
||||
ifdef CONFIG_MODULE_TESTS
|
||||
CFLAGS += -DCONFIG_MODULE_TESTS
|
||||
OBJS += hapd_module_tests.o
|
||||
|
|
|
@ -2367,6 +2367,11 @@ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,
|
|||
} else if (os_strncmp(buf, "DISASSOCIATE ", 13) == 0) {
|
||||
if (hostapd_ctrl_iface_disassociate(hapd, buf + 13))
|
||||
reply_len = -1;
|
||||
#ifdef CONFIG_TAXONOMY
|
||||
} else if (os_strncmp(buf, "SIGNATURE ", 10) == 0) {
|
||||
reply_len = hostapd_ctrl_iface_signature(hapd, buf + 10,
|
||||
reply, reply_size);
|
||||
#endif /* CONFIG_TAXONOMY */
|
||||
} else if (os_strncmp(buf, "POLL_STA ", 9) == 0) {
|
||||
if (hostapd_ctrl_iface_poll_sta(hapd, buf + 9))
|
||||
reply_len = -1;
|
||||
|
|
|
@ -337,3 +337,9 @@ CONFIG_IPV6=y
|
|||
# These extentions facilitate efficient use of multiple frequency bands
|
||||
# available to the AP and the devices that may associate with it.
|
||||
#CONFIG_MBO=y
|
||||
|
||||
# Client Taxonomy
|
||||
# Has the AP retain the Probe Request and (Re)Association Request frames from
|
||||
# a client, from which a signature can be produced which can identify the model
|
||||
# of client device like "Nexus 6P" or "iPhone 5s".
|
||||
#CONFIG_TAXONOMY=y
|
||||
|
|
|
@ -366,6 +366,22 @@ static char ** hostapd_complete_disassociate(const char *str, int pos)
|
|||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_TAXONOMY
|
||||
static int hostapd_cli_cmd_signature(struct wpa_ctrl *ctrl, int argc,
|
||||
char *argv[])
|
||||
{
|
||||
char buf[64];
|
||||
|
||||
if (argc != 1) {
|
||||
printf("Invalid 'signature' command - exactly one argument, STA address, is required.\n");
|
||||
return -1;
|
||||
}
|
||||
os_snprintf(buf, sizeof(buf), "SIGNATURE %s", argv[0]);
|
||||
return wpa_ctrl_command(ctrl, buf);
|
||||
}
|
||||
#endif /* CONFIG_TAXONOMY */
|
||||
|
||||
|
||||
#ifdef CONFIG_IEEE80211W
|
||||
static int hostapd_cli_cmd_sa_query(struct wpa_ctrl *ctrl, int argc,
|
||||
char *argv[])
|
||||
|
@ -1271,6 +1287,10 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {
|
|||
{ "disassociate", hostapd_cli_cmd_disassociate,
|
||||
hostapd_complete_disassociate,
|
||||
"<addr> = disassociate a station" },
|
||||
#ifdef CONFIG_TAXONOMY
|
||||
{ "signature", hostapd_cli_cmd_signature, NULL,
|
||||
"<addr> = get taxonomy signature for a station" },
|
||||
#endif /* CONFIG_TAXONOMY */
|
||||
#ifdef CONFIG_IEEE80211W
|
||||
{ "sa_query", hostapd_cli_cmd_sa_query, NULL,
|
||||
"<addr> = send SA Query to a station" },
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "beacon.h"
|
||||
#include "hs20.h"
|
||||
#include "dfs.h"
|
||||
#include "taxonomy.h"
|
||||
|
||||
|
||||
#ifdef NEED_AP_MLME
|
||||
|
@ -784,6 +785,14 @@ void handle_probe_req(struct hostapd_data *hapd,
|
|||
}
|
||||
#endif /* CONFIG_P2P */
|
||||
|
||||
#ifdef CONFIG_TAXONOMY
|
||||
{
|
||||
struct sta_info *sta = ap_get_sta(hapd, mgmt->sa);
|
||||
if (sta)
|
||||
taxonomy_sta_info_probe_req(hapd, sta, ie, ie_len);
|
||||
}
|
||||
#endif /* CONFIG_TAXONOMY */
|
||||
|
||||
res = ssid_match(hapd, elems.ssid, elems.ssid_len,
|
||||
elems.ssid_list, elems.ssid_list_len);
|
||||
if (res == NO_SSID_MATCH) {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "ctrl_iface_ap.h"
|
||||
#include "ap_drv_ops.h"
|
||||
#include "mbo_ap.h"
|
||||
#include "taxonomy.h"
|
||||
|
||||
|
||||
static int hostapd_get_sta_tx_rx(struct hostapd_data *hapd,
|
||||
|
@ -429,6 +430,28 @@ int hostapd_ctrl_iface_disassociate(struct hostapd_data *hapd,
|
|||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_TAXONOMY
|
||||
int hostapd_ctrl_iface_signature(struct hostapd_data *hapd,
|
||||
const char *txtaddr,
|
||||
char *buf, size_t buflen)
|
||||
{
|
||||
u8 addr[ETH_ALEN];
|
||||
struct sta_info *sta;
|
||||
|
||||
wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "CTRL_IFACE SIGNATURE %s", txtaddr);
|
||||
|
||||
if (hwaddr_aton(txtaddr, addr))
|
||||
return -1;
|
||||
|
||||
sta = ap_get_sta(hapd, addr);
|
||||
if (!sta)
|
||||
return -1;
|
||||
|
||||
return retrieve_sta_taxonomy(hapd, sta, buf, buflen);
|
||||
}
|
||||
#endif /* CONFIG_TAXONOMY */
|
||||
|
||||
|
||||
int hostapd_ctrl_iface_poll_sta(struct hostapd_data *hapd,
|
||||
const char *txtaddr)
|
||||
{
|
||||
|
|
|
@ -19,6 +19,9 @@ int hostapd_ctrl_iface_deauthenticate(struct hostapd_data *hapd,
|
|||
const char *txtaddr);
|
||||
int hostapd_ctrl_iface_disassociate(struct hostapd_data *hapd,
|
||||
const char *txtaddr);
|
||||
int hostapd_ctrl_iface_signature(struct hostapd_data *hapd,
|
||||
const char *txtaddr,
|
||||
char *buf, size_t buflen);
|
||||
int hostapd_ctrl_iface_poll_sta(struct hostapd_data *hapd,
|
||||
const char *txtaddr);
|
||||
int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "dfs.h"
|
||||
#include "mbo_ap.h"
|
||||
#include "rrm.h"
|
||||
#include "taxonomy.h"
|
||||
|
||||
|
||||
u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid)
|
||||
|
@ -2266,6 +2267,10 @@ static void handle_assoc(struct hostapd_data *hapd,
|
|||
* remove the STA immediately. */
|
||||
sta->timeout_next = STA_NULLFUNC;
|
||||
|
||||
#ifdef CONFIG_TAXONOMY
|
||||
taxonomy_sta_info_assoc_req(hapd, sta, pos, left);
|
||||
#endif /* CONFIG_TAXONOMY */
|
||||
|
||||
fail:
|
||||
/*
|
||||
* In case of a successful response, add the station to the driver.
|
||||
|
|
|
@ -222,6 +222,13 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
|
|||
hapd->iface->num_sta_ht_20mhz--;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TAXONOMY
|
||||
wpabuf_free(sta->probe_ie_taxonomy);
|
||||
sta->probe_ie_taxonomy = NULL;
|
||||
wpabuf_free(sta->assoc_ie_taxonomy);
|
||||
sta->assoc_ie_taxonomy = NULL;
|
||||
#endif /* CONFIG_TAXONOMY */
|
||||
|
||||
#ifdef CONFIG_IEEE80211N
|
||||
ht40_intolerant_remove(hapd->iface, sta);
|
||||
#endif /* CONFIG_IEEE80211N */
|
||||
|
|
|
@ -214,6 +214,11 @@ struct sta_info {
|
|||
* received, starting from the Length field */
|
||||
|
||||
u8 rrm_enabled_capa[5];
|
||||
|
||||
#ifdef CONFIG_TAXONOMY
|
||||
struct wpabuf *probe_ie_taxonomy;
|
||||
struct wpabuf *assoc_ie_taxonomy;
|
||||
#endif /* CONFIG_TAXONOMY */
|
||||
};
|
||||
|
||||
|
||||
|
|
282
src/ap/taxonomy.c
Normal file
282
src/ap/taxonomy.c
Normal file
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
* hostapd / Client taxonomy
|
||||
* Copyright (c) 2015 Google, Inc.
|
||||
*
|
||||
* This software may be distributed under the terms of the BSD license.
|
||||
* See README for more details.
|
||||
*
|
||||
* Parse a series of IEs, as in Probe Request or (Re)Association Request frames,
|
||||
* and render them to a descriptive string. The tag number of standard options
|
||||
* is written to the string, while the vendor ID and subtag are written for
|
||||
* vendor options.
|
||||
*
|
||||
* Example strings:
|
||||
* 0,1,50,45,221(00904c,51)
|
||||
* 0,1,33,36,48,45,221(00904c,51),221(0050f2,2)
|
||||
*/
|
||||
|
||||
#include "utils/includes.h"
|
||||
|
||||
#include "utils/common.h"
|
||||
#include "common/wpa_ctrl.h"
|
||||
#include "hostapd.h"
|
||||
#include "sta_info.h"
|
||||
|
||||
|
||||
/* Copy a string with no funny schtuff allowed; only alphanumerics. */
|
||||
static void no_mischief_strncpy(char *dst, const char *src, size_t n)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
unsigned char s = src[i];
|
||||
int is_lower = s >= 'a' && s <= 'z';
|
||||
int is_upper = s >= 'A' && s <= 'Z';
|
||||
int is_digit = s >= '0' && s <= '9';
|
||||
|
||||
if (is_lower || is_upper || is_digit) {
|
||||
/* TODO: if any manufacturer uses Unicode within the
|
||||
* WPS header, it will get mangled here. */
|
||||
dst[i] = s;
|
||||
} else {
|
||||
/* Note that even spaces will be transformed to
|
||||
* underscores, so 'Nexus 7' will turn into 'Nexus_7'.
|
||||
* This is deliberate, to make the string easier to
|
||||
* parse. */
|
||||
dst[i] = '_';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int get_wps_name(char *name, size_t name_len,
|
||||
const u8 *data, size_t data_len)
|
||||
{
|
||||
/* Inside the WPS IE are a series of attributes, using two byte IDs
|
||||
* and two byte lengths. We're looking for the model name, if
|
||||
* present. */
|
||||
while (data_len >= 4) {
|
||||
u16 id, elen;
|
||||
|
||||
id = WPA_GET_BE16(data);
|
||||
elen = WPA_GET_BE16(data + 2);
|
||||
data += 4;
|
||||
data_len -= 4;
|
||||
|
||||
if (elen > data_len)
|
||||
return 0;
|
||||
|
||||
if (id == 0x1023) {
|
||||
/* Model name, like 'Nexus 7' */
|
||||
size_t n = (elen < name_len) ? elen : name_len;
|
||||
no_mischief_strncpy(name, (const char *) data, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
data += elen;
|
||||
data_len -= elen;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void ie_to_string(char *fstr, size_t fstr_len, const struct wpabuf *ies)
|
||||
{
|
||||
char *fpos = fstr;
|
||||
char *fend = fstr + fstr_len;
|
||||
char htcap[7 + 4 + 1]; /* ",htcap:" + %04hx + trailing NUL */
|
||||
char htagg[7 + 2 + 1]; /* ",htagg:" + %02hx + trailing NUL */
|
||||
char htmcs[7 + 8 + 1]; /* ",htmcs:" + %08x + trailing NUL */
|
||||
char vhtcap[8 + 8 + 1]; /* ",vhtcap:" + %08x + trailing NUL */
|
||||
char vhtrxmcs[10 + 8 + 1]; /* ",vhtrxmcs:" + %08x + trailing NUL */
|
||||
char vhttxmcs[10 + 8 + 1]; /* ",vhttxmcs:" + %08x + trailing NUL */
|
||||
#define MAX_EXTCAP 254
|
||||
char extcap[8 + 2 * MAX_EXTCAP + 1]; /* ",extcap:" + hex + trailing NUL
|
||||
*/
|
||||
char txpow[7 + 4 + 1]; /* ",txpow:" + %04hx + trailing NUL */
|
||||
#define WPS_NAME_LEN 32
|
||||
char wps[WPS_NAME_LEN + 5 + 1]; /* room to prepend ",wps:" + trailing
|
||||
* NUL */
|
||||
int num = 0;
|
||||
const u8 *ie;
|
||||
size_t ie_len;
|
||||
int ret;
|
||||
|
||||
os_memset(htcap, 0, sizeof(htcap));
|
||||
os_memset(htagg, 0, sizeof(htagg));
|
||||
os_memset(htmcs, 0, sizeof(htmcs));
|
||||
os_memset(vhtcap, 0, sizeof(vhtcap));
|
||||
os_memset(vhtrxmcs, 0, sizeof(vhtrxmcs));
|
||||
os_memset(vhttxmcs, 0, sizeof(vhttxmcs));
|
||||
os_memset(extcap, 0, sizeof(extcap));
|
||||
os_memset(txpow, 0, sizeof(txpow));
|
||||
os_memset(wps, 0, sizeof(wps));
|
||||
*fpos = '\0';
|
||||
|
||||
if (!ies)
|
||||
return;
|
||||
ie = wpabuf_head(ies);
|
||||
ie_len = wpabuf_len(ies);
|
||||
|
||||
while (ie_len >= 2) {
|
||||
u8 id, elen;
|
||||
char *sep = (num++ == 0) ? "" : ",";
|
||||
|
||||
id = *ie++;
|
||||
elen = *ie++;
|
||||
ie_len -= 2;
|
||||
|
||||
if (elen > ie_len)
|
||||
break;
|
||||
|
||||
if (id == WLAN_EID_VENDOR_SPECIFIC && elen >= 4) {
|
||||
/* Vendor specific */
|
||||
if (WPA_GET_BE32(ie) == WPS_IE_VENDOR_TYPE) {
|
||||
/* WPS */
|
||||
char model_name[WPS_NAME_LEN + 1];
|
||||
const u8 *data = &ie[4];
|
||||
size_t data_len = elen - 4;
|
||||
|
||||
os_memset(model_name, 0, sizeof(model_name));
|
||||
if (get_wps_name(model_name, WPS_NAME_LEN, data,
|
||||
data_len)) {
|
||||
os_snprintf(wps, sizeof(wps),
|
||||
",wps:%s", model_name);
|
||||
}
|
||||
}
|
||||
|
||||
ret = os_snprintf(fpos, fend - fpos,
|
||||
"%s%d(%02x%02x%02x,%d)",
|
||||
sep, id, ie[0], ie[1], ie[2], ie[3]);
|
||||
} else {
|
||||
if (id == WLAN_EID_HT_CAP && elen >= 2) {
|
||||
/* HT Capabilities (802.11n) */
|
||||
os_snprintf(htcap, sizeof(htcap),
|
||||
",htcap:%04hx",
|
||||
WPA_GET_LE16(ie));
|
||||
}
|
||||
if (id == WLAN_EID_HT_CAP && elen >= 3) {
|
||||
/* HT Capabilities (802.11n), A-MPDU information
|
||||
*/
|
||||
os_snprintf(htagg, sizeof(htagg),
|
||||
",htagg:%02hx", (u16) ie[2]);
|
||||
}
|
||||
if (id == WLAN_EID_HT_CAP && elen >= 7) {
|
||||
/* HT Capabilities (802.11n), MCS information */
|
||||
os_snprintf(htmcs, sizeof(htmcs),
|
||||
",htmcs:%08hx",
|
||||
(u16) WPA_GET_LE32(ie + 3));
|
||||
}
|
||||
if (id == WLAN_EID_VHT_CAP && elen >= 4) {
|
||||
/* VHT Capabilities (802.11ac) */
|
||||
os_snprintf(vhtcap, sizeof(vhtcap),
|
||||
",vhtcap:%08x",
|
||||
WPA_GET_LE32(ie));
|
||||
}
|
||||
if (id == WLAN_EID_VHT_CAP && elen >= 8) {
|
||||
/* VHT Capabilities (802.11ac), RX MCS
|
||||
* information */
|
||||
os_snprintf(vhtrxmcs, sizeof(vhtrxmcs),
|
||||
",vhtrxmcs:%08x",
|
||||
WPA_GET_LE32(ie + 4));
|
||||
}
|
||||
if (id == WLAN_EID_VHT_CAP && elen >= 12) {
|
||||
/* VHT Capabilities (802.11ac), TX MCS
|
||||
* information */
|
||||
os_snprintf(vhttxmcs, sizeof(vhttxmcs),
|
||||
",vhttxmcs:%08x",
|
||||
WPA_GET_LE32(ie + 8));
|
||||
}
|
||||
if (id == WLAN_EID_EXT_CAPAB) {
|
||||
/* Extended Capabilities */
|
||||
int i;
|
||||
int len = (elen < MAX_EXTCAP) ? elen :
|
||||
MAX_EXTCAP;
|
||||
char *p = extcap;
|
||||
|
||||
p += os_snprintf(extcap, sizeof(extcap),
|
||||
",extcap:");
|
||||
for (i = 0; i < len; i++) {
|
||||
int lim;
|
||||
|
||||
lim = sizeof(extcap) -
|
||||
os_strlen(extcap);
|
||||
if (lim <= 0)
|
||||
break;
|
||||
p += os_snprintf(p, lim, "%02x",
|
||||
*(ie + i));
|
||||
}
|
||||
}
|
||||
if (id == WLAN_EID_PWR_CAPABILITY && elen == 2) {
|
||||
/* TX Power */
|
||||
os_snprintf(txpow, sizeof(txpow),
|
||||
",txpow:%04hx",
|
||||
WPA_GET_LE16(ie));
|
||||
}
|
||||
|
||||
ret = os_snprintf(fpos, fend - fpos, "%s%d", sep, id);
|
||||
}
|
||||
if (os_snprintf_error(fend - fpos, ret))
|
||||
goto fail;
|
||||
fpos += ret;
|
||||
|
||||
ie += elen;
|
||||
ie_len -= elen;
|
||||
}
|
||||
|
||||
ret = os_snprintf(fpos, fend - fpos, "%s%s%s%s%s%s%s%s%s",
|
||||
htcap, htagg, htmcs, vhtcap, vhtrxmcs, vhttxmcs,
|
||||
txpow, extcap, wps);
|
||||
if (os_snprintf_error(fend - fpos, ret)) {
|
||||
fail:
|
||||
fstr[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int retrieve_sta_taxonomy(const struct hostapd_data *hapd,
|
||||
struct sta_info *sta, char *buf, size_t buflen)
|
||||
{
|
||||
int ret;
|
||||
char *pos, *end;
|
||||
|
||||
if (!sta->probe_ie_taxonomy || !sta->assoc_ie_taxonomy)
|
||||
return 0;
|
||||
|
||||
ret = os_snprintf(buf, buflen, "wifi4|probe:");
|
||||
if (os_snprintf_error(buflen, ret))
|
||||
return 0;
|
||||
pos = buf + ret;
|
||||
end = buf + buflen;
|
||||
|
||||
ie_to_string(pos, end - pos, sta->probe_ie_taxonomy);
|
||||
pos = os_strchr(pos, '\0');
|
||||
if (pos >= end)
|
||||
return 0;
|
||||
ret = os_snprintf(pos, end - pos, "|assoc:");
|
||||
if (os_snprintf_error(end - pos, ret))
|
||||
return 0;
|
||||
pos += ret;
|
||||
ie_to_string(pos, end - pos, sta->assoc_ie_taxonomy);
|
||||
pos = os_strchr(pos, '\0');
|
||||
return pos - buf;
|
||||
}
|
||||
|
||||
|
||||
void taxonomy_sta_info_probe_req(const struct hostapd_data *hapd,
|
||||
struct sta_info *sta,
|
||||
const u8 *ie, size_t ie_len)
|
||||
{
|
||||
wpabuf_free(sta->probe_ie_taxonomy);
|
||||
sta->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
|
||||
}
|
||||
|
||||
|
||||
void taxonomy_sta_info_assoc_req(const struct hostapd_data *hapd,
|
||||
struct sta_info *sta,
|
||||
const u8 *ie, size_t ie_len)
|
||||
{
|
||||
wpabuf_free(sta->assoc_ie_taxonomy);
|
||||
sta->assoc_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
|
||||
}
|
21
src/ap/taxonomy.h
Normal file
21
src/ap/taxonomy.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* hostapd / Station client taxonomy
|
||||
* Copyright (c) 2015 Google, Inc.
|
||||
*
|
||||
* This software may be distributed under the terms of the BSD license.
|
||||
* See README for more details.
|
||||
*/
|
||||
|
||||
#ifndef TAXONOMY_H
|
||||
#define TAXONOMY_H
|
||||
|
||||
void taxonomy_sta_info_probe_req(const struct hostapd_data *hapd,
|
||||
struct sta_info *sta,
|
||||
const u8 *ie, size_t ie_len);
|
||||
void taxonomy_sta_info_assoc_req(const struct hostapd_data *hapd,
|
||||
struct sta_info *sta,
|
||||
const u8 *ie, size_t ie_len);
|
||||
int retrieve_sta_taxonomy(const struct hostapd_data *hapd,
|
||||
struct sta_info *sta, char *buf, size_t buflen);
|
||||
|
||||
#endif /* TAXONOMY_H */
|
Loading…
Reference in a new issue