Dmitry Shmidt | aca489e | 2016-09-28 15:44:14 -0700 | [diff] [blame^] | 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 | } |