1 /* 2 * hostapd / Client taxonomy 3 * Copyright (c) 2015 Google, Inc. 4 * 5 * This software may be distributed under the terms of the BSD license. 6 * See README for more details. 7 * 8 * Parse a series of IEs, as in Probe Request or (Re)Association Request frames, 9 * and render them to a descriptive string. The tag number of standard options 10 * is written to the string, while the vendor ID and subtag are written for 11 * vendor options. 12 * 13 * Example strings: 14 * 0,1,50,45,221(00904c,51) 15 * 0,1,33,36,48,45,221(00904c,51),221(0050f2,2) 16 */ 17 18 #include "utils/includes.h" 19 20 #include "utils/common.h" 21 #include "common/wpa_ctrl.h" 22 #include "hostapd.h" 23 #include "sta_info.h" 24 #include "taxonomy.h" 25 26 27 /* Copy a string with no funny schtuff allowed; only alphanumerics. */ 28 static void no_mischief_strncpy(char *dst, const char *src, size_t n) 29 { 30 size_t i; 31 32 for (i = 0; i < n; i++) { 33 unsigned char s = src[i]; 34 int is_lower = s >= 'a' && s <= 'z'; 35 int is_upper = s >= 'A' && s <= 'Z'; 36 int is_digit = s >= '0' && s <= '9'; 37 38 if (is_lower || is_upper || is_digit) { 39 /* TODO: if any manufacturer uses Unicode within the 40 * WPS header, it will get mangled here. */ 41 dst[i] = s; 42 } else { 43 /* Note that even spaces will be transformed to 44 * underscores, so 'Nexus 7' will turn into 'Nexus_7'. 45 * This is deliberate, to make the string easier to 46 * parse. */ 47 dst[i] = '_'; 48 } 49 } 50 } 51 52 53 static int get_wps_name(char *name, size_t name_len, 54 const u8 *data, size_t data_len) 55 { 56 /* Inside the WPS IE are a series of attributes, using two byte IDs 57 * and two byte lengths. We're looking for the model name, if 58 * present. */ 59 while (data_len >= 4) { 60 u16 id, elen; 61 62 id = WPA_GET_BE16(data); 63 elen = WPA_GET_BE16(data + 2); 64 data += 4; 65 data_len -= 4; 66 67 if (elen > data_len) 68 return 0; 69 70 if (id == 0x1023) { 71 /* Model name, like 'Nexus 7' */ 72 size_t n = (elen < name_len) ? elen : name_len; 73 no_mischief_strncpy(name, (const char *) data, n); 74 return n; 75 } 76 77 data += elen; 78 data_len -= elen; 79 } 80 81 return 0; 82 } 83 84 85 static void ie_to_string(char *fstr, size_t fstr_len, const struct wpabuf *ies) 86 { 87 char *fpos = fstr; 88 char *fend = fstr + fstr_len; 89 char htcap[7 + 4 + 1]; /* ",htcap:" + %04hx + trailing NUL */ 90 char htagg[7 + 2 + 1]; /* ",htagg:" + %02hx + trailing NUL */ 91 char htmcs[7 + 8 + 1]; /* ",htmcs:" + %08x + trailing NUL */ 92 char vhtcap[8 + 8 + 1]; /* ",vhtcap:" + %08x + trailing NUL */ 93 char vhtrxmcs[10 + 8 + 1]; /* ",vhtrxmcs:" + %08x + trailing NUL */ 94 char vhttxmcs[10 + 8 + 1]; /* ",vhttxmcs:" + %08x + trailing NUL */ 95 #define MAX_EXTCAP 254 96 char extcap[8 + 2 * MAX_EXTCAP + 1]; /* ",extcap:" + hex + trailing NUL 97 */ 98 char txpow[7 + 4 + 1]; /* ",txpow:" + %04hx + trailing NUL */ 99 #define WPS_NAME_LEN 32 100 char wps[WPS_NAME_LEN + 5 + 1]; /* room to prepend ",wps:" + trailing 101 * NUL */ 102 int num = 0; 103 const u8 *ie; 104 size_t ie_len; 105 int ret; 106 107 os_memset(htcap, 0, sizeof(htcap)); 108 os_memset(htagg, 0, sizeof(htagg)); 109 os_memset(htmcs, 0, sizeof(htmcs)); 110 os_memset(vhtcap, 0, sizeof(vhtcap)); 111 os_memset(vhtrxmcs, 0, sizeof(vhtrxmcs)); 112 os_memset(vhttxmcs, 0, sizeof(vhttxmcs)); 113 os_memset(extcap, 0, sizeof(extcap)); 114 os_memset(txpow, 0, sizeof(txpow)); 115 os_memset(wps, 0, sizeof(wps)); 116 *fpos = '\0'; 117 118 if (!ies) 119 return; 120 ie = wpabuf_head(ies); 121 ie_len = wpabuf_len(ies); 122 123 while (ie_len >= 2) { 124 u8 id, elen; 125 char *sep = (num++ == 0) ? "" : ","; 126 127 id = *ie++; 128 elen = *ie++; 129 ie_len -= 2; 130 131 if (elen > ie_len) 132 break; 133 134 if (id == WLAN_EID_VENDOR_SPECIFIC && elen >= 4) { 135 /* Vendor specific */ 136 if (WPA_GET_BE32(ie) == WPS_IE_VENDOR_TYPE) { 137 /* WPS */ 138 char model_name[WPS_NAME_LEN + 1]; 139 const u8 *data = &ie[4]; 140 size_t data_len = elen - 4; 141 142 os_memset(model_name, 0, sizeof(model_name)); 143 if (get_wps_name(model_name, WPS_NAME_LEN, data, 144 data_len)) { 145 os_snprintf(wps, sizeof(wps), 146 ",wps:%s", model_name); 147 } 148 } 149 150 ret = os_snprintf(fpos, fend - fpos, 151 "%s%d(%02x%02x%02x,%d)", 152 sep, id, ie[0], ie[1], ie[2], ie[3]); 153 } else { 154 if (id == WLAN_EID_HT_CAP && elen >= 2) { 155 /* HT Capabilities (802.11n) */ 156 os_snprintf(htcap, sizeof(htcap), 157 ",htcap:%04hx", 158 WPA_GET_LE16(ie)); 159 } 160 if (id == WLAN_EID_HT_CAP && elen >= 3) { 161 /* HT Capabilities (802.11n), A-MPDU information 162 */ 163 os_snprintf(htagg, sizeof(htagg), 164 ",htagg:%02hx", (u16) ie[2]); 165 } 166 if (id == WLAN_EID_HT_CAP && elen >= 7) { 167 /* HT Capabilities (802.11n), MCS information */ 168 os_snprintf(htmcs, sizeof(htmcs), 169 ",htmcs:%08hx", 170 (u16) WPA_GET_LE32(ie + 3)); 171 } 172 if (id == WLAN_EID_VHT_CAP && elen >= 4) { 173 /* VHT Capabilities (802.11ac) */ 174 os_snprintf(vhtcap, sizeof(vhtcap), 175 ",vhtcap:%08x", 176 WPA_GET_LE32(ie)); 177 } 178 if (id == WLAN_EID_VHT_CAP && elen >= 8) { 179 /* VHT Capabilities (802.11ac), RX MCS 180 * information */ 181 os_snprintf(vhtrxmcs, sizeof(vhtrxmcs), 182 ",vhtrxmcs:%08x", 183 WPA_GET_LE32(ie + 4)); 184 } 185 if (id == WLAN_EID_VHT_CAP && elen >= 12) { 186 /* VHT Capabilities (802.11ac), TX MCS 187 * information */ 188 os_snprintf(vhttxmcs, sizeof(vhttxmcs), 189 ",vhttxmcs:%08x", 190 WPA_GET_LE32(ie + 8)); 191 } 192 if (id == WLAN_EID_EXT_CAPAB) { 193 /* Extended Capabilities */ 194 int i; 195 int len = (elen < MAX_EXTCAP) ? elen : 196 MAX_EXTCAP; 197 char *p = extcap; 198 199 p += os_snprintf(extcap, sizeof(extcap), 200 ",extcap:"); 201 for (i = 0; i < len; i++) { 202 int lim; 203 204 lim = sizeof(extcap) - 205 os_strlen(extcap); 206 if (lim <= 0) 207 break; 208 p += os_snprintf(p, lim, "%02x", 209 *(ie + i)); 210 } 211 } 212 if (id == WLAN_EID_PWR_CAPABILITY && elen == 2) { 213 /* TX Power */ 214 os_snprintf(txpow, sizeof(txpow), 215 ",txpow:%04hx", 216 WPA_GET_LE16(ie)); 217 } 218 219 ret = os_snprintf(fpos, fend - fpos, "%s%d", sep, id); 220 } 221 if (os_snprintf_error(fend - fpos, ret)) 222 goto fail; 223 fpos += ret; 224 225 ie += elen; 226 ie_len -= elen; 227 } 228 229 ret = os_snprintf(fpos, fend - fpos, "%s%s%s%s%s%s%s%s%s", 230 htcap, htagg, htmcs, vhtcap, vhtrxmcs, vhttxmcs, 231 txpow, extcap, wps); 232 if (os_snprintf_error(fend - fpos, ret)) { 233 fail: 234 fstr[0] = '\0'; 235 } 236 } 237 238 239 int retrieve_sta_taxonomy(const struct hostapd_data *hapd, 240 struct sta_info *sta, char *buf, size_t buflen) 241 { 242 int ret; 243 char *pos, *end; 244 245 if (!sta->probe_ie_taxonomy || !sta->assoc_ie_taxonomy) 246 return 0; 247 248 ret = os_snprintf(buf, buflen, "wifi4|probe:"); 249 if (os_snprintf_error(buflen, ret)) 250 return 0; 251 pos = buf + ret; 252 end = buf + buflen; 253 254 ie_to_string(pos, end - pos, sta->probe_ie_taxonomy); 255 pos = os_strchr(pos, '\0'); 256 if (pos >= end) 257 return 0; 258 ret = os_snprintf(pos, end - pos, "|assoc:"); 259 if (os_snprintf_error(end - pos, ret)) 260 return 0; 261 pos += ret; 262 ie_to_string(pos, end - pos, sta->assoc_ie_taxonomy); 263 pos = os_strchr(pos, '\0'); 264 return pos - buf; 265 } 266 267 268 void taxonomy_sta_info_probe_req(const struct hostapd_data *hapd, 269 struct sta_info *sta, 270 const u8 *ie, size_t ie_len) 271 { 272 wpabuf_free(sta->probe_ie_taxonomy); 273 sta->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len); 274 } 275 276 277 void taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data *hapd, 278 struct hostapd_sta_info *info, 279 const u8 *ie, size_t ie_len) 280 { 281 wpabuf_free(info->probe_ie_taxonomy); 282 info->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len); 283 } 284 285 286 void taxonomy_sta_info_assoc_req(const struct hostapd_data *hapd, 287 struct sta_info *sta, 288 const u8 *ie, size_t ie_len) 289 { 290 wpabuf_free(sta->assoc_ie_taxonomy); 291 sta->assoc_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len); 292 } 293