1169691Skan/*
2169691Skan * hostapd / WMM (Wi-Fi Multimedia)
3169691Skan * Copyright 2002-2003, Instant802 Networks, Inc.
4169691Skan * Copyright 2005-2006, Devicescape Software, Inc.
5169691Skan * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
6169691Skan *
7169691Skan * This program is free software; you can redistribute it and/or modify
8169691Skan * it under the terms of the GNU General Public License version 2 as
9169691Skan * published by the Free Software Foundation.
10169691Skan *
11169691Skan * Alternatively, this software may be distributed under the terms of BSD
12169691Skan * license.
13169691Skan *
14169691Skan * See README and COPYING for more details.
15169691Skan */
16169691Skan
17169691Skan#include "utils/includes.h"
18169691Skan
19169691Skan#include "utils/common.h"
20169691Skan#include "common/ieee802_11_defs.h"
21169691Skan#include "common/ieee802_11_common.h"
22169691Skan#include "hostapd.h"
23169691Skan#include "ieee802_11.h"
24169691Skan#include "sta_info.h"
25169691Skan#include "ap_config.h"
26169691Skan#include "wmm.h"
27169691Skan
28169691Skan
29169691Skan/* TODO: maintain separate sequence and fragment numbers for each AC
30169691Skan * TODO: IGMP snooping to track which multicasts to forward - and use QOS-DATA
31169691Skan * if only WMM stations are receiving a certain group */
32169691Skan
33169691Skan
34169691Skanstatic inline u8 wmm_aci_aifsn(int aifsn, int acm, int aci)
35169691Skan{
36169691Skan	u8 ret;
37169691Skan	ret = (aifsn << WMM_AC_AIFNS_SHIFT) & WMM_AC_AIFSN_MASK;
38169691Skan	if (acm)
39169691Skan		ret |= WMM_AC_ACM;
40169691Skan	ret |= (aci << WMM_AC_ACI_SHIFT) & WMM_AC_ACI_MASK;
41169691Skan	return ret;
42169691Skan}
43169691Skan
44169691Skan
45169691Skanstatic inline u8 wmm_ecw(int ecwmin, int ecwmax)
46169691Skan{
47169691Skan	return ((ecwmin << WMM_AC_ECWMIN_SHIFT) & WMM_AC_ECWMIN_MASK) |
48169691Skan		((ecwmax << WMM_AC_ECWMAX_SHIFT) & WMM_AC_ECWMAX_MASK);
49169691Skan}
50169691Skan
51169691Skan
52169691Skan/*
53169691Skan * Add WMM Parameter Element to Beacon, Probe Response, and (Re)Association
54169691Skan * Response frames.
55169691Skan */
56169691Skanu8 * hostapd_eid_wmm(struct hostapd_data *hapd, u8 *eid)
57169691Skan{
58169691Skan	u8 *pos = eid;
59169691Skan	struct wmm_parameter_element *wmm =
60169691Skan		(struct wmm_parameter_element *) (pos + 2);
61169691Skan	int e;
62169691Skan
63169691Skan	if (!hapd->conf->wmm_enabled)
64169691Skan		return eid;
65169691Skan	eid[0] = WLAN_EID_VENDOR_SPECIFIC;
66169691Skan	wmm->oui[0] = 0x00;
67169691Skan	wmm->oui[1] = 0x50;
68169691Skan	wmm->oui[2] = 0xf2;
69169691Skan	wmm->oui_type = WMM_OUI_TYPE;
70169691Skan	wmm->oui_subtype = WMM_OUI_SUBTYPE_PARAMETER_ELEMENT;
71169691Skan	wmm->version = WMM_VERSION;
72169691Skan	wmm->qos_info = hapd->parameter_set_count & 0xf;
73169691Skan
74169691Skan	if (hapd->conf->wmm_uapsd)
75169691Skan		wmm->qos_info |= 0x80;
76169691Skan
77169691Skan	/* fill in a parameter set record for each AC */
78169691Skan	for (e = 0; e < 4; e++) {
79169691Skan		struct wmm_ac_parameter *ac = &wmm->ac[e];
80169691Skan		struct hostapd_wmm_ac_params *acp =
81169691Skan			&hapd->iconf->wmm_ac_params[e];
82169691Skan
83169691Skan		ac->aci_aifsn = wmm_aci_aifsn(acp->aifs,
84169691Skan					      acp->admission_control_mandatory,
85169691Skan					      e);
86169691Skan		ac->cw = wmm_ecw(acp->cwmin, acp->cwmax);
87169691Skan		ac->txop_limit = host_to_le16(acp->txop_limit);
88169691Skan	}
89169691Skan
90169691Skan	pos = (u8 *) (wmm + 1);
91169691Skan	eid[1] = pos - eid - 2; /* element length */
92169691Skan
93169691Skan	return pos;
94169691Skan}
95169691Skan
96169691Skan
97169691Skan/* This function is called when a station sends an association request with
98169691Skan * WMM info element. The function returns zero on success or non-zero on any
99169691Skan * error in WMM element. eid does not include Element ID and Length octets. */
100169691Skanint hostapd_eid_wmm_valid(struct hostapd_data *hapd, const u8 *eid, size_t len)
101169691Skan{
102169691Skan	struct wmm_information_element *wmm;
103169691Skan
104169691Skan	wpa_hexdump(MSG_MSGDUMP, "WMM IE", eid, len);
105169691Skan
106169691Skan	if (len < sizeof(struct wmm_information_element)) {
107169691Skan		wpa_printf(MSG_DEBUG, "Too short WMM IE (len=%lu)",
108169691Skan			   (unsigned long) len);
109169691Skan		return -1;
110169691Skan	}
111169691Skan
112169691Skan	wmm = (struct wmm_information_element *) eid;
113169691Skan	wpa_printf(MSG_DEBUG, "Validating WMM IE: OUI %02x:%02x:%02x  "
114169691Skan		   "OUI type %d  OUI sub-type %d  version %d  QoS info 0x%x",
115169691Skan		   wmm->oui[0], wmm->oui[1], wmm->oui[2], wmm->oui_type,
116169691Skan		   wmm->oui_subtype, wmm->version, wmm->qos_info);
117169691Skan	if (wmm->oui_subtype != WMM_OUI_SUBTYPE_INFORMATION_ELEMENT ||
118169691Skan	    wmm->version != WMM_VERSION) {
119169691Skan		wpa_printf(MSG_DEBUG, "Unsupported WMM IE Subtype/Version");
120169691Skan		return -1;
121169691Skan	}
122169691Skan
123169691Skan	return 0;
124169691Skan}
125169691Skan
126169691Skan
127169691Skanstatic void wmm_send_action(struct hostapd_data *hapd, const u8 *addr,
128169691Skan			    const struct wmm_tspec_element *tspec,
129169691Skan			    u8 action_code, u8 dialogue_token, u8 status_code)
130169691Skan{
131169691Skan	u8 buf[256];
132169691Skan	struct ieee80211_mgmt *m = (struct ieee80211_mgmt *) buf;
133169691Skan	struct wmm_tspec_element *t = (struct wmm_tspec_element *)
134169691Skan		m->u.action.u.wmm_action.variable;
135169691Skan	int len;
136169691Skan
137169691Skan	hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211,
138169691Skan		       HOSTAPD_LEVEL_DEBUG,
139169691Skan		       "action response - reason %d", status_code);
140169691Skan	os_memset(buf, 0, sizeof(buf));
141169691Skan	m->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
142169691Skan					WLAN_FC_STYPE_ACTION);
143169691Skan	os_memcpy(m->da, addr, ETH_ALEN);
144169691Skan	os_memcpy(m->sa, hapd->own_addr, ETH_ALEN);
145169691Skan	os_memcpy(m->bssid, hapd->own_addr, ETH_ALEN);
146169691Skan	m->u.action.category = WLAN_ACTION_WMM;
147169691Skan	m->u.action.u.wmm_action.action_code = action_code;
148169691Skan	m->u.action.u.wmm_action.dialog_token = dialogue_token;
149169691Skan	m->u.action.u.wmm_action.status_code = status_code;
150169691Skan	os_memcpy(t, tspec, sizeof(struct wmm_tspec_element));
151169691Skan	len = ((u8 *) (t + 1)) - buf;
152169691Skan
153169691Skan	if (hapd->drv.send_mgmt_frame(hapd, m, len) < 0)
154169691Skan		perror("wmm_send_action: send");
155169691Skan}
156169691Skan
157169691Skan
158169691Skanint wmm_process_tspec(struct wmm_tspec_element *tspec)
159169691Skan{
160169691Skan	int medium_time, pps, duration;
161169691Skan	int up, psb, dir, tid;
162169691Skan	u16 val, surplus;
163169691Skan
164169691Skan	up = (tspec->ts_info[1] >> 3) & 0x07;
165169691Skan	psb = (tspec->ts_info[1] >> 2) & 0x01;
166169691Skan	dir = (tspec->ts_info[0] >> 5) & 0x03;
167169691Skan	tid = (tspec->ts_info[0] >> 1) & 0x0f;
168169691Skan	wpa_printf(MSG_DEBUG, "WMM: TS Info: UP=%d PSB=%d Direction=%d TID=%d",
169169691Skan		   up, psb, dir, tid);
170169691Skan	val = le_to_host16(tspec->nominal_msdu_size);
171169691Skan	wpa_printf(MSG_DEBUG, "WMM: Nominal MSDU Size: %d%s",
172169691Skan		   val & 0x7fff, val & 0x8000 ? " (fixed)" : "");
173169691Skan	wpa_printf(MSG_DEBUG, "WMM: Mean Data Rate: %u bps",
174169691Skan		   le_to_host32(tspec->mean_data_rate));
175169691Skan	wpa_printf(MSG_DEBUG, "WMM: Minimum PHY Rate: %u bps",
176169691Skan		   le_to_host32(tspec->minimum_phy_rate));
177169691Skan	val = le_to_host16(tspec->surplus_bandwidth_allowance);
178169691Skan	wpa_printf(MSG_DEBUG, "WMM: Surplus Bandwidth Allowance: %u.%04u",
179169691Skan		   val >> 13, 10000 * (val & 0x1fff) / 0x2000);
180169691Skan
181169691Skan	val = le_to_host16(tspec->nominal_msdu_size);
182169691Skan	if (val == 0) {
183169691Skan		wpa_printf(MSG_DEBUG, "WMM: Invalid Nominal MSDU Size (0)");
184169691Skan		return WMM_ADDTS_STATUS_INVALID_PARAMETERS;
185169691Skan	}
186169691Skan	/* pps = Ceiling((Mean Data Rate / 8) / Nominal MSDU Size) */
187169691Skan	pps = ((le_to_host32(tspec->mean_data_rate) / 8) + val - 1) / val;
188169691Skan	wpa_printf(MSG_DEBUG, "WMM: Packets-per-second estimate for TSPEC: %d",
189169691Skan		   pps);
190169691Skan
191169691Skan	if (le_to_host32(tspec->minimum_phy_rate) < 1000000) {
192169691Skan		wpa_printf(MSG_DEBUG, "WMM: Too small Minimum PHY Rate");
193169691Skan		return WMM_ADDTS_STATUS_INVALID_PARAMETERS;
194169691Skan	}
195169691Skan
196169691Skan	duration = (le_to_host16(tspec->nominal_msdu_size) & 0x7fff) * 8 /
197169691Skan		(le_to_host32(tspec->minimum_phy_rate) / 1000000) +
198169691Skan		50 /* FIX: proper SIFS + ACK duration */;
199169691Skan
200169691Skan	/* unsigned binary number with an implicit binary point after the
201169691Skan	 * leftmost 3 bits, i.e., 0x2000 = 1.0 */
202169691Skan	surplus = le_to_host16(tspec->surplus_bandwidth_allowance);
203169691Skan	if (surplus <= 0x2000) {
204169691Skan		wpa_printf(MSG_DEBUG, "WMM: Surplus Bandwidth Allowance not "
205169691Skan			   "greater than unity");
206169691Skan		return WMM_ADDTS_STATUS_INVALID_PARAMETERS;
207169691Skan	}
208169691Skan
209169691Skan	medium_time = surplus * pps * duration / 0x2000;
210169691Skan	wpa_printf(MSG_DEBUG, "WMM: Estimated medium time: %u", medium_time);
211169691Skan
212169691Skan	/*
213169691Skan	 * TODO: store list of granted (and still active) TSPECs and check
214169691Skan	 * whether there is available medium time for this request. For now,
215169691Skan	 * just refuse requests that would by themselves take very large
216169691Skan	 * portion of the available bandwidth.
217169691Skan	 */
218169691Skan	if (medium_time > 750000) {
219169691Skan		wpa_printf(MSG_DEBUG, "WMM: Refuse TSPEC request for over "
220169691Skan			   "75%% of available bandwidth");
221169691Skan		return WMM_ADDTS_STATUS_REFUSED;
222169691Skan	}
223169691Skan
224169691Skan	/* Convert to 32 microseconds per second unit */
225169691Skan	tspec->medium_time = host_to_le16(medium_time / 32);
226169691Skan
227169691Skan	return WMM_ADDTS_STATUS_ADMISSION_ACCEPTED;
228169691Skan}
229169691Skan
230169691Skan
231169691Skanstatic void wmm_addts_req(struct hostapd_data *hapd,
232169691Skan			  const struct ieee80211_mgmt *mgmt,
233169691Skan			  struct wmm_tspec_element *tspec, size_t len)
234169691Skan{
235169691Skan	const u8 *end = ((const u8 *) mgmt) + len;
236169691Skan	int res;
237169691Skan
238169691Skan	if ((const u8 *) (tspec + 1) > end) {
239169691Skan		wpa_printf(MSG_DEBUG, "WMM: TSPEC overflow in ADDTS Request");
240169691Skan		return;
241169691Skan	}
242169691Skan
243169691Skan	wpa_printf(MSG_DEBUG, "WMM: ADDTS Request (Dialog Token %d) for TSPEC "
244169691Skan		   "from " MACSTR,
245169691Skan		   mgmt->u.action.u.wmm_action.dialog_token,
246169691Skan		   MAC2STR(mgmt->sa));
247169691Skan
248169691Skan	res = wmm_process_tspec(tspec);
249169691Skan	wpa_printf(MSG_DEBUG, "WMM: ADDTS processing result: %d", res);
250169691Skan
251169691Skan	wmm_send_action(hapd, mgmt->sa, tspec, WMM_ACTION_CODE_ADDTS_RESP,
252169691Skan			mgmt->u.action.u.wmm_action.dialog_token, res);
253169691Skan}
254169691Skan
255169691Skan
256169691Skanvoid hostapd_wmm_action(struct hostapd_data *hapd,
257169691Skan			const struct ieee80211_mgmt *mgmt, size_t len)
258169691Skan{
259169691Skan	int action_code;
260169691Skan	int left = len - IEEE80211_HDRLEN - 4;
261169691Skan	const u8 *pos = ((const u8 *) mgmt) + IEEE80211_HDRLEN + 4;
262169691Skan	struct ieee802_11_elems elems;
263169691Skan	struct sta_info *sta = ap_get_sta(hapd, mgmt->sa);
264169691Skan
265169691Skan	/* check that the request comes from a valid station */
266169691Skan	if (!sta ||
267169691Skan	    (sta->flags & (WLAN_STA_ASSOC | WLAN_STA_WMM)) !=
268169691Skan	    (WLAN_STA_ASSOC | WLAN_STA_WMM)) {
269169691Skan		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
270169691Skan			       HOSTAPD_LEVEL_DEBUG,
271169691Skan			       "wmm action received is not from associated wmm"
272169691Skan			       " station");
273169691Skan		/* TODO: respond with action frame refused status code */
274169691Skan		return;
275169691Skan	}
276169691Skan
277169691Skan	/* extract the tspec info element */
278169691Skan	if (ieee802_11_parse_elems(pos, left, &elems, 1) == ParseFailed) {
279169691Skan		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
280169691Skan			       HOSTAPD_LEVEL_DEBUG,
281169691Skan			       "hostapd_wmm_action - could not parse wmm "
282169691Skan			       "action");
283169691Skan		/* TODO: respond with action frame invalid parameters status
284169691Skan		 * code */
285169691Skan		return;
286169691Skan	}
287169691Skan
288169691Skan	if (!elems.wmm_tspec ||
289169691Skan	    elems.wmm_tspec_len != (sizeof(struct wmm_tspec_element) - 2)) {
290169691Skan		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
291169691Skan			       HOSTAPD_LEVEL_DEBUG,
292169691Skan			       "hostapd_wmm_action - missing or wrong length "
293169691Skan			       "tspec");
294169691Skan		/* TODO: respond with action frame invalid parameters status
295169691Skan		 * code */
296169691Skan		return;
297169691Skan	}
298169691Skan
299169691Skan	/* TODO: check the request is for an AC with ACM set, if not, refuse
300169691Skan	 * request */
301169691Skan
302169691Skan	action_code = mgmt->u.action.u.wmm_action.action_code;
303169691Skan	switch (action_code) {
304169691Skan	case WMM_ACTION_CODE_ADDTS_REQ:
305169691Skan		wmm_addts_req(hapd, mgmt, (struct wmm_tspec_element *)
306169691Skan			      (elems.wmm_tspec - 2), len);
307169691Skan		return;
308169691Skan#if 0
309169691Skan	/* TODO: needed for client implementation */
310169691Skan	case WMM_ACTION_CODE_ADDTS_RESP:
311169691Skan		wmm_setup_request(hapd, mgmt, len);
312169691Skan		return;
313169691Skan	/* TODO: handle station teardown requests */
314169691Skan	case WMM_ACTION_CODE_DELTS:
315169691Skan		wmm_teardown(hapd, mgmt, len);
316169691Skan		return;
317169691Skan#endif
318169691Skan	}
319169691Skan
320169691Skan	hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
321169691Skan		       HOSTAPD_LEVEL_DEBUG,
322169691Skan		       "hostapd_wmm_action - unknown action code %d",
323169691Skan		       action_code);
324169691Skan}
325169691Skan