xref: /netbsd-src/external/bsd/wpa/dist/src/ap/taxonomy.c (revision 0a73ee0a32b4208ab171f89f408b38fd4c664291)
136ebd06eSchristos /*
236ebd06eSchristos  * hostapd / Client taxonomy
336ebd06eSchristos  * Copyright (c) 2015 Google, Inc.
436ebd06eSchristos  *
536ebd06eSchristos  * This software may be distributed under the terms of the BSD license.
636ebd06eSchristos  * See README for more details.
736ebd06eSchristos  *
836ebd06eSchristos  * Parse a series of IEs, as in Probe Request or (Re)Association Request frames,
936ebd06eSchristos  * and render them to a descriptive string. The tag number of standard options
1036ebd06eSchristos  * is written to the string, while the vendor ID and subtag are written for
1136ebd06eSchristos  * vendor options.
1236ebd06eSchristos  *
1336ebd06eSchristos  * Example strings:
1436ebd06eSchristos  * 0,1,50,45,221(00904c,51)
1536ebd06eSchristos  * 0,1,33,36,48,45,221(00904c,51),221(0050f2,2)
1636ebd06eSchristos  */
1736ebd06eSchristos 
1836ebd06eSchristos #include "utils/includes.h"
1936ebd06eSchristos 
2036ebd06eSchristos #include "utils/common.h"
2136ebd06eSchristos #include "common/wpa_ctrl.h"
2236ebd06eSchristos #include "hostapd.h"
2336ebd06eSchristos #include "sta_info.h"
24*0a73ee0aSchristos #include "taxonomy.h"
2536ebd06eSchristos 
2636ebd06eSchristos 
2736ebd06eSchristos /* Copy a string with no funny schtuff allowed; only alphanumerics. */
no_mischief_strncpy(char * dst,const char * src,size_t n)2836ebd06eSchristos static void no_mischief_strncpy(char *dst, const char *src, size_t n)
2936ebd06eSchristos {
3036ebd06eSchristos 	size_t i;
3136ebd06eSchristos 
3236ebd06eSchristos 	for (i = 0; i < n; i++) {
3336ebd06eSchristos 		unsigned char s = src[i];
3436ebd06eSchristos 		int is_lower = s >= 'a' && s <= 'z';
3536ebd06eSchristos 		int is_upper = s >= 'A' && s <= 'Z';
3636ebd06eSchristos 		int is_digit = s >= '0' && s <= '9';
3736ebd06eSchristos 
3836ebd06eSchristos 		if (is_lower || is_upper || is_digit) {
3936ebd06eSchristos 			/* TODO: if any manufacturer uses Unicode within the
4036ebd06eSchristos 			 * WPS header, it will get mangled here. */
4136ebd06eSchristos 			dst[i] = s;
4236ebd06eSchristos 		} else {
4336ebd06eSchristos 			/* Note that even spaces will be transformed to
4436ebd06eSchristos 			 * underscores, so 'Nexus 7' will turn into 'Nexus_7'.
4536ebd06eSchristos 			 * This is deliberate, to make the string easier to
4636ebd06eSchristos 			 * parse. */
4736ebd06eSchristos 			dst[i] = '_';
4836ebd06eSchristos 		}
4936ebd06eSchristos 	}
5036ebd06eSchristos }
5136ebd06eSchristos 
5236ebd06eSchristos 
get_wps_name(char * name,size_t name_len,const u8 * data,size_t data_len)5336ebd06eSchristos static int get_wps_name(char *name, size_t name_len,
5436ebd06eSchristos 			const u8 *data, size_t data_len)
5536ebd06eSchristos {
5636ebd06eSchristos 	/* Inside the WPS IE are a series of attributes, using two byte IDs
5736ebd06eSchristos 	 * and two byte lengths. We're looking for the model name, if
5836ebd06eSchristos 	 * present. */
5936ebd06eSchristos 	while (data_len >= 4) {
6036ebd06eSchristos 		u16 id, elen;
6136ebd06eSchristos 
6236ebd06eSchristos 		id = WPA_GET_BE16(data);
6336ebd06eSchristos 		elen = WPA_GET_BE16(data + 2);
6436ebd06eSchristos 		data += 4;
6536ebd06eSchristos 		data_len -= 4;
6636ebd06eSchristos 
6736ebd06eSchristos 		if (elen > data_len)
6836ebd06eSchristos 			return 0;
6936ebd06eSchristos 
7036ebd06eSchristos 		if (id == 0x1023) {
7136ebd06eSchristos 			/* Model name, like 'Nexus 7' */
7236ebd06eSchristos 			size_t n = (elen < name_len) ? elen : name_len;
7336ebd06eSchristos 			no_mischief_strncpy(name, (const char *) data, n);
7436ebd06eSchristos 			return n;
7536ebd06eSchristos 		}
7636ebd06eSchristos 
7736ebd06eSchristos 		data += elen;
7836ebd06eSchristos 		data_len -= elen;
7936ebd06eSchristos 	}
8036ebd06eSchristos 
8136ebd06eSchristos 	return 0;
8236ebd06eSchristos }
8336ebd06eSchristos 
8436ebd06eSchristos 
ie_to_string(char * fstr,size_t fstr_len,const struct wpabuf * ies)8536ebd06eSchristos static void ie_to_string(char *fstr, size_t fstr_len, const struct wpabuf *ies)
8636ebd06eSchristos {
8736ebd06eSchristos 	char *fpos = fstr;
8836ebd06eSchristos 	char *fend = fstr + fstr_len;
8936ebd06eSchristos 	char htcap[7 + 4 + 1]; /* ",htcap:" + %04hx + trailing NUL */
9036ebd06eSchristos 	char htagg[7 + 2 + 1]; /* ",htagg:" + %02hx + trailing NUL */
9136ebd06eSchristos 	char htmcs[7 + 8 + 1]; /* ",htmcs:" + %08x + trailing NUL */
9236ebd06eSchristos 	char vhtcap[8 + 8 + 1]; /* ",vhtcap:" + %08x + trailing NUL */
9336ebd06eSchristos 	char vhtrxmcs[10 + 8 + 1]; /* ",vhtrxmcs:" + %08x + trailing NUL */
9436ebd06eSchristos 	char vhttxmcs[10 + 8 + 1]; /* ",vhttxmcs:" + %08x + trailing NUL */
9536ebd06eSchristos #define MAX_EXTCAP	254
9636ebd06eSchristos 	char extcap[8 + 2 * MAX_EXTCAP + 1]; /* ",extcap:" + hex + trailing NUL
9736ebd06eSchristos 					      */
9836ebd06eSchristos 	char txpow[7 + 4 + 1]; /* ",txpow:" + %04hx + trailing NUL */
9936ebd06eSchristos #define WPS_NAME_LEN		32
10036ebd06eSchristos 	char wps[WPS_NAME_LEN + 5 + 1]; /* room to prepend ",wps:" + trailing
10136ebd06eSchristos 					 * NUL */
10236ebd06eSchristos 	int num = 0;
10336ebd06eSchristos 	const u8 *ie;
10436ebd06eSchristos 	size_t ie_len;
10536ebd06eSchristos 	int ret;
10636ebd06eSchristos 
10736ebd06eSchristos 	os_memset(htcap, 0, sizeof(htcap));
10836ebd06eSchristos 	os_memset(htagg, 0, sizeof(htagg));
10936ebd06eSchristos 	os_memset(htmcs, 0, sizeof(htmcs));
11036ebd06eSchristos 	os_memset(vhtcap, 0, sizeof(vhtcap));
11136ebd06eSchristos 	os_memset(vhtrxmcs, 0, sizeof(vhtrxmcs));
11236ebd06eSchristos 	os_memset(vhttxmcs, 0, sizeof(vhttxmcs));
11336ebd06eSchristos 	os_memset(extcap, 0, sizeof(extcap));
11436ebd06eSchristos 	os_memset(txpow, 0, sizeof(txpow));
11536ebd06eSchristos 	os_memset(wps, 0, sizeof(wps));
11636ebd06eSchristos 	*fpos = '\0';
11736ebd06eSchristos 
11836ebd06eSchristos 	if (!ies)
11936ebd06eSchristos 		return;
12036ebd06eSchristos 	ie = wpabuf_head(ies);
12136ebd06eSchristos 	ie_len = wpabuf_len(ies);
12236ebd06eSchristos 
12336ebd06eSchristos 	while (ie_len >= 2) {
12436ebd06eSchristos 		u8 id, elen;
12536ebd06eSchristos 		char *sep = (num++ == 0) ? "" : ",";
12636ebd06eSchristos 
12736ebd06eSchristos 		id = *ie++;
12836ebd06eSchristos 		elen = *ie++;
12936ebd06eSchristos 		ie_len -= 2;
13036ebd06eSchristos 
13136ebd06eSchristos 		if (elen > ie_len)
13236ebd06eSchristos 			break;
13336ebd06eSchristos 
13436ebd06eSchristos 		if (id == WLAN_EID_VENDOR_SPECIFIC && elen >= 4) {
13536ebd06eSchristos 			/* Vendor specific */
13636ebd06eSchristos 			if (WPA_GET_BE32(ie) == WPS_IE_VENDOR_TYPE) {
13736ebd06eSchristos 				/* WPS */
13836ebd06eSchristos 				char model_name[WPS_NAME_LEN + 1];
13936ebd06eSchristos 				const u8 *data = &ie[4];
14036ebd06eSchristos 				size_t data_len = elen - 4;
14136ebd06eSchristos 
14236ebd06eSchristos 				os_memset(model_name, 0, sizeof(model_name));
14336ebd06eSchristos 				if (get_wps_name(model_name, WPS_NAME_LEN, data,
14436ebd06eSchristos 						 data_len)) {
14536ebd06eSchristos 					os_snprintf(wps, sizeof(wps),
14636ebd06eSchristos 						    ",wps:%s", model_name);
14736ebd06eSchristos 				}
14836ebd06eSchristos 			}
14936ebd06eSchristos 
15036ebd06eSchristos 			ret = os_snprintf(fpos, fend - fpos,
15136ebd06eSchristos 					  "%s%d(%02x%02x%02x,%d)",
15236ebd06eSchristos 					  sep, id, ie[0], ie[1], ie[2], ie[3]);
15336ebd06eSchristos 		} else {
15436ebd06eSchristos 			if (id == WLAN_EID_HT_CAP && elen >= 2) {
15536ebd06eSchristos 				/* HT Capabilities (802.11n) */
15636ebd06eSchristos 				os_snprintf(htcap, sizeof(htcap),
15736ebd06eSchristos 					    ",htcap:%04hx",
15836ebd06eSchristos 					    WPA_GET_LE16(ie));
15936ebd06eSchristos 			}
16036ebd06eSchristos 			if (id == WLAN_EID_HT_CAP && elen >= 3) {
16136ebd06eSchristos 				/* HT Capabilities (802.11n), A-MPDU information
16236ebd06eSchristos 				 */
16336ebd06eSchristos 				os_snprintf(htagg, sizeof(htagg),
16436ebd06eSchristos 					    ",htagg:%02hx", (u16) ie[2]);
16536ebd06eSchristos 			}
16636ebd06eSchristos 			if (id == WLAN_EID_HT_CAP && elen >= 7) {
16736ebd06eSchristos 				/* HT Capabilities (802.11n), MCS information */
16836ebd06eSchristos 				os_snprintf(htmcs, sizeof(htmcs),
16936ebd06eSchristos 					    ",htmcs:%08hx",
17036ebd06eSchristos 					    (u16) WPA_GET_LE32(ie + 3));
17136ebd06eSchristos 			}
17236ebd06eSchristos 			if (id == WLAN_EID_VHT_CAP && elen >= 4) {
17336ebd06eSchristos 				/* VHT Capabilities (802.11ac) */
17436ebd06eSchristos 				os_snprintf(vhtcap, sizeof(vhtcap),
17536ebd06eSchristos 					    ",vhtcap:%08x",
17636ebd06eSchristos 					    WPA_GET_LE32(ie));
17736ebd06eSchristos 			}
17836ebd06eSchristos 			if (id == WLAN_EID_VHT_CAP && elen >= 8) {
17936ebd06eSchristos 				/* VHT Capabilities (802.11ac), RX MCS
18036ebd06eSchristos 				 * information */
18136ebd06eSchristos 				os_snprintf(vhtrxmcs, sizeof(vhtrxmcs),
18236ebd06eSchristos 					    ",vhtrxmcs:%08x",
18336ebd06eSchristos 					    WPA_GET_LE32(ie + 4));
18436ebd06eSchristos 			}
18536ebd06eSchristos 			if (id == WLAN_EID_VHT_CAP && elen >= 12) {
18636ebd06eSchristos 				/* VHT Capabilities (802.11ac), TX MCS
18736ebd06eSchristos 				 * information */
18836ebd06eSchristos 				os_snprintf(vhttxmcs, sizeof(vhttxmcs),
18936ebd06eSchristos 					    ",vhttxmcs:%08x",
19036ebd06eSchristos 					    WPA_GET_LE32(ie + 8));
19136ebd06eSchristos 			}
19236ebd06eSchristos 			if (id == WLAN_EID_EXT_CAPAB) {
19336ebd06eSchristos 				/* Extended Capabilities */
19436ebd06eSchristos 				int i;
19536ebd06eSchristos 				int len = (elen < MAX_EXTCAP) ? elen :
19636ebd06eSchristos 					MAX_EXTCAP;
19736ebd06eSchristos 				char *p = extcap;
19836ebd06eSchristos 
19936ebd06eSchristos 				p += os_snprintf(extcap, sizeof(extcap),
20036ebd06eSchristos 						 ",extcap:");
20136ebd06eSchristos 				for (i = 0; i < len; i++) {
20236ebd06eSchristos 					int lim;
20336ebd06eSchristos 
20436ebd06eSchristos 					lim = sizeof(extcap) -
20536ebd06eSchristos 						os_strlen(extcap);
20636ebd06eSchristos 					if (lim <= 0)
20736ebd06eSchristos 						break;
20836ebd06eSchristos 					p += os_snprintf(p, lim, "%02x",
20936ebd06eSchristos 							 *(ie + i));
21036ebd06eSchristos 				}
21136ebd06eSchristos 			}
21236ebd06eSchristos 			if (id == WLAN_EID_PWR_CAPABILITY && elen == 2) {
21336ebd06eSchristos 				/* TX Power */
21436ebd06eSchristos 				os_snprintf(txpow, sizeof(txpow),
21536ebd06eSchristos 					    ",txpow:%04hx",
21636ebd06eSchristos 					    WPA_GET_LE16(ie));
21736ebd06eSchristos 			}
21836ebd06eSchristos 
21936ebd06eSchristos 			ret = os_snprintf(fpos, fend - fpos, "%s%d", sep, id);
22036ebd06eSchristos 		}
22136ebd06eSchristos 		if (os_snprintf_error(fend - fpos, ret))
22236ebd06eSchristos 			goto fail;
22336ebd06eSchristos 		fpos += ret;
22436ebd06eSchristos 
22536ebd06eSchristos 		ie += elen;
22636ebd06eSchristos 		ie_len -= elen;
22736ebd06eSchristos 	}
22836ebd06eSchristos 
22936ebd06eSchristos 	ret = os_snprintf(fpos, fend - fpos, "%s%s%s%s%s%s%s%s%s",
23036ebd06eSchristos 			  htcap, htagg, htmcs, vhtcap, vhtrxmcs, vhttxmcs,
23136ebd06eSchristos 			  txpow, extcap, wps);
23236ebd06eSchristos 	if (os_snprintf_error(fend - fpos, ret)) {
23336ebd06eSchristos 	fail:
23436ebd06eSchristos 		fstr[0] = '\0';
23536ebd06eSchristos 	}
23636ebd06eSchristos }
23736ebd06eSchristos 
23836ebd06eSchristos 
retrieve_sta_taxonomy(const struct hostapd_data * hapd,struct sta_info * sta,char * buf,size_t buflen)23936ebd06eSchristos int retrieve_sta_taxonomy(const struct hostapd_data *hapd,
24036ebd06eSchristos 			  struct sta_info *sta, char *buf, size_t buflen)
24136ebd06eSchristos {
24236ebd06eSchristos 	int ret;
24336ebd06eSchristos 	char *pos, *end;
24436ebd06eSchristos 
24536ebd06eSchristos 	if (!sta->probe_ie_taxonomy || !sta->assoc_ie_taxonomy)
24636ebd06eSchristos 		return 0;
24736ebd06eSchristos 
24836ebd06eSchristos 	ret = os_snprintf(buf, buflen, "wifi4|probe:");
24936ebd06eSchristos 	if (os_snprintf_error(buflen, ret))
25036ebd06eSchristos 		return 0;
25136ebd06eSchristos 	pos = buf + ret;
25236ebd06eSchristos 	end = buf + buflen;
25336ebd06eSchristos 
25436ebd06eSchristos 	ie_to_string(pos, end - pos, sta->probe_ie_taxonomy);
25536ebd06eSchristos 	pos = os_strchr(pos, '\0');
25636ebd06eSchristos 	if (pos >= end)
25736ebd06eSchristos 		return 0;
25836ebd06eSchristos 	ret = os_snprintf(pos, end - pos, "|assoc:");
25936ebd06eSchristos 	if (os_snprintf_error(end - pos, ret))
26036ebd06eSchristos 		return 0;
26136ebd06eSchristos 	pos += ret;
26236ebd06eSchristos 	ie_to_string(pos, end - pos, sta->assoc_ie_taxonomy);
26336ebd06eSchristos 	pos = os_strchr(pos, '\0');
26436ebd06eSchristos 	return pos - buf;
26536ebd06eSchristos }
26636ebd06eSchristos 
26736ebd06eSchristos 
taxonomy_sta_info_probe_req(const struct hostapd_data * hapd,struct sta_info * sta,const u8 * ie,size_t ie_len)26836ebd06eSchristos void taxonomy_sta_info_probe_req(const struct hostapd_data *hapd,
26936ebd06eSchristos 				 struct sta_info *sta,
27036ebd06eSchristos 				 const u8 *ie, size_t ie_len)
27136ebd06eSchristos {
27236ebd06eSchristos 	wpabuf_free(sta->probe_ie_taxonomy);
27336ebd06eSchristos 	sta->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
27436ebd06eSchristos }
27536ebd06eSchristos 
27636ebd06eSchristos 
taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data * hapd,struct hostapd_sta_info * info,const u8 * ie,size_t ie_len)27736ebd06eSchristos void taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data *hapd,
27836ebd06eSchristos 					 struct hostapd_sta_info *info,
27936ebd06eSchristos 					 const u8 *ie, size_t ie_len)
28036ebd06eSchristos {
28136ebd06eSchristos 	wpabuf_free(info->probe_ie_taxonomy);
28236ebd06eSchristos 	info->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
28336ebd06eSchristos }
28436ebd06eSchristos 
28536ebd06eSchristos 
taxonomy_sta_info_assoc_req(const struct hostapd_data * hapd,struct sta_info * sta,const u8 * ie,size_t ie_len)28636ebd06eSchristos void taxonomy_sta_info_assoc_req(const struct hostapd_data *hapd,
28736ebd06eSchristos 				 struct sta_info *sta,
28836ebd06eSchristos 				 const u8 *ie, size_t ie_len)
28936ebd06eSchristos {
29036ebd06eSchristos 	wpabuf_free(sta->assoc_ie_taxonomy);
29136ebd06eSchristos 	sta->assoc_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
29236ebd06eSchristos }
293