atfpolicy: add package

Add a Airtime Scheduler that adjusts an UEs ATF weight based on its
WMM/TID usage.

Fixes: WIFI-5703
Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
John Crispin
2021-11-11 02:22:02 +01:00
parent 065539bbb3
commit a77d881147
10 changed files with 715 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
#
# Copyright (C) 2021 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=atfpolicy
PKG_VERSION:=1
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
define Package/atfpolicy
SECTION:=net
CATEGORY:=Network
TITLE:=A simple daemon for handling airtime fairness prioritization
DEPENDS:=+libubox +libubus +libnl-tiny
endef
TARGET_CFLAGS += -I$(STAGING_DIR)/usr/include/libnl-tiny
define Package/atfpolicy/install
$(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/init.d $(1)/etc/config
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/atfpolicy $(1)/usr/sbin/
$(INSTALL_BIN) ./files/atfpolicy.init $(1)/etc/init.d/atfpolicy
$(INSTALL_DATA) ./files/atfpolicy.conf $(1)/etc/config/atfpolicy
endef
$(eval $(call BuildPackage,atfpolicy))

View File

@@ -0,0 +1,8 @@
config defaults
option vo_queue_weight 4
option update_pkt_threshold 100
option bulk_percent_thresh 50
option prio_percent_thresh 30
option weight_normal 256
option weight_prio 512
option weight_bulk 128

View File

@@ -0,0 +1,57 @@
#!/bin/sh /etc/rc.common
# Copyright (c) 2021 OpenWrt.org
START=50
USE_PROCD=1
PROG=/usr/sbin/atfpolicy
add_option() {
local type="$1"
local name="$2"
config_get val "$cfg" "$name"
[ -n "$val" ] && json_add_$type "$name" "$val"
}
add_defaults() {
cfg="$1"
json_add_boolean reset 1
add_option int vo_queue_weight
add_option int update_pkt_threshold
add_option int bulk_percent_thresh
add_option int prio_percent_thresh
add_option int weight_normal
add_option int weight_prio
add_option int weight_bulk
}
reload_service() {
json_init
config_load atfpolicy
config_foreach add_defaults defaults
ubus call atfpolicy config "$(json_dump)"
}
service_triggers() {
procd_add_reload_trigger atfpolicy
}
start_service() {
procd_open_instance
procd_set_param command "$PROG"
procd_set_param respawn
procd_close_instance
}
service_started() {
ubus -t 10 wait_for atfpolicy
[ $? = 0 ] && reload_service
}

View File

@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.10)
PROJECT(atfpolicy C)
ADD_DEFINITIONS(-Os -Wall -Wno-unknown-warning-option -Wno-array-bounds -Wno-format-truncation -Werror --std=gnu99)
SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
find_library(nl NAMES nl-tiny)
ADD_EXECUTABLE(atfpolicy main.c ubus.c interface.c nl80211.c)
TARGET_LINK_LIBRARIES(atfpolicy ${nl} ubox ubus)
INSTALL(TARGETS atfpolicy
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)

View File

@@ -0,0 +1,90 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#ifndef __ATF_H
#define __ATF_H
#include <net/if.h>
#include <stdint.h>
#include <libubox/avl.h>
#define ATF_AVG_SCALE 12
#define ATF_AVG_WEIGHT_FACTOR 3
#define ATF_AVG_WEIGHT_DIV 4
#define MAC_ADDR_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
#define MAC_ADDR_DATA(_a) \
((const uint8_t *)(_a))[0], \
((const uint8_t *)(_a))[1], \
((const uint8_t *)(_a))[2], \
((const uint8_t *)(_a))[3], \
((const uint8_t *)(_a))[4], \
((const uint8_t *)(_a))[5]
#define D(format, ...) do { \
if (debug_flag) \
fprintf(stderr, "DEBUG: %s(%d) " format "\n", __func__, __LINE__, ## __VA_ARGS__); \
} while (0)
struct atf_config {
int voice_queue_weight;
int min_pkt_thresh;
int bulk_percent_thresh;
int prio_percent_thresh;
int weight_normal;
int weight_prio;
int weight_bulk;
};
struct atf_interface {
struct avl_node avl;
char ifname[IFNAMSIZ + 1];
uint32_t ubus_obj;
struct avl_tree stations;
};
struct atf_stats {
uint64_t bulk, normal, prio;
};
struct atf_station {
struct avl_node avl;
uint8_t macaddr[6];
bool present;
uint8_t stats_idx;
struct atf_stats stats[2];
uint16_t avg_bulk;
uint16_t avg_prio;
int weight;
};
extern struct atf_config config;
extern int debug_flag;
void reset_config(void);
struct atf_interface *atf_interface_get(const char *ifname);
void atf_interface_sta_update(struct atf_interface *iface);
struct atf_station *atf_interface_sta_get(struct atf_interface *iface, uint8_t *macaddr);
void atf_interface_sta_changed(struct atf_interface *iface, struct atf_station *sta);
void atf_interface_sta_flush(struct atf_interface *iface);
void atf_interface_update_all(void);
int atf_ubus_init(void);
void atf_ubus_stop(void);
void atf_ubus_set_sta_weight(struct atf_interface *iface, struct atf_station *sta);
int atf_nl80211_init(void);
int atf_nl80211_interface_update(struct atf_interface *iface);
#endif

View File

@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <string.h>
#include <stdlib.h>
#include <libubox/avl-cmp.h>
#include "atf.h"
static AVL_TREE(interfaces, avl_strcmp, false, NULL);
#ifndef container_of_safe
#define container_of_safe(ptr, type, member) \
(ptr ? container_of(ptr, type, member) : NULL)
#endif
static int avl_macaddr_cmp(const void *k1, const void *k2, void *ptr)
{
return memcmp(k1, k2, 6);
}
void atf_interface_sta_update(struct atf_interface *iface)
{
struct atf_station *sta;
avl_for_each_element(&iface->stations, sta, avl)
sta->present = false;
}
struct atf_station *atf_interface_sta_get(struct atf_interface *iface, uint8_t *macaddr)
{
struct atf_station *sta;
sta = avl_find_element(&iface->stations, macaddr, sta, avl);
if (sta)
goto out;
sta = calloc(1, sizeof(*sta));
memcpy(sta->macaddr, macaddr, sizeof(sta->macaddr));
sta->avl.key = sta->macaddr;
sta->weight = -1;
avl_insert(&iface->stations, &sta->avl);
out:
sta->present = true;
return sta;
}
void atf_interface_sta_flush(struct atf_interface *iface)
{
struct atf_station *sta, *tmp;
avl_for_each_element_safe(&iface->stations, sta, avl, tmp) {
if (sta->present)
continue;
avl_delete(&iface->stations, &sta->avl);
free(sta);
}
}
void atf_interface_sta_changed(struct atf_interface *iface, struct atf_station *sta)
{
int weight;
if (sta->avg_prio > config.prio_percent_thresh)
weight = config.weight_prio;
else if (sta->avg_prio > config.bulk_percent_thresh)
weight = config.weight_bulk;
else
weight = config.weight_normal;
if (sta->weight == weight)
return;
sta->weight = weight;
atf_ubus_set_sta_weight(iface, sta);
}
struct atf_interface *atf_interface_get(const char *ifname)
{
struct atf_interface *iface;
iface = avl_find_element(&interfaces, ifname, iface, avl);
if (iface)
return iface;
if (strlen(ifname) + 1 > sizeof(iface->ifname))
return NULL;
iface = calloc(1, sizeof(*iface));
strcpy(iface->ifname, ifname);
iface->avl.key = iface->ifname;
avl_init(&iface->stations, avl_macaddr_cmp, false, NULL);
avl_insert(&interfaces, &iface->avl);
return iface;
}
void atf_interface_update_all(void)
{
struct atf_interface *iface, *tmp;
avl_for_each_element_safe(&interfaces, iface, avl, tmp)
atf_nl80211_interface_update(iface);
}

View File

@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <stdio.h>
#include <string.h>
#include <getopt.h>
#include <libubox/uloop.h>
#include "atf.h"
struct atf_config config;
int debug_flag;
void reset_config(void)
{
memset(&config, 0, sizeof(config));
config.voice_queue_weight = 4;
config.min_pkt_thresh = 100;
config.bulk_percent_thresh = (50 << ATF_AVG_SCALE) / 100;
config.prio_percent_thresh = (30 << ATF_AVG_SCALE) / 100;
config.weight_normal = 256;
config.weight_bulk = 128;
config.weight_prio = 512;
}
static void atf_update_cb(struct uloop_timeout *t)
{
atf_interface_update_all();
uloop_timeout_set(t, 1000);
}
int main(int argc, char **argv)
{
static struct uloop_timeout update_timer = {
.cb = atf_update_cb,
};
int ch;
while ((ch = getopt(argc, argv, "d")) != -1) {
switch (ch) {
case 'd':
debug_flag = 1;
break;
}
}
reset_config();
uloop_init();
atf_ubus_init();
atf_nl80211_init();
atf_update_cb(&update_timer);
uloop_run();
atf_ubus_stop();
uloop_done();
return 0;
}

View File

@@ -0,0 +1,174 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#define _GNU_SOURCE
#include <linux/nl80211.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <unl.h>
#include "atf.h"
static struct unl unl;
static void
atf_parse_tid_stats(struct atf_interface *iface, struct atf_stats *stats,
int tid, struct nlattr *attr)
{
struct nlattr *tb[NL80211_TID_STATS_MAX + 1];
uint64_t msdu;
if (nla_parse_nested(tb, NL80211_TID_STATS_MAX, attr, NULL))
return;
if (!tb[NL80211_TID_STATS_TX_MSDU])
return;
msdu = nla_get_u64(tb[NL80211_TID_STATS_TX_MSDU]);
switch (tid) {
case 0:
case 3:
/* BE */
stats->normal += msdu;
break;
case 1:
case 2:
/* BK */
stats->bulk += msdu;
break;
case 4:
case 5:
/* VI */
stats->prio += msdu;
break;
case 6:
case 7:
stats->prio += msdu * config.voice_queue_weight;
/* VO */
break;
default:
break;
}
}
static uint64_t atf_stats_total(struct atf_stats *stats)
{
return stats->normal + stats->prio + stats->bulk;
}
static void atf_stats_diff(struct atf_stats *dest, struct atf_stats *cur, struct atf_stats *prev)
{
dest->normal = cur->normal - prev->normal;
dest->prio = cur->prio - prev->prio;
dest->bulk = cur->bulk - prev->bulk;
}
static uint16_t atf_stats_avg(uint16_t avg, uint64_t cur, uint32_t total)
{
cur <<= ATF_AVG_SCALE;
cur /= total;
if (!avg)
return (uint16_t)cur;
avg *= ATF_AVG_WEIGHT_FACTOR;
avg += cur * (ATF_AVG_WEIGHT_DIV - ATF_AVG_WEIGHT_FACTOR);
avg /= ATF_AVG_WEIGHT_DIV;
if (!avg)
avg = 1;
return avg;
}
static void atf_sta_update_avg(struct atf_station *sta, struct atf_stats *cur)
{
uint64_t total = atf_stats_total(cur);
D("sta "MAC_ADDR_FMT" total pkts: total=%d bulk=%d normal=%d prio=%d",
MAC_ADDR_DATA(sta->macaddr), (uint32_t)total,
(uint32_t)cur->bulk, (uint32_t)cur->normal, (uint32_t)cur->prio);
if (total < config.min_pkt_thresh)
return;
sta->avg_bulk = atf_stats_avg(sta->avg_bulk, cur->bulk, total);
sta->avg_prio = atf_stats_avg(sta->avg_prio, cur->prio, total);
D("avg bulk=%d prio=%d",
(sta->avg_bulk * 100) >> ATF_AVG_SCALE,
(sta->avg_prio * 100) >> ATF_AVG_SCALE);
sta->stats_idx = !sta->stats_idx;
}
static int
atf_sta_cb(struct nl_msg *msg, void *arg)
{
struct atf_interface *iface = arg;
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1];
struct atf_station *sta;
struct atf_stats *stats, diff = {};
struct nlattr *cur;
int idx = 0;
int rem;
nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[NL80211_ATTR_STA_INFO] || !tb[NL80211_ATTR_MAC])
return NL_SKIP;
if (nla_parse_nested(sinfo, NL80211_STA_INFO_MAX,
tb[NL80211_ATTR_STA_INFO], NULL))
return NL_SKIP;
if (!sinfo[NL80211_STA_INFO_TID_STATS])
return NL_SKIP;
sta = atf_interface_sta_get(iface, nla_data(tb[NL80211_ATTR_MAC]));
if (!sta)
return NL_SKIP;
stats = &sta->stats[sta->stats_idx];
memset(stats, 0, sizeof(*stats));
nla_for_each_nested(cur, sinfo[NL80211_STA_INFO_TID_STATS], rem)
atf_parse_tid_stats(iface, stats, idx++, cur);
atf_stats_diff(&diff, stats, &sta->stats[!sta->stats_idx]);
atf_sta_update_avg(sta, &diff);
atf_interface_sta_changed(iface, sta);
return NL_SKIP;
}
int atf_nl80211_interface_update(struct atf_interface *iface)
{
struct nl_msg *msg;
int ifindex;
ifindex = if_nametoindex(iface->ifname);
if (!ifindex)
return -1;
atf_interface_sta_update(iface);
msg = unl_genl_msg(&unl, NL80211_CMD_GET_STATION, true);
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifindex);
unl_genl_request(&unl, msg, atf_sta_cb, iface);
atf_interface_sta_flush(iface);
return 0;
nla_put_failure:
nlmsg_free(msg);
return -1;
}
int atf_nl80211_init(void)
{
return unl_genl_init(&unl, "nl80211");
}

View File

@@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <libubus.h>
#include "atf.h"
#define HOSTAPD_PREFIX "hostapd."
static struct ubus_auto_conn conn;
static struct blob_buf b;
enum {
ATF_CONFIG_RESET,
ATF_CONFIG_VO_Q_WEIGHT,
ATF_CONFIG_MIN_PKT_THRESH,
ATF_CONFIG_BULK_PERCENT_THR,
ATF_CONFIG_PRIO_PERCENT_THR,
ATF_CONFIG_WEIGHT_NORMAL,
ATF_CONFIG_WEIGHT_PRIO,
ATF_CONFIG_WEIGHT_BULK,
__ATF_CONFIG_MAX
};
static const struct blobmsg_policy atf_config_policy[__ATF_CONFIG_MAX] = {
[ATF_CONFIG_VO_Q_WEIGHT] = { "vo_queue_weight", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_MIN_PKT_THRESH] = { "update_pkt_threshold", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_BULK_PERCENT_THR] = { "bulk_percent_thresh", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_PRIO_PERCENT_THR] = { "prio_percent_thresh", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_WEIGHT_NORMAL] = { "weight_normal", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_WEIGHT_PRIO] = { "weight_prio", BLOBMSG_TYPE_INT32 },
[ATF_CONFIG_WEIGHT_BULK] = { "weight_bulk", BLOBMSG_TYPE_INT32 },
};
static int
atf_ubus_config(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__ATF_CONFIG_MAX];
struct blob_attr *cur;
static const struct {
int id;
int *field;
} field_map[] = {
{ ATF_CONFIG_VO_Q_WEIGHT, &config.voice_queue_weight },
{ ATF_CONFIG_MIN_PKT_THRESH, &config.min_pkt_thresh },
{ ATF_CONFIG_BULK_PERCENT_THR, &config.bulk_percent_thresh },
{ ATF_CONFIG_PRIO_PERCENT_THR, &config.prio_percent_thresh },
{ ATF_CONFIG_WEIGHT_NORMAL, &config.weight_normal },
{ ATF_CONFIG_WEIGHT_PRIO, &config.weight_prio },
{ ATF_CONFIG_WEIGHT_BULK, &config.weight_bulk },
};
bool reset = false;
int i;
blobmsg_parse(atf_config_policy, __ATF_CONFIG_MAX, tb,
blobmsg_data(msg), blobmsg_len(msg));
if ((cur = tb[ATF_CONFIG_RESET]) != NULL)
reset = blobmsg_get_bool(cur);
if (reset)
reset_config();
for (i = 0; i < ARRAY_SIZE(field_map); i++) {
if ((cur = tb[field_map[i].id]) != NULL)
*(field_map[i].field) = blobmsg_get_u32(cur);
}
return 0;
}
static const struct ubus_method atf_methods[] = {
UBUS_METHOD("config", atf_ubus_config, atf_config_policy),
};
static struct ubus_object_type atf_object_type =
UBUS_OBJECT_TYPE("atfpolicy", atf_methods);
static struct ubus_object atf_object = {
.name = "atfpolicy",
.type = &atf_object_type,
.methods = atf_methods,
.n_methods = ARRAY_SIZE(atf_methods),
};
static void
atf_ubus_add_interface(struct ubus_context *ctx, const char *name)
{
struct atf_interface *iface;
iface = atf_interface_get(name + strlen(HOSTAPD_PREFIX));
if (!iface)
return;
iface->ubus_obj = 0;
ubus_lookup_id(ctx, name, &iface->ubus_obj);
D("add interface %s", name + strlen(HOSTAPD_PREFIX));
}
static void
atf_ubus_lookup_cb(struct ubus_context *ctx, struct ubus_object_data *obj,
void *priv)
{
if (!strncmp(obj->path, HOSTAPD_PREFIX, strlen(HOSTAPD_PREFIX)))
atf_ubus_add_interface(ctx, obj->path);
}
void atf_ubus_set_sta_weight(struct atf_interface *iface, struct atf_station *sta)
{
D("set sta "MAC_ADDR_FMT" weight=%d", MAC_ADDR_DATA(sta->macaddr), sta->weight);
blob_buf_init(&b, 0);
blobmsg_printf(&b, "sta", MAC_ADDR_FMT, MAC_ADDR_DATA(sta->macaddr));
blobmsg_add_u32(&b, "weight", sta->weight);
if (ubus_invoke(&conn.ctx, iface->ubus_obj, "update_airtime", b.head, NULL, NULL, 100))
D("set airtime weight failed");
}
static void
atf_ubus_event_cb(struct ubus_context *ctx, struct ubus_event_handler *ev,
const char *type, struct blob_attr *msg)
{
static const struct blobmsg_policy policy =
{ "path", BLOBMSG_TYPE_STRING };
struct ubus_object_data obj;
struct blob_attr *attr;
blobmsg_parse(&policy, 1, &attr, blobmsg_data(msg), blobmsg_len(msg));
if (!attr)
return;
obj.path = blobmsg_get_string(attr);
atf_ubus_lookup_cb(ctx, &obj, NULL);
}
static void
ubus_connect_handler(struct ubus_context *ctx)
{
static struct ubus_event_handler ev = {
.cb = atf_ubus_event_cb
};
ubus_add_object(ctx, &atf_object);
ubus_register_event_handler(ctx, &ev, "ubus.object.add");
ubus_lookup(ctx, "hostapd.*", atf_ubus_lookup_cb, NULL);
}
int atf_ubus_init(void)
{
conn.cb = ubus_connect_handler;
ubus_auto_connect(&conn);
return 0;
}
void atf_ubus_stop(void)
{
ubus_auto_shutdown(&conn);
}

View File

@@ -14,6 +14,7 @@ include:
- qosify
packages:
- atfpolicy
- kmod-batman-adv
- batctl-default
- cJSON