1214501Srpaulo/*
2214501Srpaulo * hostapd / WMM (Wi-Fi Multimedia)
3214501Srpaulo * Copyright 2002-2003, Instant802 Networks, Inc.
4214501Srpaulo * Copyright 2005-2006, Devicescape Software, Inc.
5214501Srpaulo * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
6214501Srpaulo *
7214501Srpaulo * This program is free software; you can redistribute it and/or modify
8214501Srpaulo * it under the terms of the GNU General Public License version 2 as
9214501Srpaulo * published by the Free Software Foundation.
10214501Srpaulo *
11214501Srpaulo * Alternatively, this software may be distributed under the terms of BSD
12214501Srpaulo * license.
13214501Srpaulo *
14214501Srpaulo * See README and COPYING for more details.
15214501Srpaulo */
16214501Srpaulo
17214501Srpaulo#include "utils/includes.h"
18214501Srpaulo
19214501Srpaulo#include "utils/common.h"
20214501Srpaulo#include "common/ieee802_11_defs.h"
21214501Srpaulo#include "common/ieee802_11_common.h"
22214501Srpaulo#include "hostapd.h"
23214501Srpaulo#include "ieee802_11.h"
24214501Srpaulo#include "sta_info.h"
25214501Srpaulo#include "ap_config.h"
26252726Srpaulo#include "ap_drv_ops.h"
27214501Srpaulo#include "wmm.h"
28214501Srpaulo
29214501Srpaulo
30214501Srpaulo/* TODO: maintain separate sequence and fragment numbers for each AC
31214501Srpaulo * TODO: IGMP snooping to track which multicasts to forward - and use QOS-DATA
32214501Srpaulo * if only WMM stations are receiving a certain group */
33214501Srpaulo
34214501Srpaulo
35214501Srpaulostatic inline u8 wmm_aci_aifsn(int aifsn, int acm, int aci)
36214501Srpaulo{
37214501Srpaulo	u8 ret;
38214501Srpaulo	ret = (aifsn << WMM_AC_AIFNS_SHIFT) & WMM_AC_AIFSN_MASK;
39214501Srpaulo	if (acm)
40214501Srpaulo		ret |= WMM_AC_ACM;
41214501Srpaulo	ret |= (aci << WMM_AC_ACI_SHIFT) & WMM_AC_ACI_MASK;
42214501Srpaulo	return ret;
43214501Srpaulo}
44214501Srpaulo
45214501Srpaulo
46214501Srpaulostatic inline u8 wmm_ecw(int ecwmin, int ecwmax)
47214501Srpaulo{
48214501Srpaulo	return ((ecwmin << WMM_AC_ECWMIN_SHIFT) & WMM_AC_ECWMIN_MASK) |
49214501Srpaulo		((ecwmax << WMM_AC_ECWMAX_SHIFT) & WMM_AC_ECWMAX_MASK);
50214501Srpaulo}
51214501Srpaulo
52214501Srpaulo
53214501Srpaulo/*
54214501Srpaulo * Add WMM Parameter Element to Beacon, Probe Response, and (Re)Association
55214501Srpaulo * Response frames.
56214501Srpaulo */
57214501Srpaulou8 * hostapd_eid_wmm(struct hostapd_data *hapd, u8 *eid)
58214501Srpaulo{
59214501Srpaulo	u8 *pos = eid;
60214501Srpaulo	struct wmm_parameter_element *wmm =
61214501Srpaulo		(struct wmm_parameter_element *) (pos + 2);
62214501Srpaulo	int e;
63214501Srpaulo
64214501Srpaulo	if (!hapd->conf->wmm_enabled)
65214501Srpaulo		return eid;
66214501Srpaulo	eid[0] = WLAN_EID_VENDOR_SPECIFIC;
67214501Srpaulo	wmm->oui[0] = 0x00;
68214501Srpaulo	wmm->oui[1] = 0x50;
69214501Srpaulo	wmm->oui[2] = 0xf2;
70214501Srpaulo	wmm->oui_type = WMM_OUI_TYPE;
71214501Srpaulo	wmm->oui_subtype = WMM_OUI_SUBTYPE_PARAMETER_ELEMENT;
72214501Srpaulo	wmm->version = WMM_VERSION;
73214501Srpaulo	wmm->qos_info = hapd->parameter_set_count & 0xf;
74214501Srpaulo
75252726Srpaulo	if (hapd->conf->wmm_uapsd &&
76252726Srpaulo	    (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_UAPSD))
77214501Srpaulo		wmm->qos_info |= 0x80;
78214501Srpaulo
79252726Srpaulo	wmm->reserved = 0;
80252726Srpaulo
81214501Srpaulo	/* fill in a parameter set record for each AC */
82214501Srpaulo	for (e = 0; e < 4; e++) {
83214501Srpaulo		struct wmm_ac_parameter *ac = &wmm->ac[e];
84214501Srpaulo		struct hostapd_wmm_ac_params *acp =
85214501Srpaulo			&hapd->iconf->wmm_ac_params[e];
86214501Srpaulo
87214501Srpaulo		ac->aci_aifsn = wmm_aci_aifsn(acp->aifs,
88214501Srpaulo					      acp->admission_control_mandatory,
89214501Srpaulo					      e);
90214501Srpaulo		ac->cw = wmm_ecw(acp->cwmin, acp->cwmax);
91214501Srpaulo		ac->txop_limit = host_to_le16(acp->txop_limit);
92214501Srpaulo	}
93214501Srpaulo
94214501Srpaulo	pos = (u8 *) (wmm + 1);
95214501Srpaulo	eid[1] = pos - eid - 2; /* element length */
96214501Srpaulo
97214501Srpaulo	return pos;
98214501Srpaulo}
99214501Srpaulo
100214501Srpaulo
101252726Srpaulo/*
102252726Srpaulo * This function is called when a station sends an association request with
103252726Srpaulo * WMM info element. The function returns 1 on success or 0 on any error in WMM
104252726Srpaulo * element. eid does not include Element ID and Length octets.
105252726Srpaulo */
106214501Srpauloint hostapd_eid_wmm_valid(struct hostapd_data *hapd, const u8 *eid, size_t len)
107214501Srpaulo{
108214501Srpaulo	struct wmm_information_element *wmm;
109214501Srpaulo
110214501Srpaulo	wpa_hexdump(MSG_MSGDUMP, "WMM IE", eid, len);
111214501Srpaulo
112214501Srpaulo	if (len < sizeof(struct wmm_information_element)) {
113214501Srpaulo		wpa_printf(MSG_DEBUG, "Too short WMM IE (len=%lu)",
114214501Srpaulo			   (unsigned long) len);
115252726Srpaulo		return 0;
116214501Srpaulo	}
117214501Srpaulo
118214501Srpaulo	wmm = (struct wmm_information_element *) eid;
119214501Srpaulo	wpa_printf(MSG_DEBUG, "Validating WMM IE: OUI %02x:%02x:%02x  "
120214501Srpaulo		   "OUI type %d  OUI sub-type %d  version %d  QoS info 0x%x",
121214501Srpaulo		   wmm->oui[0], wmm->oui[1], wmm->oui[2], wmm->oui_type,
122214501Srpaulo		   wmm->oui_subtype, wmm->version, wmm->qos_info);
123214501Srpaulo	if (wmm->oui_subtype != WMM_OUI_SUBTYPE_INFORMATION_ELEMENT ||
124214501Srpaulo	    wmm->version != WMM_VERSION) {
125214501Srpaulo		wpa_printf(MSG_DEBUG, "Unsupported WMM IE Subtype/Version");
126252726Srpaulo		return 0;
127214501Srpaulo	}
128214501Srpaulo
129252726Srpaulo	return 1;
130214501Srpaulo}
131214501Srpaulo
132214501Srpaulo
133214501Srpaulostatic void wmm_send_action(struct hostapd_data *hapd, const u8 *addr,
134214501Srpaulo			    const struct wmm_tspec_element *tspec,
135214501Srpaulo			    u8 action_code, u8 dialogue_token, u8 status_code)
136214501Srpaulo{
137214501Srpaulo	u8 buf[256];
138214501Srpaulo	struct ieee80211_mgmt *m = (struct ieee80211_mgmt *) buf;
139214501Srpaulo	struct wmm_tspec_element *t = (struct wmm_tspec_element *)
140214501Srpaulo		m->u.action.u.wmm_action.variable;
141214501Srpaulo	int len;
142214501Srpaulo
143214501Srpaulo	hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211,
144214501Srpaulo		       HOSTAPD_LEVEL_DEBUG,
145214501Srpaulo		       "action response - reason %d", status_code);
146214501Srpaulo	os_memset(buf, 0, sizeof(buf));
147214501Srpaulo	m->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
148214501Srpaulo					WLAN_FC_STYPE_ACTION);
149214501Srpaulo	os_memcpy(m->da, addr, ETH_ALEN);
150214501Srpaulo	os_memcpy(m->sa, hapd->own_addr, ETH_ALEN);
151214501Srpaulo	os_memcpy(m->bssid, hapd->own_addr, ETH_ALEN);
152214501Srpaulo	m->u.action.category = WLAN_ACTION_WMM;
153214501Srpaulo	m->u.action.u.wmm_action.action_code = action_code;
154214501Srpaulo	m->u.action.u.wmm_action.dialog_token = dialogue_token;
155214501Srpaulo	m->u.action.u.wmm_action.status_code = status_code;
156214501Srpaulo	os_memcpy(t, tspec, sizeof(struct wmm_tspec_element));
157214501Srpaulo	len = ((u8 *) (t + 1)) - buf;
158214501Srpaulo
159252726Srpaulo	if (hostapd_drv_send_mlme(hapd, m, len, 0) < 0)
160214501Srpaulo		perror("wmm_send_action: send");
161214501Srpaulo}
162214501Srpaulo
163214501Srpaulo
164214501Srpauloint wmm_process_tspec(struct wmm_tspec_element *tspec)
165214501Srpaulo{
166214501Srpaulo	int medium_time, pps, duration;
167214501Srpaulo	int up, psb, dir, tid;
168214501Srpaulo	u16 val, surplus;
169214501Srpaulo
170214501Srpaulo	up = (tspec->ts_info[1] >> 3) & 0x07;
171214501Srpaulo	psb = (tspec->ts_info[1] >> 2) & 0x01;
172214501Srpaulo	dir = (tspec->ts_info[0] >> 5) & 0x03;
173214501Srpaulo	tid = (tspec->ts_info[0] >> 1) & 0x0f;
174214501Srpaulo	wpa_printf(MSG_DEBUG, "WMM: TS Info: UP=%d PSB=%d Direction=%d TID=%d",
175214501Srpaulo		   up, psb, dir, tid);
176214501Srpaulo	val = le_to_host16(tspec->nominal_msdu_size);
177214501Srpaulo	wpa_printf(MSG_DEBUG, "WMM: Nominal MSDU Size: %d%s",
178214501Srpaulo		   val & 0x7fff, val & 0x8000 ? " (fixed)" : "");
179214501Srpaulo	wpa_printf(MSG_DEBUG, "WMM: Mean Data Rate: %u bps",
180214501Srpaulo		   le_to_host32(tspec->mean_data_rate));
181214501Srpaulo	wpa_printf(MSG_DEBUG, "WMM: Minimum PHY Rate: %u bps",
182214501Srpaulo		   le_to_host32(tspec->minimum_phy_rate));
183214501Srpaulo	val = le_to_host16(tspec->surplus_bandwidth_allowance);
184214501Srpaulo	wpa_printf(MSG_DEBUG, "WMM: Surplus Bandwidth Allowance: %u.%04u",
185214501Srpaulo		   val >> 13, 10000 * (val & 0x1fff) / 0x2000);
186214501Srpaulo
187214501Srpaulo	val = le_to_host16(tspec->nominal_msdu_size);
188214501Srpaulo	if (val == 0) {
189214501Srpaulo		wpa_printf(MSG_DEBUG, "WMM: Invalid Nominal MSDU Size (0)");
190214501Srpaulo		return WMM_ADDTS_STATUS_INVALID_PARAMETERS;
191214501Srpaulo	}
192214501Srpaulo	/* pps = Ceiling((Mean Data Rate / 8) / Nominal MSDU Size) */
193214501Srpaulo	pps = ((le_to_host32(tspec->mean_data_rate) / 8) + val - 1) / val;
194214501Srpaulo	wpa_printf(MSG_DEBUG, "WMM: Packets-per-second estimate for TSPEC: %d",
195214501Srpaulo		   pps);
196214501Srpaulo
197214501Srpaulo	if (le_to_host32(tspec->minimum_phy_rate) < 1000000) {
198214501Srpaulo		wpa_printf(MSG_DEBUG, "WMM: Too small Minimum PHY Rate");
199214501Srpaulo		return WMM_ADDTS_STATUS_INVALID_PARAMETERS;
200214501Srpaulo	}
201214501Srpaulo
202214501Srpaulo	duration = (le_to_host16(tspec->nominal_msdu_size) & 0x7fff) * 8 /
203214501Srpaulo		(le_to_host32(tspec->minimum_phy_rate) / 1000000) +
204214501Srpaulo		50 /* FIX: proper SIFS + ACK duration */;
205214501Srpaulo
206214501Srpaulo	/* unsigned binary number with an implicit binary point after the
207214501Srpaulo	 * leftmost 3 bits, i.e., 0x2000 = 1.0 */
208214501Srpaulo	surplus = le_to_host16(tspec->surplus_bandwidth_allowance);
209214501Srpaulo	if (surplus <= 0x2000) {
210214501Srpaulo		wpa_printf(MSG_DEBUG, "WMM: Surplus Bandwidth Allowance not "
211214501Srpaulo			   "greater than unity");
212214501Srpaulo		return WMM_ADDTS_STATUS_INVALID_PARAMETERS;
213214501Srpaulo	}
214214501Srpaulo
215214501Srpaulo	medium_time = surplus * pps * duration / 0x2000;
216214501Srpaulo	wpa_printf(MSG_DEBUG, "WMM: Estimated medium time: %u", medium_time);
217214501Srpaulo
218214501Srpaulo	/*
219214501Srpaulo	 * TODO: store list of granted (and still active) TSPECs and check
220214501Srpaulo	 * whether there is available medium time for this request. For now,
221214501Srpaulo	 * just refuse requests that would by themselves take very large
222214501Srpaulo	 * portion of the available bandwidth.
223214501Srpaulo	 */
224214501Srpaulo	if (medium_time > 750000) {
225214501Srpaulo		wpa_printf(MSG_DEBUG, "WMM: Refuse TSPEC request for over "
226214501Srpaulo			   "75%% of available bandwidth");
227214501Srpaulo		return WMM_ADDTS_STATUS_REFUSED;
228214501Srpaulo	}
229214501Srpaulo
230214501Srpaulo	/* Convert to 32 microseconds per second unit */
231214501Srpaulo	tspec->medium_time = host_to_le16(medium_time / 32);
232214501Srpaulo
233214501Srpaulo	return WMM_ADDTS_STATUS_ADMISSION_ACCEPTED;
234214501Srpaulo}
235214501Srpaulo
236214501Srpaulo
237214501Srpaulostatic void wmm_addts_req(struct hostapd_data *hapd,
238214501Srpaulo			  const struct ieee80211_mgmt *mgmt,
239214501Srpaulo			  struct wmm_tspec_element *tspec, size_t len)
240214501Srpaulo{
241214501Srpaulo	const u8 *end = ((const u8 *) mgmt) + len;
242214501Srpaulo	int res;
243214501Srpaulo
244214501Srpaulo	if ((const u8 *) (tspec + 1) > end) {
245214501Srpaulo		wpa_printf(MSG_DEBUG, "WMM: TSPEC overflow in ADDTS Request");
246214501Srpaulo		return;
247214501Srpaulo	}
248214501Srpaulo
249214501Srpaulo	wpa_printf(MSG_DEBUG, "WMM: ADDTS Request (Dialog Token %d) for TSPEC "
250214501Srpaulo		   "from " MACSTR,
251214501Srpaulo		   mgmt->u.action.u.wmm_action.dialog_token,
252214501Srpaulo		   MAC2STR(mgmt->sa));
253214501Srpaulo
254214501Srpaulo	res = wmm_process_tspec(tspec);
255214501Srpaulo	wpa_printf(MSG_DEBUG, "WMM: ADDTS processing result: %d", res);
256214501Srpaulo
257214501Srpaulo	wmm_send_action(hapd, mgmt->sa, tspec, WMM_ACTION_CODE_ADDTS_RESP,
258214501Srpaulo			mgmt->u.action.u.wmm_action.dialog_token, res);
259214501Srpaulo}
260214501Srpaulo
261214501Srpaulo
262214501Srpaulovoid hostapd_wmm_action(struct hostapd_data *hapd,
263214501Srpaulo			const struct ieee80211_mgmt *mgmt, size_t len)
264214501Srpaulo{
265214501Srpaulo	int action_code;
266214501Srpaulo	int left = len - IEEE80211_HDRLEN - 4;
267214501Srpaulo	const u8 *pos = ((const u8 *) mgmt) + IEEE80211_HDRLEN + 4;
268214501Srpaulo	struct ieee802_11_elems elems;
269214501Srpaulo	struct sta_info *sta = ap_get_sta(hapd, mgmt->sa);
270214501Srpaulo
271214501Srpaulo	/* check that the request comes from a valid station */
272214501Srpaulo	if (!sta ||
273214501Srpaulo	    (sta->flags & (WLAN_STA_ASSOC | WLAN_STA_WMM)) !=
274214501Srpaulo	    (WLAN_STA_ASSOC | WLAN_STA_WMM)) {
275214501Srpaulo		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
276214501Srpaulo			       HOSTAPD_LEVEL_DEBUG,
277214501Srpaulo			       "wmm action received is not from associated wmm"
278214501Srpaulo			       " station");
279214501Srpaulo		/* TODO: respond with action frame refused status code */
280214501Srpaulo		return;
281214501Srpaulo	}
282214501Srpaulo
283214501Srpaulo	/* extract the tspec info element */
284214501Srpaulo	if (ieee802_11_parse_elems(pos, left, &elems, 1) == ParseFailed) {
285214501Srpaulo		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
286214501Srpaulo			       HOSTAPD_LEVEL_DEBUG,
287214501Srpaulo			       "hostapd_wmm_action - could not parse wmm "
288214501Srpaulo			       "action");
289214501Srpaulo		/* TODO: respond with action frame invalid parameters status
290214501Srpaulo		 * code */
291214501Srpaulo		return;
292214501Srpaulo	}
293214501Srpaulo
294214501Srpaulo	if (!elems.wmm_tspec ||
295214501Srpaulo	    elems.wmm_tspec_len != (sizeof(struct wmm_tspec_element) - 2)) {
296214501Srpaulo		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
297214501Srpaulo			       HOSTAPD_LEVEL_DEBUG,
298214501Srpaulo			       "hostapd_wmm_action - missing or wrong length "
299214501Srpaulo			       "tspec");
300214501Srpaulo		/* TODO: respond with action frame invalid parameters status
301214501Srpaulo		 * code */
302214501Srpaulo		return;
303214501Srpaulo	}
304214501Srpaulo
305214501Srpaulo	/* TODO: check the request is for an AC with ACM set, if not, refuse
306214501Srpaulo	 * request */
307214501Srpaulo
308214501Srpaulo	action_code = mgmt->u.action.u.wmm_action.action_code;
309214501Srpaulo	switch (action_code) {
310214501Srpaulo	case WMM_ACTION_CODE_ADDTS_REQ:
311214501Srpaulo		wmm_addts_req(hapd, mgmt, (struct wmm_tspec_element *)
312214501Srpaulo			      (elems.wmm_tspec - 2), len);
313214501Srpaulo		return;
314214501Srpaulo#if 0
315214501Srpaulo	/* TODO: needed for client implementation */
316214501Srpaulo	case WMM_ACTION_CODE_ADDTS_RESP:
317214501Srpaulo		wmm_setup_request(hapd, mgmt, len);
318214501Srpaulo		return;
319214501Srpaulo	/* TODO: handle station teardown requests */
320214501Srpaulo	case WMM_ACTION_CODE_DELTS:
321214501Srpaulo		wmm_teardown(hapd, mgmt, len);
322214501Srpaulo		return;
323214501Srpaulo#endif
324214501Srpaulo	}
325214501Srpaulo
326214501Srpaulo	hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
327214501Srpaulo		       HOSTAPD_LEVEL_DEBUG,
328214501Srpaulo		       "hostapd_wmm_action - unknown action code %d",
329214501Srpaulo		       action_code);
330214501Srpaulo}
331