xref: /freebsd/contrib/wpa/src/ap/taxonomy.c (revision 67350cb56a69468c118bd4ccf6e361b7ebfa9eb4)
1780fb4a2SCy Schubert /*
2780fb4a2SCy Schubert  * hostapd / Client taxonomy
3780fb4a2SCy Schubert  * Copyright (c) 2015 Google, Inc.
4780fb4a2SCy Schubert  *
5780fb4a2SCy Schubert  * This software may be distributed under the terms of the BSD license.
6780fb4a2SCy Schubert  * See README for more details.
7780fb4a2SCy Schubert  *
8780fb4a2SCy Schubert  * Parse a series of IEs, as in Probe Request or (Re)Association Request frames,
9780fb4a2SCy Schubert  * and render them to a descriptive string. The tag number of standard options
10780fb4a2SCy Schubert  * is written to the string, while the vendor ID and subtag are written for
11780fb4a2SCy Schubert  * vendor options.
12780fb4a2SCy Schubert  *
13780fb4a2SCy Schubert  * Example strings:
14780fb4a2SCy Schubert  * 0,1,50,45,221(00904c,51)
15780fb4a2SCy Schubert  * 0,1,33,36,48,45,221(00904c,51),221(0050f2,2)
16780fb4a2SCy Schubert  */
17780fb4a2SCy Schubert 
18780fb4a2SCy Schubert #include "utils/includes.h"
19780fb4a2SCy Schubert 
20780fb4a2SCy Schubert #include "utils/common.h"
21780fb4a2SCy Schubert #include "common/wpa_ctrl.h"
22780fb4a2SCy Schubert #include "hostapd.h"
23780fb4a2SCy Schubert #include "sta_info.h"
24*85732ac8SCy Schubert #include "taxonomy.h"
25780fb4a2SCy Schubert 
26780fb4a2SCy Schubert 
27780fb4a2SCy Schubert /* Copy a string with no funny schtuff allowed; only alphanumerics. */
no_mischief_strncpy(char * dst,const char * src,size_t n)28780fb4a2SCy Schubert static void no_mischief_strncpy(char *dst, const char *src, size_t n)
29780fb4a2SCy Schubert {
30780fb4a2SCy Schubert 	size_t i;
31780fb4a2SCy Schubert 
32780fb4a2SCy Schubert 	for (i = 0; i < n; i++) {
33780fb4a2SCy Schubert 		unsigned char s = src[i];
34780fb4a2SCy Schubert 		int is_lower = s >= 'a' && s <= 'z';
35780fb4a2SCy Schubert 		int is_upper = s >= 'A' && s <= 'Z';
36780fb4a2SCy Schubert 		int is_digit = s >= '0' && s <= '9';
37780fb4a2SCy Schubert 
38780fb4a2SCy Schubert 		if (is_lower || is_upper || is_digit) {
39780fb4a2SCy Schubert 			/* TODO: if any manufacturer uses Unicode within the
40780fb4a2SCy Schubert 			 * WPS header, it will get mangled here. */
41780fb4a2SCy Schubert 			dst[i] = s;
42780fb4a2SCy Schubert 		} else {
43780fb4a2SCy Schubert 			/* Note that even spaces will be transformed to
44780fb4a2SCy Schubert 			 * underscores, so 'Nexus 7' will turn into 'Nexus_7'.
45780fb4a2SCy Schubert 			 * This is deliberate, to make the string easier to
46780fb4a2SCy Schubert 			 * parse. */
47780fb4a2SCy Schubert 			dst[i] = '_';
48780fb4a2SCy Schubert 		}
49780fb4a2SCy Schubert 	}
50780fb4a2SCy Schubert }
51780fb4a2SCy Schubert 
52780fb4a2SCy Schubert 
get_wps_name(char * name,size_t name_len,const u8 * data,size_t data_len)53780fb4a2SCy Schubert static int get_wps_name(char *name, size_t name_len,
54780fb4a2SCy Schubert 			const u8 *data, size_t data_len)
55780fb4a2SCy Schubert {
56780fb4a2SCy Schubert 	/* Inside the WPS IE are a series of attributes, using two byte IDs
57780fb4a2SCy Schubert 	 * and two byte lengths. We're looking for the model name, if
58780fb4a2SCy Schubert 	 * present. */
59780fb4a2SCy Schubert 	while (data_len >= 4) {
60780fb4a2SCy Schubert 		u16 id, elen;
61780fb4a2SCy Schubert 
62780fb4a2SCy Schubert 		id = WPA_GET_BE16(data);
63780fb4a2SCy Schubert 		elen = WPA_GET_BE16(data + 2);
64780fb4a2SCy Schubert 		data += 4;
65780fb4a2SCy Schubert 		data_len -= 4;
66780fb4a2SCy Schubert 
67780fb4a2SCy Schubert 		if (elen > data_len)
68780fb4a2SCy Schubert 			return 0;
69780fb4a2SCy Schubert 
70780fb4a2SCy Schubert 		if (id == 0x1023) {
71780fb4a2SCy Schubert 			/* Model name, like 'Nexus 7' */
72780fb4a2SCy Schubert 			size_t n = (elen < name_len) ? elen : name_len;
73780fb4a2SCy Schubert 			no_mischief_strncpy(name, (const char *) data, n);
74780fb4a2SCy Schubert 			return n;
75780fb4a2SCy Schubert 		}
76780fb4a2SCy Schubert 
77780fb4a2SCy Schubert 		data += elen;
78780fb4a2SCy Schubert 		data_len -= elen;
79780fb4a2SCy Schubert 	}
80780fb4a2SCy Schubert 
81780fb4a2SCy Schubert 	return 0;
82780fb4a2SCy Schubert }
83780fb4a2SCy Schubert 
84780fb4a2SCy Schubert 
ie_to_string(char * fstr,size_t fstr_len,const struct wpabuf * ies)85780fb4a2SCy Schubert static void ie_to_string(char *fstr, size_t fstr_len, const struct wpabuf *ies)
86780fb4a2SCy Schubert {
87780fb4a2SCy Schubert 	char *fpos = fstr;
88780fb4a2SCy Schubert 	char *fend = fstr + fstr_len;
89780fb4a2SCy Schubert 	char htcap[7 + 4 + 1]; /* ",htcap:" + %04hx + trailing NUL */
90780fb4a2SCy Schubert 	char htagg[7 + 2 + 1]; /* ",htagg:" + %02hx + trailing NUL */
91780fb4a2SCy Schubert 	char htmcs[7 + 8 + 1]; /* ",htmcs:" + %08x + trailing NUL */
92780fb4a2SCy Schubert 	char vhtcap[8 + 8 + 1]; /* ",vhtcap:" + %08x + trailing NUL */
93780fb4a2SCy Schubert 	char vhtrxmcs[10 + 8 + 1]; /* ",vhtrxmcs:" + %08x + trailing NUL */
94780fb4a2SCy Schubert 	char vhttxmcs[10 + 8 + 1]; /* ",vhttxmcs:" + %08x + trailing NUL */
95780fb4a2SCy Schubert #define MAX_EXTCAP	254
96780fb4a2SCy Schubert 	char extcap[8 + 2 * MAX_EXTCAP + 1]; /* ",extcap:" + hex + trailing NUL
97780fb4a2SCy Schubert 					      */
98780fb4a2SCy Schubert 	char txpow[7 + 4 + 1]; /* ",txpow:" + %04hx + trailing NUL */
99780fb4a2SCy Schubert #define WPS_NAME_LEN		32
100780fb4a2SCy Schubert 	char wps[WPS_NAME_LEN + 5 + 1]; /* room to prepend ",wps:" + trailing
101780fb4a2SCy Schubert 					 * NUL */
102780fb4a2SCy Schubert 	int num = 0;
103780fb4a2SCy Schubert 	const u8 *ie;
104780fb4a2SCy Schubert 	size_t ie_len;
105780fb4a2SCy Schubert 	int ret;
106780fb4a2SCy Schubert 
107780fb4a2SCy Schubert 	os_memset(htcap, 0, sizeof(htcap));
108780fb4a2SCy Schubert 	os_memset(htagg, 0, sizeof(htagg));
109780fb4a2SCy Schubert 	os_memset(htmcs, 0, sizeof(htmcs));
110780fb4a2SCy Schubert 	os_memset(vhtcap, 0, sizeof(vhtcap));
111780fb4a2SCy Schubert 	os_memset(vhtrxmcs, 0, sizeof(vhtrxmcs));
112780fb4a2SCy Schubert 	os_memset(vhttxmcs, 0, sizeof(vhttxmcs));
113780fb4a2SCy Schubert 	os_memset(extcap, 0, sizeof(extcap));
114780fb4a2SCy Schubert 	os_memset(txpow, 0, sizeof(txpow));
115780fb4a2SCy Schubert 	os_memset(wps, 0, sizeof(wps));
116780fb4a2SCy Schubert 	*fpos = '\0';
117780fb4a2SCy Schubert 
118780fb4a2SCy Schubert 	if (!ies)
119780fb4a2SCy Schubert 		return;
120780fb4a2SCy Schubert 	ie = wpabuf_head(ies);
121780fb4a2SCy Schubert 	ie_len = wpabuf_len(ies);
122780fb4a2SCy Schubert 
123780fb4a2SCy Schubert 	while (ie_len >= 2) {
124780fb4a2SCy Schubert 		u8 id, elen;
125780fb4a2SCy Schubert 		char *sep = (num++ == 0) ? "" : ",";
126780fb4a2SCy Schubert 
127780fb4a2SCy Schubert 		id = *ie++;
128780fb4a2SCy Schubert 		elen = *ie++;
129780fb4a2SCy Schubert 		ie_len -= 2;
130780fb4a2SCy Schubert 
131780fb4a2SCy Schubert 		if (elen > ie_len)
132780fb4a2SCy Schubert 			break;
133780fb4a2SCy Schubert 
134780fb4a2SCy Schubert 		if (id == WLAN_EID_VENDOR_SPECIFIC && elen >= 4) {
135780fb4a2SCy Schubert 			/* Vendor specific */
136780fb4a2SCy Schubert 			if (WPA_GET_BE32(ie) == WPS_IE_VENDOR_TYPE) {
137780fb4a2SCy Schubert 				/* WPS */
138780fb4a2SCy Schubert 				char model_name[WPS_NAME_LEN + 1];
139780fb4a2SCy Schubert 				const u8 *data = &ie[4];
140780fb4a2SCy Schubert 				size_t data_len = elen - 4;
141780fb4a2SCy Schubert 
142780fb4a2SCy Schubert 				os_memset(model_name, 0, sizeof(model_name));
143780fb4a2SCy Schubert 				if (get_wps_name(model_name, WPS_NAME_LEN, data,
144780fb4a2SCy Schubert 						 data_len)) {
145780fb4a2SCy Schubert 					os_snprintf(wps, sizeof(wps),
146780fb4a2SCy Schubert 						    ",wps:%s", model_name);
147780fb4a2SCy Schubert 				}
148780fb4a2SCy Schubert 			}
149780fb4a2SCy Schubert 
150780fb4a2SCy Schubert 			ret = os_snprintf(fpos, fend - fpos,
151780fb4a2SCy Schubert 					  "%s%d(%02x%02x%02x,%d)",
152780fb4a2SCy Schubert 					  sep, id, ie[0], ie[1], ie[2], ie[3]);
153780fb4a2SCy Schubert 		} else {
154780fb4a2SCy Schubert 			if (id == WLAN_EID_HT_CAP && elen >= 2) {
155780fb4a2SCy Schubert 				/* HT Capabilities (802.11n) */
156780fb4a2SCy Schubert 				os_snprintf(htcap, sizeof(htcap),
157780fb4a2SCy Schubert 					    ",htcap:%04hx",
158780fb4a2SCy Schubert 					    WPA_GET_LE16(ie));
159780fb4a2SCy Schubert 			}
160780fb4a2SCy Schubert 			if (id == WLAN_EID_HT_CAP && elen >= 3) {
161780fb4a2SCy Schubert 				/* HT Capabilities (802.11n), A-MPDU information
162780fb4a2SCy Schubert 				 */
163780fb4a2SCy Schubert 				os_snprintf(htagg, sizeof(htagg),
164780fb4a2SCy Schubert 					    ",htagg:%02hx", (u16) ie[2]);
165780fb4a2SCy Schubert 			}
166780fb4a2SCy Schubert 			if (id == WLAN_EID_HT_CAP && elen >= 7) {
167780fb4a2SCy Schubert 				/* HT Capabilities (802.11n), MCS information */
168780fb4a2SCy Schubert 				os_snprintf(htmcs, sizeof(htmcs),
169780fb4a2SCy Schubert 					    ",htmcs:%08hx",
170780fb4a2SCy Schubert 					    (u16) WPA_GET_LE32(ie + 3));
171780fb4a2SCy Schubert 			}
172780fb4a2SCy Schubert 			if (id == WLAN_EID_VHT_CAP && elen >= 4) {
173780fb4a2SCy Schubert 				/* VHT Capabilities (802.11ac) */
174780fb4a2SCy Schubert 				os_snprintf(vhtcap, sizeof(vhtcap),
175780fb4a2SCy Schubert 					    ",vhtcap:%08x",
176780fb4a2SCy Schubert 					    WPA_GET_LE32(ie));
177780fb4a2SCy Schubert 			}
178780fb4a2SCy Schubert 			if (id == WLAN_EID_VHT_CAP && elen >= 8) {
179780fb4a2SCy Schubert 				/* VHT Capabilities (802.11ac), RX MCS
180780fb4a2SCy Schubert 				 * information */
181780fb4a2SCy Schubert 				os_snprintf(vhtrxmcs, sizeof(vhtrxmcs),
182780fb4a2SCy Schubert 					    ",vhtrxmcs:%08x",
183780fb4a2SCy Schubert 					    WPA_GET_LE32(ie + 4));
184780fb4a2SCy Schubert 			}
185780fb4a2SCy Schubert 			if (id == WLAN_EID_VHT_CAP && elen >= 12) {
186780fb4a2SCy Schubert 				/* VHT Capabilities (802.11ac), TX MCS
187780fb4a2SCy Schubert 				 * information */
188780fb4a2SCy Schubert 				os_snprintf(vhttxmcs, sizeof(vhttxmcs),
189780fb4a2SCy Schubert 					    ",vhttxmcs:%08x",
190780fb4a2SCy Schubert 					    WPA_GET_LE32(ie + 8));
191780fb4a2SCy Schubert 			}
192780fb4a2SCy Schubert 			if (id == WLAN_EID_EXT_CAPAB) {
193780fb4a2SCy Schubert 				/* Extended Capabilities */
194780fb4a2SCy Schubert 				int i;
195780fb4a2SCy Schubert 				int len = (elen < MAX_EXTCAP) ? elen :
196780fb4a2SCy Schubert 					MAX_EXTCAP;
197780fb4a2SCy Schubert 				char *p = extcap;
198780fb4a2SCy Schubert 
199780fb4a2SCy Schubert 				p += os_snprintf(extcap, sizeof(extcap),
200780fb4a2SCy Schubert 						 ",extcap:");
201780fb4a2SCy Schubert 				for (i = 0; i < len; i++) {
202780fb4a2SCy Schubert 					int lim;
203780fb4a2SCy Schubert 
204780fb4a2SCy Schubert 					lim = sizeof(extcap) -
205780fb4a2SCy Schubert 						os_strlen(extcap);
206780fb4a2SCy Schubert 					if (lim <= 0)
207780fb4a2SCy Schubert 						break;
208780fb4a2SCy Schubert 					p += os_snprintf(p, lim, "%02x",
209780fb4a2SCy Schubert 							 *(ie + i));
210780fb4a2SCy Schubert 				}
211780fb4a2SCy Schubert 			}
212780fb4a2SCy Schubert 			if (id == WLAN_EID_PWR_CAPABILITY && elen == 2) {
213780fb4a2SCy Schubert 				/* TX Power */
214780fb4a2SCy Schubert 				os_snprintf(txpow, sizeof(txpow),
215780fb4a2SCy Schubert 					    ",txpow:%04hx",
216780fb4a2SCy Schubert 					    WPA_GET_LE16(ie));
217780fb4a2SCy Schubert 			}
218780fb4a2SCy Schubert 
219780fb4a2SCy Schubert 			ret = os_snprintf(fpos, fend - fpos, "%s%d", sep, id);
220780fb4a2SCy Schubert 		}
221780fb4a2SCy Schubert 		if (os_snprintf_error(fend - fpos, ret))
222780fb4a2SCy Schubert 			goto fail;
223780fb4a2SCy Schubert 		fpos += ret;
224780fb4a2SCy Schubert 
225780fb4a2SCy Schubert 		ie += elen;
226780fb4a2SCy Schubert 		ie_len -= elen;
227780fb4a2SCy Schubert 	}
228780fb4a2SCy Schubert 
229780fb4a2SCy Schubert 	ret = os_snprintf(fpos, fend - fpos, "%s%s%s%s%s%s%s%s%s",
230780fb4a2SCy Schubert 			  htcap, htagg, htmcs, vhtcap, vhtrxmcs, vhttxmcs,
231780fb4a2SCy Schubert 			  txpow, extcap, wps);
232780fb4a2SCy Schubert 	if (os_snprintf_error(fend - fpos, ret)) {
233780fb4a2SCy Schubert 	fail:
234780fb4a2SCy Schubert 		fstr[0] = '\0';
235780fb4a2SCy Schubert 	}
236780fb4a2SCy Schubert }
237780fb4a2SCy Schubert 
238780fb4a2SCy Schubert 
retrieve_sta_taxonomy(const struct hostapd_data * hapd,struct sta_info * sta,char * buf,size_t buflen)239780fb4a2SCy Schubert int retrieve_sta_taxonomy(const struct hostapd_data *hapd,
240780fb4a2SCy Schubert 			  struct sta_info *sta, char *buf, size_t buflen)
241780fb4a2SCy Schubert {
242780fb4a2SCy Schubert 	int ret;
243780fb4a2SCy Schubert 	char *pos, *end;
244780fb4a2SCy Schubert 
245780fb4a2SCy Schubert 	if (!sta->probe_ie_taxonomy || !sta->assoc_ie_taxonomy)
246780fb4a2SCy Schubert 		return 0;
247780fb4a2SCy Schubert 
248780fb4a2SCy Schubert 	ret = os_snprintf(buf, buflen, "wifi4|probe:");
249780fb4a2SCy Schubert 	if (os_snprintf_error(buflen, ret))
250780fb4a2SCy Schubert 		return 0;
251780fb4a2SCy Schubert 	pos = buf + ret;
252780fb4a2SCy Schubert 	end = buf + buflen;
253780fb4a2SCy Schubert 
254780fb4a2SCy Schubert 	ie_to_string(pos, end - pos, sta->probe_ie_taxonomy);
255780fb4a2SCy Schubert 	pos = os_strchr(pos, '\0');
256780fb4a2SCy Schubert 	if (pos >= end)
257780fb4a2SCy Schubert 		return 0;
258780fb4a2SCy Schubert 	ret = os_snprintf(pos, end - pos, "|assoc:");
259780fb4a2SCy Schubert 	if (os_snprintf_error(end - pos, ret))
260780fb4a2SCy Schubert 		return 0;
261780fb4a2SCy Schubert 	pos += ret;
262780fb4a2SCy Schubert 	ie_to_string(pos, end - pos, sta->assoc_ie_taxonomy);
263780fb4a2SCy Schubert 	pos = os_strchr(pos, '\0');
264780fb4a2SCy Schubert 	return pos - buf;
265780fb4a2SCy Schubert }
266780fb4a2SCy Schubert 
267780fb4a2SCy Schubert 
taxonomy_sta_info_probe_req(const struct hostapd_data * hapd,struct sta_info * sta,const u8 * ie,size_t ie_len)268780fb4a2SCy Schubert void taxonomy_sta_info_probe_req(const struct hostapd_data *hapd,
269780fb4a2SCy Schubert 				 struct sta_info *sta,
270780fb4a2SCy Schubert 				 const u8 *ie, size_t ie_len)
271780fb4a2SCy Schubert {
272780fb4a2SCy Schubert 	wpabuf_free(sta->probe_ie_taxonomy);
273780fb4a2SCy Schubert 	sta->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
274780fb4a2SCy Schubert }
275780fb4a2SCy Schubert 
276780fb4a2SCy Schubert 
taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data * hapd,struct hostapd_sta_info * info,const u8 * ie,size_t ie_len)277780fb4a2SCy Schubert void taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data *hapd,
278780fb4a2SCy Schubert 					 struct hostapd_sta_info *info,
279780fb4a2SCy Schubert 					 const u8 *ie, size_t ie_len)
280780fb4a2SCy Schubert {
281780fb4a2SCy Schubert 	wpabuf_free(info->probe_ie_taxonomy);
282780fb4a2SCy Schubert 	info->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
283780fb4a2SCy Schubert }
284780fb4a2SCy Schubert 
285780fb4a2SCy Schubert 
taxonomy_sta_info_assoc_req(const struct hostapd_data * hapd,struct sta_info * sta,const u8 * ie,size_t ie_len)286780fb4a2SCy Schubert void taxonomy_sta_info_assoc_req(const struct hostapd_data *hapd,
287780fb4a2SCy Schubert 				 struct sta_info *sta,
288780fb4a2SCy Schubert 				 const u8 *ie, size_t ie_len)
289780fb4a2SCy Schubert {
290780fb4a2SCy Schubert 	wpabuf_free(sta->assoc_ie_taxonomy);
291780fb4a2SCy Schubert 	sta->assoc_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
292780fb4a2SCy Schubert }
293