1324714Scy/* 2324714Scy * hostapd / Client taxonomy 3324714Scy * Copyright (c) 2015 Google, Inc. 4324714Scy * 5324714Scy * This software may be distributed under the terms of the BSD license. 6324714Scy * See README for more details. 7324714Scy * 8324714Scy * Parse a series of IEs, as in Probe Request or (Re)Association Request frames, 9324714Scy * and render them to a descriptive string. The tag number of standard options 10324714Scy * is written to the string, while the vendor ID and subtag are written for 11324714Scy * vendor options. 12324714Scy * 13324714Scy * Example strings: 14324714Scy * 0,1,50,45,221(00904c,51) 15324714Scy * 0,1,33,36,48,45,221(00904c,51),221(0050f2,2) 16324714Scy */ 17324714Scy 18324714Scy#include "utils/includes.h" 19324714Scy 20324714Scy#include "utils/common.h" 21324714Scy#include "common/wpa_ctrl.h" 22324714Scy#include "hostapd.h" 23324714Scy#include "sta_info.h" 24346981Scy#include "taxonomy.h" 25324714Scy 26324714Scy 27324714Scy/* Copy a string with no funny schtuff allowed; only alphanumerics. */ 28324714Scystatic void no_mischief_strncpy(char *dst, const char *src, size_t n) 29324714Scy{ 30324714Scy size_t i; 31324714Scy 32324714Scy for (i = 0; i < n; i++) { 33324714Scy unsigned char s = src[i]; 34324714Scy int is_lower = s >= 'a' && s <= 'z'; 35324714Scy int is_upper = s >= 'A' && s <= 'Z'; 36324714Scy int is_digit = s >= '0' && s <= '9'; 37324714Scy 38324714Scy if (is_lower || is_upper || is_digit) { 39324714Scy /* TODO: if any manufacturer uses Unicode within the 40324714Scy * WPS header, it will get mangled here. */ 41324714Scy dst[i] = s; 42324714Scy } else { 43324714Scy /* Note that even spaces will be transformed to 44324714Scy * underscores, so 'Nexus 7' will turn into 'Nexus_7'. 45324714Scy * This is deliberate, to make the string easier to 46324714Scy * parse. */ 47324714Scy dst[i] = '_'; 48324714Scy } 49324714Scy } 50324714Scy} 51324714Scy 52324714Scy 53324714Scystatic int get_wps_name(char *name, size_t name_len, 54324714Scy const u8 *data, size_t data_len) 55324714Scy{ 56324714Scy /* Inside the WPS IE are a series of attributes, using two byte IDs 57324714Scy * and two byte lengths. We're looking for the model name, if 58324714Scy * present. */ 59324714Scy while (data_len >= 4) { 60324714Scy u16 id, elen; 61324714Scy 62324714Scy id = WPA_GET_BE16(data); 63324714Scy elen = WPA_GET_BE16(data + 2); 64324714Scy data += 4; 65324714Scy data_len -= 4; 66324714Scy 67324714Scy if (elen > data_len) 68324714Scy return 0; 69324714Scy 70324714Scy if (id == 0x1023) { 71324714Scy /* Model name, like 'Nexus 7' */ 72324714Scy size_t n = (elen < name_len) ? elen : name_len; 73324714Scy no_mischief_strncpy(name, (const char *) data, n); 74324714Scy return n; 75324714Scy } 76324714Scy 77324714Scy data += elen; 78324714Scy data_len -= elen; 79324714Scy } 80324714Scy 81324714Scy return 0; 82324714Scy} 83324714Scy 84324714Scy 85324714Scystatic void ie_to_string(char *fstr, size_t fstr_len, const struct wpabuf *ies) 86324714Scy{ 87324714Scy char *fpos = fstr; 88324714Scy char *fend = fstr + fstr_len; 89324714Scy char htcap[7 + 4 + 1]; /* ",htcap:" + %04hx + trailing NUL */ 90324714Scy char htagg[7 + 2 + 1]; /* ",htagg:" + %02hx + trailing NUL */ 91324714Scy char htmcs[7 + 8 + 1]; /* ",htmcs:" + %08x + trailing NUL */ 92324714Scy char vhtcap[8 + 8 + 1]; /* ",vhtcap:" + %08x + trailing NUL */ 93324714Scy char vhtrxmcs[10 + 8 + 1]; /* ",vhtrxmcs:" + %08x + trailing NUL */ 94324714Scy char vhttxmcs[10 + 8 + 1]; /* ",vhttxmcs:" + %08x + trailing NUL */ 95324714Scy#define MAX_EXTCAP 254 96324714Scy char extcap[8 + 2 * MAX_EXTCAP + 1]; /* ",extcap:" + hex + trailing NUL 97324714Scy */ 98324714Scy char txpow[7 + 4 + 1]; /* ",txpow:" + %04hx + trailing NUL */ 99324714Scy#define WPS_NAME_LEN 32 100324714Scy char wps[WPS_NAME_LEN + 5 + 1]; /* room to prepend ",wps:" + trailing 101324714Scy * NUL */ 102324714Scy int num = 0; 103324714Scy const u8 *ie; 104324714Scy size_t ie_len; 105324714Scy int ret; 106324714Scy 107324714Scy os_memset(htcap, 0, sizeof(htcap)); 108324714Scy os_memset(htagg, 0, sizeof(htagg)); 109324714Scy os_memset(htmcs, 0, sizeof(htmcs)); 110324714Scy os_memset(vhtcap, 0, sizeof(vhtcap)); 111324714Scy os_memset(vhtrxmcs, 0, sizeof(vhtrxmcs)); 112324714Scy os_memset(vhttxmcs, 0, sizeof(vhttxmcs)); 113324714Scy os_memset(extcap, 0, sizeof(extcap)); 114324714Scy os_memset(txpow, 0, sizeof(txpow)); 115324714Scy os_memset(wps, 0, sizeof(wps)); 116324714Scy *fpos = '\0'; 117324714Scy 118324714Scy if (!ies) 119324714Scy return; 120324714Scy ie = wpabuf_head(ies); 121324714Scy ie_len = wpabuf_len(ies); 122324714Scy 123324714Scy while (ie_len >= 2) { 124324714Scy u8 id, elen; 125324714Scy char *sep = (num++ == 0) ? "" : ","; 126324714Scy 127324714Scy id = *ie++; 128324714Scy elen = *ie++; 129324714Scy ie_len -= 2; 130324714Scy 131324714Scy if (elen > ie_len) 132324714Scy break; 133324714Scy 134324714Scy if (id == WLAN_EID_VENDOR_SPECIFIC && elen >= 4) { 135324714Scy /* Vendor specific */ 136324714Scy if (WPA_GET_BE32(ie) == WPS_IE_VENDOR_TYPE) { 137324714Scy /* WPS */ 138324714Scy char model_name[WPS_NAME_LEN + 1]; 139324714Scy const u8 *data = &ie[4]; 140324714Scy size_t data_len = elen - 4; 141324714Scy 142324714Scy os_memset(model_name, 0, sizeof(model_name)); 143324714Scy if (get_wps_name(model_name, WPS_NAME_LEN, data, 144324714Scy data_len)) { 145324714Scy os_snprintf(wps, sizeof(wps), 146324714Scy ",wps:%s", model_name); 147324714Scy } 148324714Scy } 149324714Scy 150324714Scy ret = os_snprintf(fpos, fend - fpos, 151324714Scy "%s%d(%02x%02x%02x,%d)", 152324714Scy sep, id, ie[0], ie[1], ie[2], ie[3]); 153324714Scy } else { 154324714Scy if (id == WLAN_EID_HT_CAP && elen >= 2) { 155324714Scy /* HT Capabilities (802.11n) */ 156324714Scy os_snprintf(htcap, sizeof(htcap), 157324714Scy ",htcap:%04hx", 158324714Scy WPA_GET_LE16(ie)); 159324714Scy } 160324714Scy if (id == WLAN_EID_HT_CAP && elen >= 3) { 161324714Scy /* HT Capabilities (802.11n), A-MPDU information 162324714Scy */ 163324714Scy os_snprintf(htagg, sizeof(htagg), 164324714Scy ",htagg:%02hx", (u16) ie[2]); 165324714Scy } 166324714Scy if (id == WLAN_EID_HT_CAP && elen >= 7) { 167324714Scy /* HT Capabilities (802.11n), MCS information */ 168324714Scy os_snprintf(htmcs, sizeof(htmcs), 169324714Scy ",htmcs:%08hx", 170324714Scy (u16) WPA_GET_LE32(ie + 3)); 171324714Scy } 172324714Scy if (id == WLAN_EID_VHT_CAP && elen >= 4) { 173324714Scy /* VHT Capabilities (802.11ac) */ 174324714Scy os_snprintf(vhtcap, sizeof(vhtcap), 175324714Scy ",vhtcap:%08x", 176324714Scy WPA_GET_LE32(ie)); 177324714Scy } 178324714Scy if (id == WLAN_EID_VHT_CAP && elen >= 8) { 179324714Scy /* VHT Capabilities (802.11ac), RX MCS 180324714Scy * information */ 181324714Scy os_snprintf(vhtrxmcs, sizeof(vhtrxmcs), 182324714Scy ",vhtrxmcs:%08x", 183324714Scy WPA_GET_LE32(ie + 4)); 184324714Scy } 185324714Scy if (id == WLAN_EID_VHT_CAP && elen >= 12) { 186324714Scy /* VHT Capabilities (802.11ac), TX MCS 187324714Scy * information */ 188324714Scy os_snprintf(vhttxmcs, sizeof(vhttxmcs), 189324714Scy ",vhttxmcs:%08x", 190324714Scy WPA_GET_LE32(ie + 8)); 191324714Scy } 192324714Scy if (id == WLAN_EID_EXT_CAPAB) { 193324714Scy /* Extended Capabilities */ 194324714Scy int i; 195324714Scy int len = (elen < MAX_EXTCAP) ? elen : 196324714Scy MAX_EXTCAP; 197324714Scy char *p = extcap; 198324714Scy 199324714Scy p += os_snprintf(extcap, sizeof(extcap), 200324714Scy ",extcap:"); 201324714Scy for (i = 0; i < len; i++) { 202324714Scy int lim; 203324714Scy 204324714Scy lim = sizeof(extcap) - 205324714Scy os_strlen(extcap); 206324714Scy if (lim <= 0) 207324714Scy break; 208324714Scy p += os_snprintf(p, lim, "%02x", 209324714Scy *(ie + i)); 210324714Scy } 211324714Scy } 212324714Scy if (id == WLAN_EID_PWR_CAPABILITY && elen == 2) { 213324714Scy /* TX Power */ 214324714Scy os_snprintf(txpow, sizeof(txpow), 215324714Scy ",txpow:%04hx", 216324714Scy WPA_GET_LE16(ie)); 217324714Scy } 218324714Scy 219324714Scy ret = os_snprintf(fpos, fend - fpos, "%s%d", sep, id); 220324714Scy } 221324714Scy if (os_snprintf_error(fend - fpos, ret)) 222324714Scy goto fail; 223324714Scy fpos += ret; 224324714Scy 225324714Scy ie += elen; 226324714Scy ie_len -= elen; 227324714Scy } 228324714Scy 229324714Scy ret = os_snprintf(fpos, fend - fpos, "%s%s%s%s%s%s%s%s%s", 230324714Scy htcap, htagg, htmcs, vhtcap, vhtrxmcs, vhttxmcs, 231324714Scy txpow, extcap, wps); 232324714Scy if (os_snprintf_error(fend - fpos, ret)) { 233324714Scy fail: 234324714Scy fstr[0] = '\0'; 235324714Scy } 236324714Scy} 237324714Scy 238324714Scy 239324714Scyint retrieve_sta_taxonomy(const struct hostapd_data *hapd, 240324714Scy struct sta_info *sta, char *buf, size_t buflen) 241324714Scy{ 242324714Scy int ret; 243324714Scy char *pos, *end; 244324714Scy 245324714Scy if (!sta->probe_ie_taxonomy || !sta->assoc_ie_taxonomy) 246324714Scy return 0; 247324714Scy 248324714Scy ret = os_snprintf(buf, buflen, "wifi4|probe:"); 249324714Scy if (os_snprintf_error(buflen, ret)) 250324714Scy return 0; 251324714Scy pos = buf + ret; 252324714Scy end = buf + buflen; 253324714Scy 254324714Scy ie_to_string(pos, end - pos, sta->probe_ie_taxonomy); 255324714Scy pos = os_strchr(pos, '\0'); 256324714Scy if (pos >= end) 257324714Scy return 0; 258324714Scy ret = os_snprintf(pos, end - pos, "|assoc:"); 259324714Scy if (os_snprintf_error(end - pos, ret)) 260324714Scy return 0; 261324714Scy pos += ret; 262324714Scy ie_to_string(pos, end - pos, sta->assoc_ie_taxonomy); 263324714Scy pos = os_strchr(pos, '\0'); 264324714Scy return pos - buf; 265324714Scy} 266324714Scy 267324714Scy 268324714Scyvoid taxonomy_sta_info_probe_req(const struct hostapd_data *hapd, 269324714Scy struct sta_info *sta, 270324714Scy const u8 *ie, size_t ie_len) 271324714Scy{ 272324714Scy wpabuf_free(sta->probe_ie_taxonomy); 273324714Scy sta->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len); 274324714Scy} 275324714Scy 276324714Scy 277324714Scyvoid taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data *hapd, 278324714Scy struct hostapd_sta_info *info, 279324714Scy const u8 *ie, size_t ie_len) 280324714Scy{ 281324714Scy wpabuf_free(info->probe_ie_taxonomy); 282324714Scy info->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len); 283324714Scy} 284324714Scy 285324714Scy 286324714Scyvoid taxonomy_sta_info_assoc_req(const struct hostapd_data *hapd, 287324714Scy struct sta_info *sta, 288324714Scy const u8 *ie, size_t ie_len) 289324714Scy{ 290324714Scy wpabuf_free(sta->assoc_ie_taxonomy); 291324714Scy sta->assoc_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len); 292324714Scy} 293