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