xref: /freebsd/contrib/wpa/src/ap/airtime_policy.c (revision 206b73d0429edb7c49b612537544e677fa568e83)
1*206b73d0SCy Schubert /*
2*206b73d0SCy Schubert  * Airtime policy configuration
3*206b73d0SCy Schubert  * Copyright (c) 2018-2019, Toke Høiland-Jørgensen <toke@toke.dk>
4*206b73d0SCy Schubert  *
5*206b73d0SCy Schubert  * This software may be distributed under the terms of the BSD license.
6*206b73d0SCy Schubert  * See README for more details.
7*206b73d0SCy Schubert  */
8*206b73d0SCy Schubert 
9*206b73d0SCy Schubert #include "utils/includes.h"
10*206b73d0SCy Schubert 
11*206b73d0SCy Schubert #include "utils/common.h"
12*206b73d0SCy Schubert #include "utils/eloop.h"
13*206b73d0SCy Schubert #include "hostapd.h"
14*206b73d0SCy Schubert #include "ap_drv_ops.h"
15*206b73d0SCy Schubert #include "sta_info.h"
16*206b73d0SCy Schubert #include "airtime_policy.h"
17*206b73d0SCy Schubert 
18*206b73d0SCy Schubert /* Idea:
19*206b73d0SCy Schubert  * Two modes of airtime enforcement:
20*206b73d0SCy Schubert  * 1. Static weights: specify weights per MAC address with a per-BSS default
21*206b73d0SCy Schubert  * 2. Per-BSS limits: Dynamically calculate weights of backlogged stations to
22*206b73d0SCy Schubert  *    enforce relative total shares between BSSes.
23*206b73d0SCy Schubert  *
24*206b73d0SCy Schubert  * - Periodic per-station callback to update queue status.
25*206b73d0SCy Schubert  *
26*206b73d0SCy Schubert  * Copy accounting_sta_update_stats() to get TXQ info and airtime weights and
27*206b73d0SCy Schubert  * keep them updated in sta_info.
28*206b73d0SCy Schubert  *
29*206b73d0SCy Schubert  * - Separate periodic per-bss (or per-iface?) callback to update weights.
30*206b73d0SCy Schubert  *
31*206b73d0SCy Schubert  * Just need to loop through all interfaces, count sum the active stations (or
32*206b73d0SCy Schubert  * should the per-STA callback just adjust that for the BSS?) and calculate new
33*206b73d0SCy Schubert  * weights.
34*206b73d0SCy Schubert  */
35*206b73d0SCy Schubert 
36*206b73d0SCy Schubert static int get_airtime_policy_update_timeout(struct hostapd_iface *iface,
37*206b73d0SCy Schubert 					     unsigned int *sec,
38*206b73d0SCy Schubert 					     unsigned int *usec)
39*206b73d0SCy Schubert {
40*206b73d0SCy Schubert 	unsigned int update_int = iface->conf->airtime_update_interval;
41*206b73d0SCy Schubert 
42*206b73d0SCy Schubert 	if (!update_int) {
43*206b73d0SCy Schubert 		wpa_printf(MSG_ERROR,
44*206b73d0SCy Schubert 			   "Airtime policy: Invalid airtime policy update interval %u",
45*206b73d0SCy Schubert 			   update_int);
46*206b73d0SCy Schubert 		return -1;
47*206b73d0SCy Schubert 	}
48*206b73d0SCy Schubert 
49*206b73d0SCy Schubert 	*sec = update_int / 1000;
50*206b73d0SCy Schubert 	*usec = (update_int % 1000) * 1000;
51*206b73d0SCy Schubert 
52*206b73d0SCy Schubert 	return 0;
53*206b73d0SCy Schubert }
54*206b73d0SCy Schubert 
55*206b73d0SCy Schubert 
56*206b73d0SCy Schubert static void set_new_backlog_time(struct hostapd_data *hapd,
57*206b73d0SCy Schubert 				 struct sta_info *sta,
58*206b73d0SCy Schubert 				 struct os_reltime *now)
59*206b73d0SCy Schubert {
60*206b73d0SCy Schubert 	sta->backlogged_until = *now;
61*206b73d0SCy Schubert 	sta->backlogged_until.usec += hapd->iconf->airtime_update_interval *
62*206b73d0SCy Schubert 		AIRTIME_BACKLOG_EXPIRY_FACTOR;
63*206b73d0SCy Schubert 	while (sta->backlogged_until.usec >= 1000000) {
64*206b73d0SCy Schubert 		sta->backlogged_until.sec++;
65*206b73d0SCy Schubert 		sta->backlogged_until.usec -= 1000000;
66*206b73d0SCy Schubert 	}
67*206b73d0SCy Schubert }
68*206b73d0SCy Schubert 
69*206b73d0SCy Schubert 
70*206b73d0SCy Schubert static void count_backlogged_sta(struct hostapd_data *hapd)
71*206b73d0SCy Schubert {
72*206b73d0SCy Schubert 	struct sta_info *sta;
73*206b73d0SCy Schubert 	struct hostap_sta_driver_data data = {};
74*206b73d0SCy Schubert 	unsigned int num_backlogged = 0;
75*206b73d0SCy Schubert 	struct os_reltime now;
76*206b73d0SCy Schubert 
77*206b73d0SCy Schubert 	os_get_reltime(&now);
78*206b73d0SCy Schubert 
79*206b73d0SCy Schubert 	for (sta = hapd->sta_list; sta; sta = sta->next) {
80*206b73d0SCy Schubert 		if (hostapd_drv_read_sta_data(hapd, &data, sta->addr))
81*206b73d0SCy Schubert 			continue;
82*206b73d0SCy Schubert 
83*206b73d0SCy Schubert 		if (data.backlog_bytes > 0)
84*206b73d0SCy Schubert 			set_new_backlog_time(hapd, sta, &now);
85*206b73d0SCy Schubert 		if (os_reltime_before(&now, &sta->backlogged_until))
86*206b73d0SCy Schubert 			num_backlogged++;
87*206b73d0SCy Schubert 	}
88*206b73d0SCy Schubert 	hapd->num_backlogged_sta = num_backlogged;
89*206b73d0SCy Schubert }
90*206b73d0SCy Schubert 
91*206b73d0SCy Schubert 
92*206b73d0SCy Schubert static int sta_set_airtime_weight(struct hostapd_data *hapd,
93*206b73d0SCy Schubert 				  struct sta_info *sta,
94*206b73d0SCy Schubert 				  unsigned int weight)
95*206b73d0SCy Schubert {
96*206b73d0SCy Schubert 	int ret = 0;
97*206b73d0SCy Schubert 
98*206b73d0SCy Schubert 	if (weight != sta->airtime_weight &&
99*206b73d0SCy Schubert 	    (ret = hostapd_sta_set_airtime_weight(hapd, sta->addr, weight)))
100*206b73d0SCy Schubert 		return ret;
101*206b73d0SCy Schubert 
102*206b73d0SCy Schubert 	sta->airtime_weight = weight;
103*206b73d0SCy Schubert 	return ret;
104*206b73d0SCy Schubert }
105*206b73d0SCy Schubert 
106*206b73d0SCy Schubert 
107*206b73d0SCy Schubert static void set_sta_weights(struct hostapd_data *hapd, unsigned int weight)
108*206b73d0SCy Schubert {
109*206b73d0SCy Schubert 	struct sta_info *sta;
110*206b73d0SCy Schubert 
111*206b73d0SCy Schubert 	for (sta = hapd->sta_list; sta; sta = sta->next)
112*206b73d0SCy Schubert 		sta_set_airtime_weight(hapd, sta, weight);
113*206b73d0SCy Schubert }
114*206b73d0SCy Schubert 
115*206b73d0SCy Schubert 
116*206b73d0SCy Schubert static unsigned int get_airtime_quantum(unsigned int max_wt)
117*206b73d0SCy Schubert {
118*206b73d0SCy Schubert 	unsigned int quantum = AIRTIME_QUANTUM_TARGET / max_wt;
119*206b73d0SCy Schubert 
120*206b73d0SCy Schubert 	if (quantum < AIRTIME_QUANTUM_MIN)
121*206b73d0SCy Schubert 		quantum = AIRTIME_QUANTUM_MIN;
122*206b73d0SCy Schubert 	else if (quantum > AIRTIME_QUANTUM_MAX)
123*206b73d0SCy Schubert 		quantum = AIRTIME_QUANTUM_MAX;
124*206b73d0SCy Schubert 
125*206b73d0SCy Schubert 	return quantum;
126*206b73d0SCy Schubert }
127*206b73d0SCy Schubert 
128*206b73d0SCy Schubert 
129*206b73d0SCy Schubert static void update_airtime_weights(void *eloop_data, void *user_data)
130*206b73d0SCy Schubert {
131*206b73d0SCy Schubert 	struct hostapd_iface *iface = eloop_data;
132*206b73d0SCy Schubert 	struct hostapd_data *bss;
133*206b73d0SCy Schubert 	unsigned int sec, usec;
134*206b73d0SCy Schubert 	unsigned int num_sta_min = 0, num_sta_prod = 1, num_sta_sum = 0,
135*206b73d0SCy Schubert 		wt_sum = 0;
136*206b73d0SCy Schubert 	unsigned int quantum;
137*206b73d0SCy Schubert 	Boolean all_div_min = TRUE;
138*206b73d0SCy Schubert 	Boolean apply_limit = iface->conf->airtime_mode == AIRTIME_MODE_DYNAMIC;
139*206b73d0SCy Schubert 	int wt, num_bss = 0, max_wt = 0;
140*206b73d0SCy Schubert 	size_t i;
141*206b73d0SCy Schubert 
142*206b73d0SCy Schubert 	for (i = 0; i < iface->num_bss; i++) {
143*206b73d0SCy Schubert 		bss = iface->bss[i];
144*206b73d0SCy Schubert 		if (!bss->started || !bss->conf->airtime_weight)
145*206b73d0SCy Schubert 			continue;
146*206b73d0SCy Schubert 
147*206b73d0SCy Schubert 		count_backlogged_sta(bss);
148*206b73d0SCy Schubert 		if (!bss->num_backlogged_sta)
149*206b73d0SCy Schubert 			continue;
150*206b73d0SCy Schubert 
151*206b73d0SCy Schubert 		if (!num_sta_min || bss->num_backlogged_sta < num_sta_min)
152*206b73d0SCy Schubert 			num_sta_min = bss->num_backlogged_sta;
153*206b73d0SCy Schubert 
154*206b73d0SCy Schubert 		num_sta_prod *= bss->num_backlogged_sta;
155*206b73d0SCy Schubert 		num_sta_sum += bss->num_backlogged_sta;
156*206b73d0SCy Schubert 		wt_sum += bss->conf->airtime_weight;
157*206b73d0SCy Schubert 		num_bss++;
158*206b73d0SCy Schubert 	}
159*206b73d0SCy Schubert 
160*206b73d0SCy Schubert 	if (num_sta_min) {
161*206b73d0SCy Schubert 		for (i = 0; i < iface->num_bss; i++) {
162*206b73d0SCy Schubert 			bss = iface->bss[i];
163*206b73d0SCy Schubert 			if (!bss->started || !bss->conf->airtime_weight)
164*206b73d0SCy Schubert 				continue;
165*206b73d0SCy Schubert 
166*206b73d0SCy Schubert 			/* Check if we can divide all sta numbers by the
167*206b73d0SCy Schubert 			 * smallest number to keep weights as small as possible.
168*206b73d0SCy Schubert 			 * This is a lazy way to avoid having to factor
169*206b73d0SCy Schubert 			 * integers. */
170*206b73d0SCy Schubert 			if (bss->num_backlogged_sta &&
171*206b73d0SCy Schubert 			    bss->num_backlogged_sta % num_sta_min > 0)
172*206b73d0SCy Schubert 				all_div_min = FALSE;
173*206b73d0SCy Schubert 
174*206b73d0SCy Schubert 			/* If we're in LIMIT mode, we only apply the weight
175*206b73d0SCy Schubert 			 * scaling when the BSS(es) marked as limited would a
176*206b73d0SCy Schubert 			 * larger share than the relative BSS weights indicates
177*206b73d0SCy Schubert 			 * it should. */
178*206b73d0SCy Schubert 			if (!apply_limit && bss->conf->airtime_limit) {
179*206b73d0SCy Schubert 				if (bss->num_backlogged_sta * wt_sum >
180*206b73d0SCy Schubert 				    bss->conf->airtime_weight * num_sta_sum)
181*206b73d0SCy Schubert 					apply_limit = TRUE;
182*206b73d0SCy Schubert 			}
183*206b73d0SCy Schubert 		}
184*206b73d0SCy Schubert 		if (all_div_min)
185*206b73d0SCy Schubert 			num_sta_prod /= num_sta_min;
186*206b73d0SCy Schubert 	}
187*206b73d0SCy Schubert 
188*206b73d0SCy Schubert 	for (i = 0; i < iface->num_bss; i++) {
189*206b73d0SCy Schubert 		bss = iface->bss[i];
190*206b73d0SCy Schubert 		if (!bss->started || !bss->conf->airtime_weight)
191*206b73d0SCy Schubert 			continue;
192*206b73d0SCy Schubert 
193*206b73d0SCy Schubert 		/* We only set the calculated weight if the BSS has active
194*206b73d0SCy Schubert 		 * stations and there are other active interfaces as well -
195*206b73d0SCy Schubert 		 * otherwise we just set a unit weight. This ensures that
196*206b73d0SCy Schubert 		 * the weights are set reasonably when stations transition from
197*206b73d0SCy Schubert 		 * inactive to active. */
198*206b73d0SCy Schubert 		if (apply_limit && bss->num_backlogged_sta && num_bss > 1)
199*206b73d0SCy Schubert 			wt = bss->conf->airtime_weight * num_sta_prod /
200*206b73d0SCy Schubert 				bss->num_backlogged_sta;
201*206b73d0SCy Schubert 		else
202*206b73d0SCy Schubert 			wt = 1;
203*206b73d0SCy Schubert 
204*206b73d0SCy Schubert 		bss->airtime_weight = wt;
205*206b73d0SCy Schubert 		if (wt > max_wt)
206*206b73d0SCy Schubert 			max_wt = wt;
207*206b73d0SCy Schubert 	}
208*206b73d0SCy Schubert 
209*206b73d0SCy Schubert 	quantum = get_airtime_quantum(max_wt);
210*206b73d0SCy Schubert 
211*206b73d0SCy Schubert 	for (i = 0; i < iface->num_bss; i++) {
212*206b73d0SCy Schubert 		bss = iface->bss[i];
213*206b73d0SCy Schubert 		if (!bss->started || !bss->conf->airtime_weight)
214*206b73d0SCy Schubert 			continue;
215*206b73d0SCy Schubert 		set_sta_weights(bss, bss->airtime_weight * quantum);
216*206b73d0SCy Schubert 	}
217*206b73d0SCy Schubert 
218*206b73d0SCy Schubert 	if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
219*206b73d0SCy Schubert 		return;
220*206b73d0SCy Schubert 
221*206b73d0SCy Schubert 	eloop_register_timeout(sec, usec, update_airtime_weights, iface,
222*206b73d0SCy Schubert 			       NULL);
223*206b73d0SCy Schubert }
224*206b73d0SCy Schubert 
225*206b73d0SCy Schubert 
226*206b73d0SCy Schubert static int get_weight_for_sta(struct hostapd_data *hapd, const u8 *sta)
227*206b73d0SCy Schubert {
228*206b73d0SCy Schubert 	struct airtime_sta_weight *wt;
229*206b73d0SCy Schubert 
230*206b73d0SCy Schubert 	wt = hapd->conf->airtime_weight_list;
231*206b73d0SCy Schubert 	while (wt && os_memcmp(wt->addr, sta, ETH_ALEN) != 0)
232*206b73d0SCy Schubert 		wt = wt->next;
233*206b73d0SCy Schubert 
234*206b73d0SCy Schubert 	return wt ? wt->weight : hapd->conf->airtime_weight;
235*206b73d0SCy Schubert }
236*206b73d0SCy Schubert 
237*206b73d0SCy Schubert 
238*206b73d0SCy Schubert int airtime_policy_new_sta(struct hostapd_data *hapd, struct sta_info *sta)
239*206b73d0SCy Schubert {
240*206b73d0SCy Schubert 	unsigned int weight;
241*206b73d0SCy Schubert 
242*206b73d0SCy Schubert 	if (hapd->iconf->airtime_mode == AIRTIME_MODE_STATIC) {
243*206b73d0SCy Schubert 		weight = get_weight_for_sta(hapd, sta->addr);
244*206b73d0SCy Schubert 		if (weight)
245*206b73d0SCy Schubert 			return sta_set_airtime_weight(hapd, sta, weight);
246*206b73d0SCy Schubert 	}
247*206b73d0SCy Schubert 	return 0;
248*206b73d0SCy Schubert }
249*206b73d0SCy Schubert 
250*206b73d0SCy Schubert 
251*206b73d0SCy Schubert int airtime_policy_update_init(struct hostapd_iface *iface)
252*206b73d0SCy Schubert {
253*206b73d0SCy Schubert 	unsigned int sec, usec;
254*206b73d0SCy Schubert 
255*206b73d0SCy Schubert 	if (iface->conf->airtime_mode < AIRTIME_MODE_DYNAMIC)
256*206b73d0SCy Schubert 		return 0;
257*206b73d0SCy Schubert 
258*206b73d0SCy Schubert 	if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
259*206b73d0SCy Schubert 		return -1;
260*206b73d0SCy Schubert 
261*206b73d0SCy Schubert 	eloop_register_timeout(sec, usec, update_airtime_weights, iface, NULL);
262*206b73d0SCy Schubert 	return 0;
263*206b73d0SCy Schubert }
264*206b73d0SCy Schubert 
265*206b73d0SCy Schubert 
266*206b73d0SCy Schubert void airtime_policy_update_deinit(struct hostapd_iface *iface)
267*206b73d0SCy Schubert {
268*206b73d0SCy Schubert 	eloop_cancel_timeout(update_airtime_weights, iface, NULL);
269*206b73d0SCy Schubert }
270