Hai Shalom | 81f62d8 | 2019-07-22 12:10:00 -0700 | [diff] [blame] | 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; |
Hai Shalom | e21d4e8 | 2020-04-29 16:34:06 -0700 | [diff] [blame^] | 137 | bool all_div_min = true; |
| 138 | bool apply_limit = iface->conf->airtime_mode == AIRTIME_MODE_DYNAMIC; |
Hai Shalom | 81f62d8 | 2019-07-22 12:10:00 -0700 | [diff] [blame] | 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) |
Hai Shalom | e21d4e8 | 2020-04-29 16:34:06 -0700 | [diff] [blame^] | 172 | all_div_min = false; |
Hai Shalom | 81f62d8 | 2019-07-22 12:10:00 -0700 | [diff] [blame] | 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) |
Hai Shalom | e21d4e8 | 2020-04-29 16:34:06 -0700 | [diff] [blame^] | 181 | apply_limit = true; |
Hai Shalom | 81f62d8 | 2019-07-22 12:10:00 -0700 | [diff] [blame] | 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 | } |