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