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