NEED_SHA512=y
endif
+ifdef CONFIG_AIRTIME_POLICY
+CFLAGS += -DCONFIG_AIRTIME_POLICY
+OBJS += ../src/ap/airtime_policy.o
+endif
+
ifdef CONFIG_FILS
CFLAGS += -DCONFIG_FILS
OBJS += ../src/ap/fils_hlp.o
#endif /* EAP_SERVER */
+#ifdef CONFIG_AIRTIME_POLICY
+static int add_airtime_weight(struct hostapd_bss_config *bss, char *value)
+{
+ struct airtime_sta_weight *wt;
+ char *pos, *next;
+
+ wt = os_zalloc(sizeof(*wt));
+ if (!wt)
+ return -1;
+
+ /* 02:01:02:03:04:05 10 */
+ pos = value;
+ next = os_strchr(pos, ' ');
+ if (next)
+ *next++ = '\0';
+ if (!next || hwaddr_aton(pos, wt->addr)) {
+ wpa_printf(MSG_ERROR, "Invalid station address: '%s'", pos);
+ os_free(wt);
+ return -1;
+ }
+
+ pos = next;
+ wt->weight = atoi(pos);
+ if (!wt->weight) {
+ wpa_printf(MSG_ERROR, "Invalid weight: '%s'", pos);
+ os_free(wt);
+ return -1;
+ }
+
+ wt->next = bss->airtime_weight_list;
+ bss->airtime_weight_list = wt;
+ return 0;
+}
+#endif /* CONFIG_AIRTIME_POLICY */
+
+
#ifdef CONFIG_SAE
static int parse_sae_password(struct hostapd_bss_config *bss, const char *val)
{
conf->rssi_reject_assoc_timeout = atoi(pos);
} else if (os_strcmp(buf, "pbss") == 0) {
bss->pbss = atoi(pos);
+#ifdef CONFIG_AIRTIME_POLICY
+ } else if (os_strcmp(buf, "airtime_mode") == 0) {
+ int val = atoi(pos);
+
+ if (val < 0 || val > AIRTIME_MODE_MAX) {
+ wpa_printf(MSG_ERROR, "Line %d: Unknown airtime_mode",
+ line);
+ return 1;
+ }
+ conf->airtime_mode = val;
+ } else if (os_strcmp(buf, "airtime_update_interval") == 0) {
+ conf->airtime_update_interval = atoi(pos);
+ } else if (os_strcmp(buf, "airtime_bss_weight") == 0) {
+ bss->airtime_weight = atoi(pos);
+ } else if (os_strcmp(buf, "airtime_bss_limit") == 0) {
+ int val = atoi(pos);
+
+ if (val < 0 || val > 1) {
+ wpa_printf(MSG_ERROR,
+ "Line %d: Invalid airtime_bss_limit (must be 0 or 1)",
+ line);
+ return 1;
+ }
+ bss->airtime_limit = val;
+ } else if (os_strcmp(buf, "airtime_sta_weight") == 0) {
+ if (add_airtime_weight(bss, pos) < 0) {
+ wpa_printf(MSG_ERROR,
+ "Line %d: Invalid airtime weight '%s'",
+ line, pos);
+ return 1;
+ }
+#endif /* CONFIG_AIRTIME_POLICY */
} else {
wpa_printf(MSG_ERROR,
"Line %d: unknown configuration item '%s'",
# Experimental implementation of draft-harkins-owe-07.txt
#CONFIG_OWE=y
+# Airtime policy support
+#CONFIG_AIRTIME_POLICY=y
+
# Override default value for the wpa_disable_eapol_key_retries configuration
# parameter. See that parameter in hostapd.conf for more details.
#CFLAGS += -DDEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES=1
# that allows sending of such data. Default: 0.
#stationary_ap=0
+##### Airtime policy configuration ###########################################
+
+# Set the airtime policy operating mode:
+# 0 = disabled (default)
+# 1 = static config
+# 2 = per-BSS dynamic config
+# 3 = per-BSS limit mode
+#airtime_mode=0
+
+# Interval (in milliseconds) to poll the kernel for updated station activity in
+# dynamic and limit modes
+#airtime_update_interval=200
+
+# Static configuration of station weights (when airtime_mode=1). Kernel default
+# weight is 256; set higher for larger airtime share, lower for smaller share.
+# Each entry is a MAC address followed by a weight.
+#airtime_sta_weight=02:01:02:03:04:05 256
+#airtime_sta_weight=02:01:02:03:04:06 512
+
+# Per-BSS airtime weight. In multi-BSS mode, set for each BSS and hostapd will
+# configure station weights to enforce the correct ratio between BSS weights
+# depending on the number of active stations. The *ratios* between different
+# BSSes is what's important, not the absolute numbers.
+# Must be set for all BSSes if airtime_mode=2 or 3, has no effect otherwise.
+#airtime_bss_weight=1
+
+# Whether the current BSS should be limited (when airtime_mode=3).
+#
+# If set, the BSS weight ratio will be applied in the case where the current BSS
+# would exceed the share defined by the BSS weight ratio. E.g., if two BSSes are
+# set to the same weights, and one is set to limited, the limited BSS will get
+# no more than half the available airtime, but if the non-limited BSS has more
+# stations active, that *will* be allowed to exceed its half of the available
+# airtime.
+#airtime_bss_limit=1
+
##### TESTING OPTIONS #########################################################
#
# The options in this section are only available when the build configuration
CFLAGS += -DCONFIG_PROXYARP
CFLAGS += -DCONFIG_IPV6
CFLAGS += -DCONFIG_IAPP
+CFLAGS += -DCONFIG_AIRTIME_POLICY
LIB_OBJS= \
accounting.o \
ap_drv_ops.o \
ap_list.o \
ap_mlme.o \
+ airtime_policy.o \
authsrv.o \
beacon.o \
bss_load.o \
--- /dev/null
+/*
+ * Airtime policy configuration
+ * Copyright (c) 2018-2019, Toke Høiland-Jørgensen <toke@toke.dk>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "hostapd.h"
+#include "ap_drv_ops.h"
+#include "sta_info.h"
+#include "airtime_policy.h"
+
+/* Idea:
+ * Two modes of airtime enforcement:
+ * 1. Static weights: specify weights per MAC address with a per-BSS default
+ * 2. Per-BSS limits: Dynamically calculate weights of backlogged stations to
+ * enforce relative total shares between BSSes.
+ *
+ * - Periodic per-station callback to update queue status.
+ *
+ * Copy accounting_sta_update_stats() to get TXQ info and airtime weights and
+ * keep them updated in sta_info.
+ *
+ * - Separate periodic per-bss (or per-iface?) callback to update weights.
+ *
+ * Just need to loop through all interfaces, count sum the active stations (or
+ * should the per-STA callback just adjust that for the BSS?) and calculate new
+ * weights.
+ */
+
+static int get_airtime_policy_update_timeout(struct hostapd_iface *iface,
+ unsigned int *sec,
+ unsigned int *usec)
+{
+ unsigned int update_int = iface->conf->airtime_update_interval;
+
+ if (!update_int) {
+ wpa_printf(MSG_ERROR,
+ "Airtime policy: Invalid airtime policy update interval %u",
+ update_int);
+ return -1;
+ }
+
+ *sec = update_int / 1000;
+ *usec = (update_int % 1000) * 1000;
+
+ return 0;
+}
+
+
+static void set_new_backlog_time(struct hostapd_data *hapd,
+ struct sta_info *sta,
+ struct os_reltime *now)
+{
+ sta->backlogged_until = *now;
+ sta->backlogged_until.usec += hapd->iconf->airtime_update_interval *
+ AIRTIME_BACKLOG_EXPIRY_FACTOR;
+ while (sta->backlogged_until.usec >= 1000000) {
+ sta->backlogged_until.sec++;
+ sta->backlogged_until.usec -= 1000000;
+ }
+}
+
+
+static void count_backlogged_sta(struct hostapd_data *hapd)
+{
+ struct sta_info *sta;
+ struct hostap_sta_driver_data data = {};
+ unsigned int num_backlogged = 0;
+ struct os_reltime now;
+
+ os_get_reltime(&now);
+
+ for (sta = hapd->sta_list; sta; sta = sta->next) {
+ if (hostapd_drv_read_sta_data(hapd, &data, sta->addr))
+ continue;
+
+ if (data.backlog_bytes > 0)
+ set_new_backlog_time(hapd, sta, &now);
+ if (os_reltime_before(&now, &sta->backlogged_until))
+ num_backlogged++;
+ }
+ hapd->num_backlogged_sta = num_backlogged;
+}
+
+
+static int sta_set_airtime_weight(struct hostapd_data *hapd,
+ struct sta_info *sta,
+ unsigned int weight)
+{
+ int ret = 0;
+
+ if (weight != sta->airtime_weight &&
+ (ret = hostapd_sta_set_airtime_weight(hapd, sta->addr, weight)))
+ return ret;
+
+ sta->airtime_weight = weight;
+ return ret;
+}
+
+
+static void set_sta_weights(struct hostapd_data *hapd, unsigned int weight)
+{
+ struct sta_info *sta;
+
+ for (sta = hapd->sta_list; sta; sta = sta->next)
+ sta_set_airtime_weight(hapd, sta, weight);
+}
+
+
+static unsigned int get_airtime_quantum(unsigned int max_wt)
+{
+ unsigned int quantum = AIRTIME_QUANTUM_TARGET / max_wt;
+
+ if (quantum < AIRTIME_QUANTUM_MIN)
+ quantum = AIRTIME_QUANTUM_MIN;
+ else if (quantum > AIRTIME_QUANTUM_MAX)
+ quantum = AIRTIME_QUANTUM_MAX;
+
+ return quantum;
+}
+
+
+static void update_airtime_weights(void *eloop_data, void *user_data)
+{
+ struct hostapd_iface *iface = eloop_data;
+ struct hostapd_data *bss;
+ unsigned int sec, usec;
+ unsigned int num_sta_min = 0, num_sta_prod = 1, num_sta_sum = 0,
+ wt_sum = 0;
+ unsigned int quantum;
+ Boolean all_div_min = TRUE;
+ Boolean apply_limit = iface->conf->airtime_mode == AIRTIME_MODE_DYNAMIC;
+ int wt, num_bss = 0, max_wt = 0;
+ size_t i;
+
+ for (i = 0; i < iface->num_bss; i++) {
+ bss = iface->bss[i];
+ if (!bss->started || !bss->conf->airtime_weight)
+ continue;
+
+ count_backlogged_sta(bss);
+ if (!bss->num_backlogged_sta)
+ continue;
+
+ if (!num_sta_min || bss->num_backlogged_sta < num_sta_min)
+ num_sta_min = bss->num_backlogged_sta;
+
+ num_sta_prod *= bss->num_backlogged_sta;
+ num_sta_sum += bss->num_backlogged_sta;
+ wt_sum += bss->conf->airtime_weight;
+ num_bss++;
+ }
+
+ if (num_sta_min) {
+ for (i = 0; i < iface->num_bss; i++) {
+ bss = iface->bss[i];
+ if (!bss->started || !bss->conf->airtime_weight)
+ continue;
+
+ /* Check if we can divide all sta numbers by the
+ * smallest number to keep weights as small as possible.
+ * This is a lazy way to avoid having to factor
+ * integers. */
+ if (bss->num_backlogged_sta &&
+ bss->num_backlogged_sta % num_sta_min > 0)
+ all_div_min = FALSE;
+
+ /* If we're in LIMIT mode, we only apply the weight
+ * scaling when the BSS(es) marked as limited would a
+ * larger share than the relative BSS weights indicates
+ * it should. */
+ if (!apply_limit && bss->conf->airtime_limit) {
+ if (bss->num_backlogged_sta * wt_sum >
+ bss->conf->airtime_weight * num_sta_sum)
+ apply_limit = TRUE;
+ }
+ }
+ if (all_div_min)
+ num_sta_prod /= num_sta_min;
+ }
+
+ for (i = 0; i < iface->num_bss; i++) {
+ bss = iface->bss[i];
+ if (!bss->started || !bss->conf->airtime_weight)
+ continue;
+
+ /* We only set the calculated weight if the BSS has active
+ * stations and there are other active interfaces as well -
+ * otherwise we just set a unit weight. This ensures that
+ * the weights are set reasonably when stations transition from
+ * inactive to active. */
+ if (apply_limit && bss->num_backlogged_sta && num_bss > 1)
+ wt = bss->conf->airtime_weight * num_sta_prod /
+ bss->num_backlogged_sta;
+ else
+ wt = 1;
+
+ bss->airtime_weight = wt;
+ if (wt > max_wt)
+ max_wt = wt;
+ }
+
+ quantum = get_airtime_quantum(max_wt);
+
+ for (i = 0; i < iface->num_bss; i++) {
+ bss = iface->bss[i];
+ if (!bss->started || !bss->conf->airtime_weight)
+ continue;
+ set_sta_weights(bss, bss->airtime_weight * quantum);
+ }
+
+ if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
+ return;
+
+ eloop_register_timeout(sec, usec, update_airtime_weights, iface,
+ NULL);
+}
+
+
+static int get_weight_for_sta(struct hostapd_data *hapd, const u8 *sta)
+{
+ struct airtime_sta_weight *wt;
+
+ wt = hapd->conf->airtime_weight_list;
+ while (wt && os_memcmp(wt->addr, sta, ETH_ALEN) != 0)
+ wt = wt->next;
+
+ return wt ? wt->weight : hapd->conf->airtime_weight;
+}
+
+
+int airtime_policy_new_sta(struct hostapd_data *hapd, struct sta_info *sta)
+{
+ unsigned int weight;
+
+ if (hapd->iconf->airtime_mode == AIRTIME_MODE_STATIC) {
+ weight = get_weight_for_sta(hapd, sta->addr);
+ if (weight)
+ return sta_set_airtime_weight(hapd, sta, weight);
+ }
+ return 0;
+}
+
+
+int airtime_policy_update_init(struct hostapd_iface *iface)
+{
+ unsigned int sec, usec;
+
+ if (iface->conf->airtime_mode < AIRTIME_MODE_DYNAMIC)
+ return 0;
+
+ if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0)
+ return -1;
+
+ eloop_register_timeout(sec, usec, update_airtime_weights, iface, NULL);
+ return 0;
+}
+
+
+void airtime_policy_update_deinit(struct hostapd_iface *iface)
+{
+ eloop_cancel_timeout(update_airtime_weights, iface, NULL);
+}
--- /dev/null
+/*
+ * Airtime policy configuration
+ * Copyright (c) 2018-2019, Toke Høiland-Jørgensen <toke@toke.dk>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef AIRTIME_POLICY_H
+#define AIRTIME_POLICY_H
+
+struct hostapd_iface;
+
+#ifdef CONFIG_AIRTIME_POLICY
+
+#define AIRTIME_DEFAULT_UPDATE_INTERVAL 200 /* ms */
+#define AIRTIME_BACKLOG_EXPIRY_FACTOR 2500 /* 2.5 intervals + convert to usec */
+
+/* scale quantum so this becomes the effective quantum after applying the max
+ * weight, but never go below min or above max */
+#define AIRTIME_QUANTUM_MIN 8 /* usec */
+#define AIRTIME_QUANTUM_MAX 256 /* usec */
+#define AIRTIME_QUANTUM_TARGET 1024 /* usec */
+
+int airtime_policy_new_sta(struct hostapd_data *hapd, struct sta_info *sta);
+int airtime_policy_update_init(struct hostapd_iface *iface);
+void airtime_policy_update_deinit(struct hostapd_iface *iface);
+
+#else /* CONFIG_AIRTIME_POLICY */
+
+static inline int airtime_policy_new_sta(struct hostapd_data *hapd,
+ struct sta_info *sta)
+{
+ return -1;
+}
+
+static inline int airtime_policy_update_init(struct hostapd_iface *iface)
+{
+ return -1;
+}
+
+static inline void airtime_policy_update_deinit(struct hostapd_iface *iface)
+{
+}
+
+#endif /* CONFIG_AIRTIME_POLICY */
+
+#endif /* AIRTIME_POLICY_H */
#include "eap_server/eap.h"
#include "wpa_auth.h"
#include "sta_info.h"
+#include "airtime_policy.h"
#include "ap_config.h"
conf->rssi_reject_assoc_rssi = 0;
conf->rssi_reject_assoc_timeout = 30;
+#ifdef CONFIG_AIRTIME_POLICY
+ conf->airtime_update_interval = AIRTIME_DEFAULT_UPDATE_INTERVAL;
+#endif /* CONFIG_AIRTIME_POLICY */
+
return conf;
}
hostapd_config_free_sae_passwords(conf);
+#ifdef CONFIG_AIRTIME_POLICY
+ {
+ struct airtime_sta_weight *wt, *wt_prev;
+
+ wt = conf->airtime_weight_list;
+ conf->airtime_weight_list = NULL;
+ while (wt) {
+ wt_prev = wt;
+ wt = wt->next;
+ os_free(wt_prev);
+ }
+ }
+#endif /* CONFIG_AIRTIME_POLICY */
+
os_free(conf);
}
return -1;
}
+#ifdef CONFIG_AIRTIME_POLICY
+ if (full_config && conf->airtime_mode > AIRTIME_MODE_STATIC &&
+ !conf->airtime_update_interval) {
+ wpa_printf(MSG_ERROR, "Airtime update interval cannot be zero");
+ return -1;
+ }
+#endif /* CONFIG_AIRTIME_POLICY */
for (i = 0; i < NUM_TX_QUEUES; i++) {
if (hostapd_config_check_cw(conf, i))
return -1;
struct hostapd_ip_addr ipaddr;
};
+struct airtime_sta_weight {
+ struct airtime_sta_weight *next;
+ unsigned int weight;
+ u8 addr[ETH_ALEN];
+};
+
/**
* struct hostapd_bss_config - Per-BSS configuration
*/
#define BACKHAUL_BSS 1
#define FRONTHAUL_BSS 2
int multi_ap; /* bitmap of BACKHAUL_BSS, FRONTHAUL_BSS */
+
+#ifdef CONFIG_AIRTIME_POLICY
+ unsigned int airtime_weight;
+ int airtime_limit;
+ struct airtime_sta_weight *airtime_weight_list;
+#endif /* CONFIG_AIRTIME_POLICY */
};
/**
int rssi_reject_assoc_rssi;
int rssi_reject_assoc_timeout;
+
+#ifdef CONFIG_AIRTIME_POLICY
+ enum {
+ AIRTIME_MODE_OFF = 0,
+ AIRTIME_MODE_STATIC = 1,
+ AIRTIME_MODE_DYNAMIC = 2,
+ AIRTIME_MODE_LIMIT = 3,
+ __AIRTIME_MODE_MAX,
+ } airtime_mode;
+ unsigned int airtime_update_interval;
+#define AIRTIME_MODE_MAX (__AIRTIME_MODE_MAX - 1)
+#endif /* CONFIG_AIRTIME_POLICY */
};
#include "fils_hlp.h"
#include "acs.h"
#include "hs20.h"
+#include "airtime_policy.h"
static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason);
iface->basic_rates = NULL;
ap_list_deinit(iface);
sta_track_deinit(iface);
+ airtime_policy_update_deinit(iface);
}
hostapd_set_state(iface, HAPD_IFACE_ENABLED);
hostapd_owe_update_trans(iface);
+ airtime_policy_update_init(iface);
wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_ENABLED);
if (hapd->setup_complete_cb)
hapd->setup_complete_cb(hapd->setup_complete_cb_ctx);
}
#endif /* CONFIG_P2P */
+ airtime_policy_new_sta(hapd, sta);
+
/* Start accounting here, if IEEE 802.1X and WPA are not used.
* IEEE 802.1X/WPA code will start accounting after the station has
* been authorized. */
unsigned int dpp_ignore_netaccesskey_mismatch:1;
#endif /* CONFIG_TESTING_OPTIONS */
#endif /* CONFIG_DPP */
+
+#ifdef CONFIG_AIRTIME_POLICY
+ unsigned int num_backlogged_sta;
+ unsigned int airtime_weight;
+#endif /* CONFIG_AIRTIME_POLICY */
};
unsigned int num_sta_seen;
u8 dfs_domain;
+#ifdef CONFIG_AIRTIME_POLICY
+ unsigned int airtime_quantum;
+#endif /* CONFIG_AIRTIME_POLICY */
};
/* hostapd.c */
u8 last_tk[WPA_TK_MAX_LEN];
size_t last_tk_len;
#endif /* CONFIG_TESTING_OPTIONS */
+#ifdef CONFIG_AIRTIME_POLICY
+ unsigned int airtime_weight;
+ struct os_reltime backlogged_until;
+#endif /* CONFIG_AIRTIME_POLICY */
};