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