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