Passive Client Taxonomy
[mech_eap.git] / src / ap / taxonomy.c
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_sta_info_assoc_req(const struct hostapd_data *hapd,
277                                  struct sta_info *sta,
278                                  const u8 *ie, size_t ie_len)
279 {
280         wpabuf_free(sta->assoc_ie_taxonomy);
281         sta->assoc_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
282 }