diff options
Diffstat (limited to 'src/ap/taxonomy.c')
-rw-r--r-- | src/ap/taxonomy.c | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/src/ap/taxonomy.c b/src/ap/taxonomy.c new file mode 100644 index 0000000..e533a10 --- /dev/null +++ b/src/ap/taxonomy.c @@ -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); +} |