xref: /freebsd/contrib/wpa/src/ap/taxonomy.c (revision e4c66ddabdb470bab319705c1834a4867c508a43)
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