136ebd06eSchristos /*
236ebd06eSchristos * hostapd / Client taxonomy
336ebd06eSchristos * Copyright (c) 2015 Google, Inc.
436ebd06eSchristos *
536ebd06eSchristos * This software may be distributed under the terms of the BSD license.
636ebd06eSchristos * See README for more details.
736ebd06eSchristos *
836ebd06eSchristos * Parse a series of IEs, as in Probe Request or (Re)Association Request frames,
936ebd06eSchristos * and render them to a descriptive string. The tag number of standard options
1036ebd06eSchristos * is written to the string, while the vendor ID and subtag are written for
1136ebd06eSchristos * vendor options.
1236ebd06eSchristos *
1336ebd06eSchristos * Example strings:
1436ebd06eSchristos * 0,1,50,45,221(00904c,51)
1536ebd06eSchristos * 0,1,33,36,48,45,221(00904c,51),221(0050f2,2)
1636ebd06eSchristos */
1736ebd06eSchristos
1836ebd06eSchristos #include "utils/includes.h"
1936ebd06eSchristos
2036ebd06eSchristos #include "utils/common.h"
2136ebd06eSchristos #include "common/wpa_ctrl.h"
2236ebd06eSchristos #include "hostapd.h"
2336ebd06eSchristos #include "sta_info.h"
24*0a73ee0aSchristos #include "taxonomy.h"
2536ebd06eSchristos
2636ebd06eSchristos
2736ebd06eSchristos /* Copy a string with no funny schtuff allowed; only alphanumerics. */
no_mischief_strncpy(char * dst,const char * src,size_t n)2836ebd06eSchristos static void no_mischief_strncpy(char *dst, const char *src, size_t n)
2936ebd06eSchristos {
3036ebd06eSchristos size_t i;
3136ebd06eSchristos
3236ebd06eSchristos for (i = 0; i < n; i++) {
3336ebd06eSchristos unsigned char s = src[i];
3436ebd06eSchristos int is_lower = s >= 'a' && s <= 'z';
3536ebd06eSchristos int is_upper = s >= 'A' && s <= 'Z';
3636ebd06eSchristos int is_digit = s >= '0' && s <= '9';
3736ebd06eSchristos
3836ebd06eSchristos if (is_lower || is_upper || is_digit) {
3936ebd06eSchristos /* TODO: if any manufacturer uses Unicode within the
4036ebd06eSchristos * WPS header, it will get mangled here. */
4136ebd06eSchristos dst[i] = s;
4236ebd06eSchristos } else {
4336ebd06eSchristos /* Note that even spaces will be transformed to
4436ebd06eSchristos * underscores, so 'Nexus 7' will turn into 'Nexus_7'.
4536ebd06eSchristos * This is deliberate, to make the string easier to
4636ebd06eSchristos * parse. */
4736ebd06eSchristos dst[i] = '_';
4836ebd06eSchristos }
4936ebd06eSchristos }
5036ebd06eSchristos }
5136ebd06eSchristos
5236ebd06eSchristos
get_wps_name(char * name,size_t name_len,const u8 * data,size_t data_len)5336ebd06eSchristos static int get_wps_name(char *name, size_t name_len,
5436ebd06eSchristos const u8 *data, size_t data_len)
5536ebd06eSchristos {
5636ebd06eSchristos /* Inside the WPS IE are a series of attributes, using two byte IDs
5736ebd06eSchristos * and two byte lengths. We're looking for the model name, if
5836ebd06eSchristos * present. */
5936ebd06eSchristos while (data_len >= 4) {
6036ebd06eSchristos u16 id, elen;
6136ebd06eSchristos
6236ebd06eSchristos id = WPA_GET_BE16(data);
6336ebd06eSchristos elen = WPA_GET_BE16(data + 2);
6436ebd06eSchristos data += 4;
6536ebd06eSchristos data_len -= 4;
6636ebd06eSchristos
6736ebd06eSchristos if (elen > data_len)
6836ebd06eSchristos return 0;
6936ebd06eSchristos
7036ebd06eSchristos if (id == 0x1023) {
7136ebd06eSchristos /* Model name, like 'Nexus 7' */
7236ebd06eSchristos size_t n = (elen < name_len) ? elen : name_len;
7336ebd06eSchristos no_mischief_strncpy(name, (const char *) data, n);
7436ebd06eSchristos return n;
7536ebd06eSchristos }
7636ebd06eSchristos
7736ebd06eSchristos data += elen;
7836ebd06eSchristos data_len -= elen;
7936ebd06eSchristos }
8036ebd06eSchristos
8136ebd06eSchristos return 0;
8236ebd06eSchristos }
8336ebd06eSchristos
8436ebd06eSchristos
ie_to_string(char * fstr,size_t fstr_len,const struct wpabuf * ies)8536ebd06eSchristos static void ie_to_string(char *fstr, size_t fstr_len, const struct wpabuf *ies)
8636ebd06eSchristos {
8736ebd06eSchristos char *fpos = fstr;
8836ebd06eSchristos char *fend = fstr + fstr_len;
8936ebd06eSchristos char htcap[7 + 4 + 1]; /* ",htcap:" + %04hx + trailing NUL */
9036ebd06eSchristos char htagg[7 + 2 + 1]; /* ",htagg:" + %02hx + trailing NUL */
9136ebd06eSchristos char htmcs[7 + 8 + 1]; /* ",htmcs:" + %08x + trailing NUL */
9236ebd06eSchristos char vhtcap[8 + 8 + 1]; /* ",vhtcap:" + %08x + trailing NUL */
9336ebd06eSchristos char vhtrxmcs[10 + 8 + 1]; /* ",vhtrxmcs:" + %08x + trailing NUL */
9436ebd06eSchristos char vhttxmcs[10 + 8 + 1]; /* ",vhttxmcs:" + %08x + trailing NUL */
9536ebd06eSchristos #define MAX_EXTCAP 254
9636ebd06eSchristos char extcap[8 + 2 * MAX_EXTCAP + 1]; /* ",extcap:" + hex + trailing NUL
9736ebd06eSchristos */
9836ebd06eSchristos char txpow[7 + 4 + 1]; /* ",txpow:" + %04hx + trailing NUL */
9936ebd06eSchristos #define WPS_NAME_LEN 32
10036ebd06eSchristos char wps[WPS_NAME_LEN + 5 + 1]; /* room to prepend ",wps:" + trailing
10136ebd06eSchristos * NUL */
10236ebd06eSchristos int num = 0;
10336ebd06eSchristos const u8 *ie;
10436ebd06eSchristos size_t ie_len;
10536ebd06eSchristos int ret;
10636ebd06eSchristos
10736ebd06eSchristos os_memset(htcap, 0, sizeof(htcap));
10836ebd06eSchristos os_memset(htagg, 0, sizeof(htagg));
10936ebd06eSchristos os_memset(htmcs, 0, sizeof(htmcs));
11036ebd06eSchristos os_memset(vhtcap, 0, sizeof(vhtcap));
11136ebd06eSchristos os_memset(vhtrxmcs, 0, sizeof(vhtrxmcs));
11236ebd06eSchristos os_memset(vhttxmcs, 0, sizeof(vhttxmcs));
11336ebd06eSchristos os_memset(extcap, 0, sizeof(extcap));
11436ebd06eSchristos os_memset(txpow, 0, sizeof(txpow));
11536ebd06eSchristos os_memset(wps, 0, sizeof(wps));
11636ebd06eSchristos *fpos = '\0';
11736ebd06eSchristos
11836ebd06eSchristos if (!ies)
11936ebd06eSchristos return;
12036ebd06eSchristos ie = wpabuf_head(ies);
12136ebd06eSchristos ie_len = wpabuf_len(ies);
12236ebd06eSchristos
12336ebd06eSchristos while (ie_len >= 2) {
12436ebd06eSchristos u8 id, elen;
12536ebd06eSchristos char *sep = (num++ == 0) ? "" : ",";
12636ebd06eSchristos
12736ebd06eSchristos id = *ie++;
12836ebd06eSchristos elen = *ie++;
12936ebd06eSchristos ie_len -= 2;
13036ebd06eSchristos
13136ebd06eSchristos if (elen > ie_len)
13236ebd06eSchristos break;
13336ebd06eSchristos
13436ebd06eSchristos if (id == WLAN_EID_VENDOR_SPECIFIC && elen >= 4) {
13536ebd06eSchristos /* Vendor specific */
13636ebd06eSchristos if (WPA_GET_BE32(ie) == WPS_IE_VENDOR_TYPE) {
13736ebd06eSchristos /* WPS */
13836ebd06eSchristos char model_name[WPS_NAME_LEN + 1];
13936ebd06eSchristos const u8 *data = &ie[4];
14036ebd06eSchristos size_t data_len = elen - 4;
14136ebd06eSchristos
14236ebd06eSchristos os_memset(model_name, 0, sizeof(model_name));
14336ebd06eSchristos if (get_wps_name(model_name, WPS_NAME_LEN, data,
14436ebd06eSchristos data_len)) {
14536ebd06eSchristos os_snprintf(wps, sizeof(wps),
14636ebd06eSchristos ",wps:%s", model_name);
14736ebd06eSchristos }
14836ebd06eSchristos }
14936ebd06eSchristos
15036ebd06eSchristos ret = os_snprintf(fpos, fend - fpos,
15136ebd06eSchristos "%s%d(%02x%02x%02x,%d)",
15236ebd06eSchristos sep, id, ie[0], ie[1], ie[2], ie[3]);
15336ebd06eSchristos } else {
15436ebd06eSchristos if (id == WLAN_EID_HT_CAP && elen >= 2) {
15536ebd06eSchristos /* HT Capabilities (802.11n) */
15636ebd06eSchristos os_snprintf(htcap, sizeof(htcap),
15736ebd06eSchristos ",htcap:%04hx",
15836ebd06eSchristos WPA_GET_LE16(ie));
15936ebd06eSchristos }
16036ebd06eSchristos if (id == WLAN_EID_HT_CAP && elen >= 3) {
16136ebd06eSchristos /* HT Capabilities (802.11n), A-MPDU information
16236ebd06eSchristos */
16336ebd06eSchristos os_snprintf(htagg, sizeof(htagg),
16436ebd06eSchristos ",htagg:%02hx", (u16) ie[2]);
16536ebd06eSchristos }
16636ebd06eSchristos if (id == WLAN_EID_HT_CAP && elen >= 7) {
16736ebd06eSchristos /* HT Capabilities (802.11n), MCS information */
16836ebd06eSchristos os_snprintf(htmcs, sizeof(htmcs),
16936ebd06eSchristos ",htmcs:%08hx",
17036ebd06eSchristos (u16) WPA_GET_LE32(ie + 3));
17136ebd06eSchristos }
17236ebd06eSchristos if (id == WLAN_EID_VHT_CAP && elen >= 4) {
17336ebd06eSchristos /* VHT Capabilities (802.11ac) */
17436ebd06eSchristos os_snprintf(vhtcap, sizeof(vhtcap),
17536ebd06eSchristos ",vhtcap:%08x",
17636ebd06eSchristos WPA_GET_LE32(ie));
17736ebd06eSchristos }
17836ebd06eSchristos if (id == WLAN_EID_VHT_CAP && elen >= 8) {
17936ebd06eSchristos /* VHT Capabilities (802.11ac), RX MCS
18036ebd06eSchristos * information */
18136ebd06eSchristos os_snprintf(vhtrxmcs, sizeof(vhtrxmcs),
18236ebd06eSchristos ",vhtrxmcs:%08x",
18336ebd06eSchristos WPA_GET_LE32(ie + 4));
18436ebd06eSchristos }
18536ebd06eSchristos if (id == WLAN_EID_VHT_CAP && elen >= 12) {
18636ebd06eSchristos /* VHT Capabilities (802.11ac), TX MCS
18736ebd06eSchristos * information */
18836ebd06eSchristos os_snprintf(vhttxmcs, sizeof(vhttxmcs),
18936ebd06eSchristos ",vhttxmcs:%08x",
19036ebd06eSchristos WPA_GET_LE32(ie + 8));
19136ebd06eSchristos }
19236ebd06eSchristos if (id == WLAN_EID_EXT_CAPAB) {
19336ebd06eSchristos /* Extended Capabilities */
19436ebd06eSchristos int i;
19536ebd06eSchristos int len = (elen < MAX_EXTCAP) ? elen :
19636ebd06eSchristos MAX_EXTCAP;
19736ebd06eSchristos char *p = extcap;
19836ebd06eSchristos
19936ebd06eSchristos p += os_snprintf(extcap, sizeof(extcap),
20036ebd06eSchristos ",extcap:");
20136ebd06eSchristos for (i = 0; i < len; i++) {
20236ebd06eSchristos int lim;
20336ebd06eSchristos
20436ebd06eSchristos lim = sizeof(extcap) -
20536ebd06eSchristos os_strlen(extcap);
20636ebd06eSchristos if (lim <= 0)
20736ebd06eSchristos break;
20836ebd06eSchristos p += os_snprintf(p, lim, "%02x",
20936ebd06eSchristos *(ie + i));
21036ebd06eSchristos }
21136ebd06eSchristos }
21236ebd06eSchristos if (id == WLAN_EID_PWR_CAPABILITY && elen == 2) {
21336ebd06eSchristos /* TX Power */
21436ebd06eSchristos os_snprintf(txpow, sizeof(txpow),
21536ebd06eSchristos ",txpow:%04hx",
21636ebd06eSchristos WPA_GET_LE16(ie));
21736ebd06eSchristos }
21836ebd06eSchristos
21936ebd06eSchristos ret = os_snprintf(fpos, fend - fpos, "%s%d", sep, id);
22036ebd06eSchristos }
22136ebd06eSchristos if (os_snprintf_error(fend - fpos, ret))
22236ebd06eSchristos goto fail;
22336ebd06eSchristos fpos += ret;
22436ebd06eSchristos
22536ebd06eSchristos ie += elen;
22636ebd06eSchristos ie_len -= elen;
22736ebd06eSchristos }
22836ebd06eSchristos
22936ebd06eSchristos ret = os_snprintf(fpos, fend - fpos, "%s%s%s%s%s%s%s%s%s",
23036ebd06eSchristos htcap, htagg, htmcs, vhtcap, vhtrxmcs, vhttxmcs,
23136ebd06eSchristos txpow, extcap, wps);
23236ebd06eSchristos if (os_snprintf_error(fend - fpos, ret)) {
23336ebd06eSchristos fail:
23436ebd06eSchristos fstr[0] = '\0';
23536ebd06eSchristos }
23636ebd06eSchristos }
23736ebd06eSchristos
23836ebd06eSchristos
retrieve_sta_taxonomy(const struct hostapd_data * hapd,struct sta_info * sta,char * buf,size_t buflen)23936ebd06eSchristos int retrieve_sta_taxonomy(const struct hostapd_data *hapd,
24036ebd06eSchristos struct sta_info *sta, char *buf, size_t buflen)
24136ebd06eSchristos {
24236ebd06eSchristos int ret;
24336ebd06eSchristos char *pos, *end;
24436ebd06eSchristos
24536ebd06eSchristos if (!sta->probe_ie_taxonomy || !sta->assoc_ie_taxonomy)
24636ebd06eSchristos return 0;
24736ebd06eSchristos
24836ebd06eSchristos ret = os_snprintf(buf, buflen, "wifi4|probe:");
24936ebd06eSchristos if (os_snprintf_error(buflen, ret))
25036ebd06eSchristos return 0;
25136ebd06eSchristos pos = buf + ret;
25236ebd06eSchristos end = buf + buflen;
25336ebd06eSchristos
25436ebd06eSchristos ie_to_string(pos, end - pos, sta->probe_ie_taxonomy);
25536ebd06eSchristos pos = os_strchr(pos, '\0');
25636ebd06eSchristos if (pos >= end)
25736ebd06eSchristos return 0;
25836ebd06eSchristos ret = os_snprintf(pos, end - pos, "|assoc:");
25936ebd06eSchristos if (os_snprintf_error(end - pos, ret))
26036ebd06eSchristos return 0;
26136ebd06eSchristos pos += ret;
26236ebd06eSchristos ie_to_string(pos, end - pos, sta->assoc_ie_taxonomy);
26336ebd06eSchristos pos = os_strchr(pos, '\0');
26436ebd06eSchristos return pos - buf;
26536ebd06eSchristos }
26636ebd06eSchristos
26736ebd06eSchristos
taxonomy_sta_info_probe_req(const struct hostapd_data * hapd,struct sta_info * sta,const u8 * ie,size_t ie_len)26836ebd06eSchristos void taxonomy_sta_info_probe_req(const struct hostapd_data *hapd,
26936ebd06eSchristos struct sta_info *sta,
27036ebd06eSchristos const u8 *ie, size_t ie_len)
27136ebd06eSchristos {
27236ebd06eSchristos wpabuf_free(sta->probe_ie_taxonomy);
27336ebd06eSchristos sta->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
27436ebd06eSchristos }
27536ebd06eSchristos
27636ebd06eSchristos
taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data * hapd,struct hostapd_sta_info * info,const u8 * ie,size_t ie_len)27736ebd06eSchristos void taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data *hapd,
27836ebd06eSchristos struct hostapd_sta_info *info,
27936ebd06eSchristos const u8 *ie, size_t ie_len)
28036ebd06eSchristos {
28136ebd06eSchristos wpabuf_free(info->probe_ie_taxonomy);
28236ebd06eSchristos info->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
28336ebd06eSchristos }
28436ebd06eSchristos
28536ebd06eSchristos
taxonomy_sta_info_assoc_req(const struct hostapd_data * hapd,struct sta_info * sta,const u8 * ie,size_t ie_len)28636ebd06eSchristos void taxonomy_sta_info_assoc_req(const struct hostapd_data *hapd,
28736ebd06eSchristos struct sta_info *sta,
28836ebd06eSchristos const u8 *ie, size_t ie_len)
28936ebd06eSchristos {
29036ebd06eSchristos wpabuf_free(sta->assoc_ie_taxonomy);
29136ebd06eSchristos sta->assoc_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
29236ebd06eSchristos }
293