1 /*
2  * hostapd - MBO
3  * Copyright (c) 2016, Qualcomm Atheros, Inc.
4  *
5  * This software may be distributed under the terms of the BSD license.
6  * See README for more details.
7  */
8 
9 #include "utils/includes.h"
10 
11 #include "utils/common.h"
12 #include "common/ieee802_11_defs.h"
13 #include "common/ieee802_11_common.h"
14 #include "hostapd.h"
15 #include "sta_info.h"
16 #include "mbo_ap.h"
17 
18 
mbo_ap_sta_free(struct sta_info * sta)19 void mbo_ap_sta_free(struct sta_info *sta)
20 {
21 	struct mbo_non_pref_chan_info *info, *prev;
22 
23 	info = sta->non_pref_chan;
24 	sta->non_pref_chan = NULL;
25 	while (info) {
26 		prev = info;
27 		info = info->next;
28 		os_free(prev);
29 	}
30 }
31 
32 
mbo_ap_parse_non_pref_chan(struct sta_info * sta,const u8 * buf,size_t len)33 static void mbo_ap_parse_non_pref_chan(struct sta_info *sta,
34 				       const u8 *buf, size_t len)
35 {
36 	struct mbo_non_pref_chan_info *info, *tmp;
37 	char channels[200], *pos, *end;
38 	size_t num_chan, i;
39 	int ret;
40 
41 	if (len <= 4)
42 		return; /* Not enough room for any channels */
43 
44 	num_chan = len - 4;
45 	info = os_zalloc(sizeof(*info) + num_chan);
46 	if (!info)
47 		return;
48 	info->op_class = buf[0];
49 	info->pref = buf[len - 3];
50 	info->reason_code = buf[len - 2];
51 	info->reason_detail = buf[len - 1];
52 	info->num_channels = num_chan;
53 	buf++;
54 	os_memcpy(info->channels, buf, num_chan);
55 	if (!sta->non_pref_chan) {
56 		sta->non_pref_chan = info;
57 	} else {
58 		tmp = sta->non_pref_chan;
59 		while (tmp->next)
60 			tmp = tmp->next;
61 		tmp->next = info;
62 	}
63 
64 	pos = channels;
65 	end = pos + sizeof(channels);
66 	*pos = '\0';
67 	for (i = 0; i < num_chan; i++) {
68 		ret = os_snprintf(pos, end - pos, "%s%u",
69 				  i == 0 ? "" : " ", buf[i]);
70 		if (os_snprintf_error(end - pos, ret)) {
71 			*pos = '\0';
72 			break;
73 		}
74 		pos += ret;
75 	}
76 
77 	wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR
78 		   " non-preferred channel list (op class %u, pref %u, reason code %u, reason detail %u, channels %s)",
79 		   MAC2STR(sta->addr), info->op_class, info->pref,
80 		   info->reason_code, info->reason_detail, channels);
81 }
82 
83 
mbo_ap_check_sta_assoc(struct hostapd_data * hapd,struct sta_info * sta,struct ieee802_11_elems * elems)84 void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta,
85 			    struct ieee802_11_elems *elems)
86 {
87 	const u8 *pos, *attr, *end;
88 	size_t len;
89 
90 	if (!hapd->conf->mbo_enabled || !elems->mbo)
91 		return;
92 
93 	pos = elems->mbo + 4;
94 	len = elems->mbo_len - 4;
95 	wpa_hexdump(MSG_DEBUG, "MBO: Association Request attributes", pos, len);
96 
97 	attr = get_ie(pos, len, MBO_ATTR_ID_CELL_DATA_CAPA);
98 	if (attr && attr[1] >= 1)
99 		sta->cell_capa = attr[2];
100 
101 	mbo_ap_sta_free(sta);
102 	end = pos + len;
103 	while (end - pos > 1) {
104 		u8 ie_len = pos[1];
105 
106 		if (2 + ie_len > end - pos)
107 			break;
108 
109 		if (pos[0] == MBO_ATTR_ID_NON_PREF_CHAN_REPORT)
110 			mbo_ap_parse_non_pref_chan(sta, pos + 2, ie_len);
111 		pos += 2 + pos[1];
112 	}
113 }
114 
115 
mbo_ap_get_info(struct sta_info * sta,char * buf,size_t buflen)116 int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen)
117 {
118 	char *pos = buf, *end = buf + buflen;
119 	int ret;
120 	struct mbo_non_pref_chan_info *info;
121 	u8 i;
122 	unsigned int count = 0;
123 
124 	if (!sta->cell_capa)
125 		return 0;
126 
127 	ret = os_snprintf(pos, end - pos, "mbo_cell_capa=%u\n", sta->cell_capa);
128 	if (os_snprintf_error(end - pos, ret))
129 		return pos - buf;
130 	pos += ret;
131 
132 	for (info = sta->non_pref_chan; info; info = info->next) {
133 		char *pos2 = pos;
134 
135 		ret = os_snprintf(pos2, end - pos2,
136 				  "non_pref_chan[%u]=%u:%u:%u:%u:",
137 				  count, info->op_class, info->pref,
138 				  info->reason_code, info->reason_detail);
139 		count++;
140 		if (os_snprintf_error(end - pos2, ret))
141 			break;
142 		pos2 += ret;
143 
144 		for (i = 0; i < info->num_channels; i++) {
145 			ret = os_snprintf(pos2, end - pos2, "%u%s",
146 					  info->channels[i],
147 					  i + 1 < info->num_channels ?
148 					  "," : "");
149 			if (os_snprintf_error(end - pos2, ret)) {
150 				pos2 = NULL;
151 				break;
152 			}
153 			pos2 += ret;
154 		}
155 
156 		if (!pos2)
157 			break;
158 		ret = os_snprintf(pos2, end - pos2, "\n");
159 		if (os_snprintf_error(end - pos2, ret))
160 			break;
161 		pos2 += ret;
162 		pos = pos2;
163 	}
164 
165 	return pos - buf;
166 }
167 
168 
mbo_ap_wnm_notif_req_cell_capa(struct sta_info * sta,const u8 * buf,size_t len)169 static void mbo_ap_wnm_notif_req_cell_capa(struct sta_info *sta,
170 					   const u8 *buf, size_t len)
171 {
172 	if (len < 1)
173 		return;
174 	wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR
175 		   " updated cellular data capability: %u",
176 		   MAC2STR(sta->addr), buf[0]);
177 	sta->cell_capa = buf[0];
178 }
179 
180 
mbo_ap_wnm_notif_req_elem(struct sta_info * sta,u8 type,const u8 * buf,size_t len,int * first_non_pref_chan)181 static void mbo_ap_wnm_notif_req_elem(struct sta_info *sta, u8 type,
182 				      const u8 *buf, size_t len,
183 				      int *first_non_pref_chan)
184 {
185 	switch (type) {
186 	case WFA_WNM_NOTIF_SUBELEM_NON_PREF_CHAN_REPORT:
187 		if (*first_non_pref_chan) {
188 			/*
189 			 * Need to free the previously stored entries now to
190 			 * allow the update to replace all entries.
191 			 */
192 			*first_non_pref_chan = 0;
193 			mbo_ap_sta_free(sta);
194 		}
195 		mbo_ap_parse_non_pref_chan(sta, buf, len);
196 		break;
197 	case WFA_WNM_NOTIF_SUBELEM_CELL_DATA_CAPA:
198 		mbo_ap_wnm_notif_req_cell_capa(sta, buf, len);
199 		break;
200 	default:
201 		wpa_printf(MSG_DEBUG,
202 			   "MBO: Ignore unknown WNM Notification WFA subelement %u",
203 			   type);
204 		break;
205 	}
206 }
207 
208 
mbo_ap_wnm_notification_req(struct hostapd_data * hapd,const u8 * addr,const u8 * buf,size_t len)209 void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr,
210 				 const u8 *buf, size_t len)
211 {
212 	const u8 *pos, *end;
213 	u8 ie_len;
214 	struct sta_info *sta;
215 	int first_non_pref_chan = 1;
216 
217 	if (!hapd->conf->mbo_enabled)
218 		return;
219 
220 	sta = ap_get_sta(hapd, addr);
221 	if (!sta)
222 		return;
223 
224 	pos = buf;
225 	end = buf + len;
226 
227 	while (end - pos > 1) {
228 		ie_len = pos[1];
229 
230 		if (2 + ie_len > end - pos)
231 			break;
232 
233 		if (pos[0] == WLAN_EID_VENDOR_SPECIFIC &&
234 		    ie_len >= 4 && WPA_GET_BE24(pos + 2) == OUI_WFA)
235 			mbo_ap_wnm_notif_req_elem(sta, pos[5],
236 						  pos + 6, ie_len - 4,
237 						  &first_non_pref_chan);
238 		else
239 			wpa_printf(MSG_DEBUG,
240 				   "MBO: Ignore unknown WNM Notification element %u (len=%u)",
241 				   pos[0], pos[1]);
242 
243 		pos += 2 + pos[1];
244 	}
245 }
246