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