qosify: update to latest HEAD

Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
John Crispin
2022-03-10 08:18:47 +01:00
parent 6f4e00aa46
commit ef7481596a
16 changed files with 211 additions and 2673 deletions

View File

@@ -1,5 +1,5 @@
#
# Copyright (C) 2008-2012 OpenWrt.org
# Copyright (C) 2021 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
@@ -9,26 +9,31 @@ include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=qosify
PKG_RELEASE:=1
PKG_SOURCE_URL=$(PROJECT_GIT)/project/qosify.git
PKG_SOURCE_PROTO:=git
PKG_SOURCE_DATE:=2022-03-06
PKG_SOURCE_VERSION:=f13b67c9a786567df240a8f3f608e2724ddaadba
PKG_MIRROR_HASH:=3d8629e711e46a6be79a46a6394165fd1761687f24b8ed954dc4f16f177cd90f
PKG_RELEASE:=$(AUTORELEASE)
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
PKG_BUILD_DEPENDS:=bpf-headers
PKG_FLAGS:=nonshared
include $(INCLUDE_DIR)/package.mk
include $(INCLUDE_DIR)/cmake.mk
include $(INCLUDE_DIR)/bpf.mk
include $(INCLUDE_DIR)/nls.mk
define Package/qosify
SECTION:=kernel
CATEGORY:=Kernel modules
SUBMENU:=Network Support
TITLE:=QoS classifier eBPF module
DEPENDS:=+libbpf +libubox +libubus +kmod-sched-cake +tc-full
PKGFLAGS+=nonshared
SECTION:=utils
CATEGORY:=Base system
TITLE:=A simple QoS solution based eBPF + CAKE
DEPENDS:=+libbpf +libubox +libubus +kmod-sched-cake +kmod-sched-bpf +kmod-ifb +tc-full $(BPF_DEPENDS)
endef
BPF_DOC = $(wildcard $(patsubst %,$(BPF_HEADERS_DIR)/scripts/%.py,bpf_doc bpf_helpers_doc))
define Build/Compile
$(call CompileBPF,$(PKG_BUILD_DIR)/qosify-bpf.c)
$(Build/Compile/Default)
@@ -36,17 +41,28 @@ endef
define Package/qosify/conffiles
/etc/config/qosify
/etc/qosify-defaults.conf
/etc/qosify/00-defaults.conf
endef
define Package/qosify/install
$(INSTALL_DIR) $(1)/lib/bpf $(1)/usr/sbin $(1)/etc/init.d $(1)/etc/config $(1)/etc/hotplug.d/net
$(INSTALL_DIR) \
$(1)/lib/bpf \
$(1)/usr/sbin \
$(1)/etc/init.d \
$(1)/etc/config \
$(1)/etc/qosify \
$(1)/etc/hotplug.d/net \
$(1)/etc/hotplug.d/iface
$(INSTALL_DATA) $(PKG_BUILD_DIR)/qosify-bpf.o $(1)/lib/bpf
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/qosify $(1)/usr/sbin/
$(INSTALL_BIN) \
$(PKG_INSTALL_DIR)/usr/bin/qosify \
./files/qosify-status \
$(1)/usr/sbin/
$(INSTALL_BIN) ./files/qosify.init $(1)/etc/init.d/qosify
$(INSTALL_DATA) ./files/qosify-defaults.conf $(1)/etc/qosify-defaults.conf
$(INSTALL_DATA) ./files/qosify-defaults.conf $(1)/etc/qosify/00-defaults.conf
$(INSTALL_DATA) ./files/qosify.conf $(1)/etc/config/qosify
$(INSTALL_DATA) ./files/qosify.hotplug $(1)/etc/hotplug.d/net/10-qosify
$(INSTALL_DATA) ./files/qosify.hotplug $(1)/etc/hotplug.d/iface/10-qosify
endef
$(eval $(call BuildPackage,qosify))

View File

@@ -1,17 +1,17 @@
# DNS
tcp:53 CS5
tcp:5353 CS5
udp:53 CS5
udp:5353 CS5
tcp:53 voice
tcp:5353 voice
udp:53 voice
udp:5353 voice
# NTP
udp:123 CS6
udp:123 voice
# SSH
tcp:22 +CS4
tcp:22 +video
# HTTP/QUIC
tcp:80 +CS3
tcp:443 +CS3
udp:80 +CS3
udp:443 +CS3
tcp:80 +besteffort
tcp:443 +besteffort
udp:80 +besteffort
udp:443 +besteffort

View File

@@ -0,0 +1,70 @@
#!/bin/sh
. /usr/share/libubox/jshn.sh
dev_status() {
tc -s qdisc sh dev "$1" root
echo
}
common_status() {
json_get_vars ifname ingress egress
[ -n "$ifname" ] || return
[ "$egress" -gt 0 ] && {
echo "egress status:"
dev_status "$ifname"
}
[ "$ingress" -gt 0 ] && {
echo "ingress status:"
dev_status "$(printf %.16s "ifb-$ifname")"
}
}
is_active() {
json_get_vars active
[ "${active:-0}" -gt 0 ]
}
device_status() {
local name="$2"
json_select "$name"
if is_active; then
status="active"
else
status="not found"
fi
echo "===== device $name: $status ====="
is_active && common_status
json_select ..
}
interface_status() {
local name="$2"
json_select "$name"
if is_active; then
status="active"
elif ubus -S -t 0 wait_for "network.interface.$name"; then
status="down"
else
status="not found"
fi
echo "===== interface $name: $status ====="
is_active && common_status
json_select ..
}
json_load "$(ubus call qosify status)"
json_for_each_item device_status devices
json_for_each_item interface_status interfaces

View File

@@ -1,24 +1,42 @@
config defaults
list defaults /etc/qosify-defaults.conf
option dscp_prio CS5
option dscp_icmp CS6
option dscp_bulk CS0
option dscp_default_udp CS4
option bulk_trigger_timeout 5
option bulk_trigger_pps 100
list defaults /etc/qosify/*.conf
option dscp_prio video
option dscp_icmp +besteffort
option dscp_default_udp besteffort
option prio_max_avg_pkt_len 500
config class besteffort
option ingress CS0
option egress CS0
config class bulk
option ingress LE
option egress LE
config class video
option ingress AF41
option egress AF41
config class voice
option ingress CS6
option egress CS6
option bulk_trigger_pps 100
option bulk_trigger_timeout 5
option dscp_bulk CS0
config interface wan
option name wan
option disabled 1
option bandwidth_up 100mbit
option bandwidth_down 100mbit
option overhead_type none
# defaults:
option ingress 1
option egress 1
option mode diffserv4
option nat 1
option host_isolate 1
option autorate_ingress 1
option autorate_ingress 0
option ingress_options ""
option egress_options ""
option options ""

View File

@@ -1,5 +1,5 @@
#!/bin/sh /etc/rc.common
# Copyright (c) 2014 OpenWrt.org
# Copyright (c) 2021 OpenWrt.org
START=19
@@ -15,6 +15,16 @@ add_option() {
[ -n "$val" ] && json_add_$type "$name" "$val"
}
add_flow_config() {
local cfg="$1"
add_option string dscp_prio
add_option string dscp_bulk
add_option int bulk_trigger_timeout
add_option int bulk_trigger_pps
add_option int prio_max_avg_pkt_len
}
add_defaults() {
cfg="$1"
@@ -27,15 +37,11 @@ add_defaults() {
done
json_close_array
add_flow_config "$cfg"
add_option int timeout
add_option string dscp_prio
add_option string dscp_bulk
add_option string dscp_icmp
add_option string dscp_default_udp
add_option string dscp_default_tcp
add_option int bulk_trigger_timeout
add_option int bulk_trigger_pps
add_option int prio_max_avg_pkt_len
}
add_interface() {
@@ -57,19 +63,74 @@ add_interface() {
bw_down="${bw_down:-$bw}"
[ -n "$bw_down" ] && json_add_string bandwidth_down "$bw_down"
add_option string bandwidth
add_option string bandwidth
add_option boolean ingress
add_option boolean egress
add_option string mode
add_option boolean host_isolate
add_option boolean nat
add_option boolean host_isolate
add_option boolean autorate_ingress
add_option string ingress_options
add_option string egress_options
add_option string options
config_get user_options "$cfg" options
config_get otype "$cfg" overhead_type
options=
case "$otype" in
none);;
manual)
config_get overhead "$cfg" overhead
[ -n "$overhead" ] && append options "overhead $overhead"
config_get encap "$cfg" overhead_encap
[ -n "$encap" ] && append options "$encap"
;;
conservative|\
pppoa-vcmux|\
pppoa-llc|\
pppoe-vcmux|\
pppoe-llcsnap|\
bridged-vcmux|\
bridged-llcsnap|\
ipoa-vcmux|\
ipoa-llcsnap|\
pppoe-ptm|\
bridged-ptm|\
docsis|\
ethernet)
append options "$otype"
;;
esac
config_get mpu "$cfg" overhead_mpu
[ -n "$mpu" ] && append options "mpu $mpu"
config_get ovlan "$cfg" overhead_vlan
[ "${ovlan:-0}" -ge 2 ] && append options "ether-vlan"
[ "${ovlan:-0}" -ge 1 ] && append options "ether-vlan"
[ -n "$user_options" ] && append options "$user_options"
[ -n "$options" ] && json_add_string options "$options"
json_close_object
}
add_class() {
local cfg="$1"
config_get value "$cfg" value
config_get ingress "$cfg" ingress
config_get egress "$cfg" egress
json_add_object "$cfg"
json_add_string ingress "${ingress:-$value}"
json_add_string egress "${egress:-$value}"
add_flow_config "$cfg"
json_close_object
}
reload_service() {
json_init
@@ -81,6 +142,11 @@ reload_service() {
config_foreach add_interface interface
json_close_object
json_add_object classes
config_foreach add_class class
config_foreach add_class alias
json_close_object
json_add_object devices
config_foreach add_interface device
json_close_object

View File

@@ -1,15 +0,0 @@
cmake_minimum_required(VERSION 3.10)
PROJECT(qosify 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(bpf NAMES bpf)
ADD_EXECUTABLE(qosify main.c loader.c map.c ubus.c interface.c)
TARGET_LINK_LIBRARIES(qosify ${bpf} ubox ubus)
INSTALL(TARGETS qosify
RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
)

View File

@@ -1,112 +0,0 @@
QoSify is simple daemon for setting up and managing CAKE along with a custom
eBPF based classifier that sets DSCP fields of packets.
It supports the following features:
- simple TCP/UDP port based mapping
- IP address based mapping
- priority boosting based on average packet size
- bulk flow detection based on number of packets per second
- dynamically add IP entries with timeout
- dns regex entries and ubus api for providing dns lookup results
It can be configured via ubus call qosify config.
This call supports the following parameters:
- "reset": BOOL
Reset the config to defaults instead of only updating supplied values
- "files": ARRAY of STRING
List of files with port/IP/host mappings
- "timeout": INT32
Default timeout for dynamically added entries
- "dscp_default_udp": STRING
Default DSCP value for UDP packets
- "dscp_default_tcp": STRING
Default DSCP value for TCP packets
- "dscp_prio": STRING
DSCP value for priority-marked packets
- "dscp_bulk": STRING
DSCP value for bulk-marked packets
- "dscp_icmp": STRING
DSCP value for ICMP packets
- "bulk_trigger_pps": INT32
Number of packets per second to trigger bulk flow detection
- "bulk_trigger_timeout": INT32
Time below bulk_trigger_pps threshold until a bulk flow mark is removed
- "prio_max_avg_pkt_len": INT32
Maximum average packet length for marking a flow as priority
- "interfaces": TABLE of TABLE
netifd interfaces to enable QoS on
- "devices": TABLE of TABLE
netdevs to enable QoS on
interface/device properties:
- "bandwidth_up": STRING
Uplink bandwidth (same format as tc)
- "bandwidth_down": STRING
Downlink bandwidth (same format as tc)
- "ingress": BOOL
Enable ingress shaping
- "egress": BOOL
Enable egress shaping
- "mode": STRING
CAKE diffserv mode
- "nat": BOOL
Enable CAKE NAT host detection via conntrack
- "host_isolate": BOOL
Enable CAKE host isolation
- "autorate_ingress": BOOL
Enable CAKE automatic rate estimation for ingress
- "ingress_options": STRING
CAKE ingress options
- "egress_options": STRING
CAKE egress options
- "options": STRING
CAKE options for ingress + egress
Mapping file syntax:
Each line has two whitespace separated fields, match and dscp
match is one of:
- tcp:<port>[-<endport>]
TCP single port, or range from <port> to <endport>
- udp:<port>[-<endport>]
UDP single port, or range from <port> to <endport>
- <ipaddr>
IPv4 address, e.g. 1.1.1.1
- <ipv6addr>
IPv6 address, e.g. ff01::1
- dns:<regex>
POSIX.2 extended regular expression for matching hostnames
Only works, if dns lookups are passed to qosify via the add_dns_host ubus call.
dscp can be a raw value, or a codepoint like CS0
Adding a + in front of the value tells qosify to only override the DSCP value if it is zero
Planned features:
- Integration with dnsmasq to support hostname pattern based DSCP marking
- Support for LAN host based priority

View File

@@ -1,563 +0,0 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <net/if.h>
#include <unistd.h>
#include <errno.h>
#include <libubox/vlist.h>
#include <libubox/avl-cmp.h>
#include <libubox/uloop.h>
#include "qosify.h"
static void interface_update_cb(struct vlist_tree *tree,
struct vlist_node *node_new,
struct vlist_node *node_old);
static VLIST_TREE(devices, avl_strcmp, interface_update_cb, true, false);
static VLIST_TREE(interfaces, avl_strcmp, interface_update_cb, true, false);
static int socket_fd;
#define APPEND(_buf, _ofs, _format, ...) _ofs += snprintf(_buf + _ofs, sizeof(_buf) - _ofs, _format, ##__VA_ARGS__)
struct qosify_iface_config {
struct blob_attr *data;
bool ingress;
bool egress;
bool nat;
bool host_isolate;
bool autorate_ingress;
const char *bandwidth_up;
const char *bandwidth_down;
const char *mode;
const char *common_opts;
const char *ingress_opts;
const char *egress_opts;
};
struct qosify_iface {
struct vlist_node node;
char ifname[IFNAMSIZ];
bool active;
bool device;
struct blob_attr *config_data;
struct qosify_iface_config config;
};
enum {
IFACE_ATTR_BW_UP,
IFACE_ATTR_BW_DOWN,
IFACE_ATTR_INGRESS,
IFACE_ATTR_EGRESS,
IFACE_ATTR_MODE,
IFACE_ATTR_NAT,
IFACE_ATTR_HOST_ISOLATE,
IFACE_ATTR_AUTORATE_IN,
IFACE_ATTR_INGRESS_OPTS,
IFACE_ATTR_EGRESS_OPTS,
IFACE_ATTR_OPTS,
__IFACE_ATTR_MAX
};
static inline const char *qosify_iface_name(struct qosify_iface *iface)
{
return iface->node.avl.key;
}
static void
iface_config_parse(struct blob_attr *attr, struct blob_attr **tb)
{
static const struct blobmsg_policy policy[__IFACE_ATTR_MAX] = {
[IFACE_ATTR_BW_UP] = { "bandwidth_up", BLOBMSG_TYPE_STRING },
[IFACE_ATTR_BW_DOWN] = { "bandwidth_down", BLOBMSG_TYPE_STRING },
[IFACE_ATTR_INGRESS] = { "ingress", BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_EGRESS] = { "egress", BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_MODE] = { "mode", BLOBMSG_TYPE_STRING },
[IFACE_ATTR_NAT] = { "nat", BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_HOST_ISOLATE] = { "host_isolate", BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_AUTORATE_IN] = { "autorate_ingress", BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_INGRESS_OPTS] = { "ingress_options", BLOBMSG_TYPE_STRING },
[IFACE_ATTR_EGRESS_OPTS] = { "egress_options", BLOBMSG_TYPE_STRING },
[IFACE_ATTR_OPTS] = { "options", BLOBMSG_TYPE_STRING },
};
blobmsg_parse(policy, __IFACE_ATTR_MAX, tb, blobmsg_data(attr), blobmsg_len(attr));
}
static bool
iface_config_equal(struct qosify_iface *if1, struct qosify_iface *if2)
{
struct blob_attr *tb1[__IFACE_ATTR_MAX], *tb2[__IFACE_ATTR_MAX];
int i;
iface_config_parse(if1->config_data, tb1);
iface_config_parse(if2->config_data, tb2);
for (i = 0; i < __IFACE_ATTR_MAX; i++) {
if (!!tb1[i] != !!tb2[i])
return false;
if (!tb1[i])
continue;
if (blob_raw_len(tb1[i]) != blob_raw_len(tb2[i]))
return false;
if (memcmp(tb1[i], tb2[i], blob_raw_len(tb1[i])) != 0)
return false;
}
return true;
}
static const char *check_str(struct blob_attr *attr)
{
const char *str = blobmsg_get_string(attr);
if (strchr(str, '\''))
return NULL;
return str;
}
static void
iface_config_set(struct qosify_iface *iface, struct blob_attr *attr)
{
struct qosify_iface_config *cfg = &iface->config;
struct blob_attr *tb[__IFACE_ATTR_MAX];
struct blob_attr *cur;
iface_config_parse(attr, tb);
memset(cfg, 0, sizeof(*cfg));
/* defaults */
cfg->mode = "diffserv4";
cfg->ingress = true;
cfg->egress = true;
cfg->host_isolate = true;
cfg->autorate_ingress = true;
cfg->nat = !iface->device;
if ((cur = tb[IFACE_ATTR_BW_UP]) != NULL)
cfg->bandwidth_up = check_str(cur);
if ((cur = tb[IFACE_ATTR_BW_DOWN]) != NULL)
cfg->bandwidth_down = check_str(cur);
if ((cur = tb[IFACE_ATTR_MODE]) != NULL)
cfg->mode = check_str(cur);
if ((cur = tb[IFACE_ATTR_OPTS]) != NULL)
cfg->common_opts = check_str(cur);
if ((cur = tb[IFACE_ATTR_EGRESS_OPTS]) != NULL)
cfg->egress_opts = check_str(cur);
if ((cur = tb[IFACE_ATTR_INGRESS_OPTS]) != NULL)
cfg->ingress_opts = check_str(cur);
if ((cur = tb[IFACE_ATTR_INGRESS]) != NULL)
cfg->ingress = blobmsg_get_bool(cur);
if ((cur = tb[IFACE_ATTR_EGRESS]) != NULL)
cfg->egress = blobmsg_get_bool(cur);
if ((cur = tb[IFACE_ATTR_NAT]) != NULL)
cfg->nat = blobmsg_get_bool(cur);
if ((cur = tb[IFACE_ATTR_HOST_ISOLATE]) != NULL)
cfg->host_isolate = blobmsg_get_bool(cur);
if ((cur = tb[IFACE_ATTR_AUTORATE_IN]) != NULL)
cfg->autorate_ingress = blobmsg_get_bool(cur);
}
static const char *
interface_ifb_name(struct qosify_iface *iface)
{
static char ifname[IFNAMSIZ + 1] = "ifb-";
int len = strlen(iface->ifname);
if (len + 4 < IFNAMSIZ) {
snprintf(ifname + 4, IFNAMSIZ - 4, "%s", iface->ifname);
return ifname;
}
ifname[4] = iface->ifname[0];
ifname[5] = iface->ifname[1];
snprintf(ifname + 6, IFNAMSIZ - 6, "%s", iface->ifname + len - (IFNAMSIZ + 6) - 1);
return ifname;
}
static int run_cmd(char *cmd, bool ignore)
{
char *argv[] = { "sh", "-c", cmd, NULL };
bool first = true;
int status = -1;
char buf[512];
int fds[2];
FILE *f;
int pid;
if (pipe(fds))
return -1;
pid = fork();
if (!pid) {
close(fds[0]);
if (fds[1] != STDOUT_FILENO)
dup2(fds[1], STDOUT_FILENO);
if (fds[1] != STDERR_FILENO)
dup2(fds[1], STDERR_FILENO);
if (fds[1] > STDERR_FILENO)
close(fds[1]);
execv("/bin/sh", argv);
exit(1);
}
if (pid < 0)
return -1;
close(fds[1]);
f = fdopen(fds[0], "r");
if (!f) {
close(fds[0]);
goto out;
}
while (fgets(buf, sizeof(buf), f) != NULL) {
if (!strlen(buf))
break;
if (ignore)
continue;
if (first) {
ULOG_WARN("Command: %s\n", cmd);
first = false;
}
ULOG_WARN("%s%s", buf, strchr(buf, '\n') ? "" : "\n");
}
fclose(f);
out:
while (waitpid(pid, &status, 0) < 0)
if (errno != EINTR)
break;
return status;
}
static int
prepare_tc_cmd(char *buf, int len, const char *type, const char *cmd,
const char *dev, const char *extra)
{
return snprintf(buf, len, "tc %s %s dev '%s' %s", type, cmd, dev, extra);
}
static int
cmd_del_qdisc(const char *ifname, const char *type)
{
char buf[64];
prepare_tc_cmd(buf, sizeof(buf), "qdisc", "del", ifname, type);
return run_cmd(buf, true);
}
static int
cmd_add_qdisc(struct qosify_iface *iface, const char *ifname, bool egress, bool eth)
{
struct qosify_iface_config *cfg = &iface->config;
const char *bw = egress ? cfg->bandwidth_up : cfg->bandwidth_down;
const char *dir_opts = egress ? cfg->egress_opts : cfg->ingress_opts;
char buf[512];
int ofs;
cmd_del_qdisc(ifname, "root");
ofs = prepare_tc_cmd(buf, sizeof(buf), "qdisc", "add", ifname, "root handle 1: cake");
if (bw)
APPEND(buf, ofs, " bandwidth %s", bw);
APPEND(buf, ofs, " %s %sgress", cfg->mode, egress ? "e" : "in");
if (cfg->host_isolate)
APPEND(buf, ofs, " %snat dual-%shost",
cfg->nat ? "" : "no",
egress ? "src" : "dst");
else
APPEND(buf, ofs, " flows");
APPEND(buf, ofs, " %s %s",
cfg->common_opts ? cfg->common_opts : "",
dir_opts ? dir_opts : "");
run_cmd(buf, false);
ofs = prepare_tc_cmd(buf, sizeof(buf), "filter", "add", ifname, "parent 1: bpf");
APPEND(buf, ofs, " object-pinned /sys/fs/bpf/qosify_%sgress_%s verbose direct-action",
egress ? "e" : "in",
eth ? "eth" : "ip");
return run_cmd(buf, false);
}
static int
cmd_del_ingress(struct qosify_iface *iface)
{
char buf[256];
cmd_del_qdisc(iface->ifname, "handle ffff: ingress");
snprintf(buf, sizeof(buf), "ip link del '%s'", interface_ifb_name(iface));
return run_cmd(buf, true);
}
static int
cmd_add_ingress(struct qosify_iface *iface, bool eth)
{
const char *ifbdev = interface_ifb_name(iface);
char buf[256];
int ofs;
cmd_del_ingress(iface);
ofs = prepare_tc_cmd(buf, sizeof(buf), "qdisc", "add", iface->ifname, " handle ffff: ingress");
run_cmd(buf, false);
snprintf(buf, sizeof(buf), "ip link add '%s' type ifb", ifbdev);
run_cmd(buf, false);
cmd_add_qdisc(iface, ifbdev, false, eth);
snprintf(buf, sizeof(buf), "ip link set dev '%s' up", ifbdev);
run_cmd(buf, false);
ofs = prepare_tc_cmd(buf, sizeof(buf), "filter", "add", iface->ifname, " parent ffff:");
APPEND(buf, ofs, " protocol all prio 10 u32 match u32 0 0 "
"flowid 1:1 action mirred egress redirect dev '%s'", ifbdev);
return run_cmd(buf, false);
}
static void
interface_start(struct qosify_iface *iface)
{
struct ifreq ifr = {};
bool eth;
if (!iface->ifname[0] || iface->active)
return;
ULOG_INFO("start interface %s\n", iface->ifname);
strncpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name));
if (ioctl(socket_fd, SIOCGIFHWADDR, &ifr) < 0) {
ULOG_ERR("ioctl(SIOCGIFHWADDR, %s) failed: %s\n", iface->ifname, strerror(errno));
return;
}
eth = ifr.ifr_hwaddr.sa_family == ARPHRD_ETHER;
if (iface->config.egress)
cmd_add_qdisc(iface, iface->ifname, true, eth);
if (iface->config.ingress)
cmd_add_ingress(iface, eth);
iface->active = true;
}
static void
interface_stop(struct qosify_iface *iface)
{
if (!iface->ifname[0] || !iface->active)
return;
ULOG_INFO("stop interface %s\n", iface->ifname);
iface->active = false;
if (iface->config.egress)
cmd_del_qdisc(iface->ifname, "root");
if (iface->config.ingress)
cmd_del_ingress(iface);
}
static void
interface_set_config(struct qosify_iface *iface, struct blob_attr *config)
{
iface->config_data = blob_memdup(config);
iface_config_set(iface, iface->config_data);
interface_start(iface);
}
static void
interface_update_cb(struct vlist_tree *tree,
struct vlist_node *node_new, struct vlist_node *node_old)
{
struct qosify_iface *if_new = NULL, *if_old = NULL;
if (node_new)
if_new = container_of(node_new, struct qosify_iface, node);
if (node_old)
if_old = container_of(node_old, struct qosify_iface, node);
if (if_new && if_old) {
if (!iface_config_equal(if_old, if_new)) {
interface_stop(if_old);
free(if_old->config_data);
interface_set_config(if_old, if_new->config_data);
}
free(if_new);
return;
}
if (if_old) {
interface_stop(if_old);
free(if_old->config_data);
free(if_old);
}
if (if_new)
interface_set_config(if_new, if_new->config_data);
}
static void
interface_create(struct blob_attr *attr, bool device)
{
struct qosify_iface *iface;
const char *name = blobmsg_name(attr);
int name_len = strlen(name);
char *name_buf;
if (strchr(name, '\''))
return;
if (name_len >= IFNAMSIZ)
return;
if (blobmsg_type(attr) != BLOBMSG_TYPE_TABLE)
return;
iface = calloc_a(sizeof(*iface), &name_buf, name_len + 1);
strcpy(name_buf, blobmsg_name(attr));
iface->config_data = attr;
iface->device = device;
vlist_add(device ? &devices : &interfaces, &iface->node, name_buf);
}
void qosify_iface_config_update(struct blob_attr *ifaces, struct blob_attr *devs)
{
struct blob_attr *cur;
int rem;
vlist_update(&devices);
blobmsg_for_each_attr(cur, devs, rem)
interface_create(cur, true);
vlist_flush(&devices);
vlist_update(&interfaces);
blobmsg_for_each_attr(cur, ifaces, rem)
interface_create(cur, false);
vlist_flush(&interfaces);
}
static void
qosify_iface_check_device(struct qosify_iface *iface)
{
const char *name = qosify_iface_name(iface);
int ifindex;
ifindex = if_nametoindex(name);
if (!ifindex) {
interface_stop(iface);
iface->ifname[0] = 0;
} else {
snprintf(iface->ifname, sizeof(iface->ifname), "%s", name);
interface_start(iface);
}
}
static void
qosify_iface_check_interface(struct qosify_iface *iface)
{
const char *name = qosify_iface_name(iface);
char ifname[IFNAMSIZ];
if (qosify_ubus_check_interface(name, ifname, sizeof(ifname)) == 0) {
snprintf(iface->ifname, sizeof(iface->ifname), "%s", ifname);
interface_start(iface);
} else {
interface_stop(iface);
iface->ifname[0] = 0;
}
}
static void qos_iface_check_cb(struct uloop_timeout *t)
{
struct qosify_iface *iface;
vlist_for_each_element(&devices, iface, node)
qosify_iface_check_device(iface);
vlist_for_each_element(&interfaces, iface, node)
qosify_iface_check_interface(iface);
}
void qosify_iface_check(void)
{
static struct uloop_timeout timer = {
.cb = qos_iface_check_cb,
};
uloop_timeout_set(&timer, 10);
}
void qosify_iface_status(struct blob_buf *b)
{
struct qosify_iface *iface;
void *c, *i;
c = blobmsg_open_table(b, "devices");
vlist_for_each_element(&devices, iface, node) {
i = blobmsg_open_table(b, qosify_iface_name(iface));
blobmsg_add_u8(b, "active", iface->active);
blobmsg_close_table(b, i);
}
blobmsg_close_table(b, c);
c = blobmsg_open_table(b, "interfaces");
vlist_for_each_element(&interfaces, iface, node) {
i = blobmsg_open_table(b, qosify_iface_name(iface));
blobmsg_add_u8(b, "active", iface->active);
if (iface->ifname)
blobmsg_add_string(b, "ifname", iface->ifname);
blobmsg_close_table(b, i);
}
blobmsg_close_table(b, c);
}
int qosify_iface_init(void)
{
socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (socket < 0)
return -1;
return 0;
}
void qosify_iface_stop(void)
{
struct qosify_iface *iface;
vlist_for_each_element(&interfaces, iface, node)
interface_stop(iface);
vlist_for_each_element(&devices, iface, node)
interface_stop(iface);
}

View File

@@ -1,121 +0,0 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <sys/resource.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <glob.h>
#include <unistd.h>
#include "qosify.h"
static int qosify_bpf_pr(enum libbpf_print_level level, const char *format,
va_list args)
{
return vfprintf(stderr, format, args);
}
static void qosify_init_env(void)
{
struct rlimit limit = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
setrlimit(RLIMIT_MEMLOCK, &limit);
}
static void qosify_fill_rodata(struct bpf_object *obj, uint32_t flags)
{
struct bpf_map *map = NULL;
while ((map = bpf_map__next(map, obj)) != NULL) {
if (!strstr(bpf_map__name(map), ".rodata"))
continue;
bpf_map__set_initial_value(map, &flags, sizeof(flags));
}
}
static int
qosify_create_program(const char *suffix, uint32_t flags)
{
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
.pin_root_path = CLASSIFY_DATA_PATH,
);
struct bpf_program *prog;
struct bpf_object *obj;
char path[256];
int err;
snprintf(path, sizeof(path), CLASSIFY_PIN_PATH "_" "%s", suffix);
obj = bpf_object__open_file(CLASSIFY_PROG_PATH, &opts);
err = libbpf_get_error(obj);
if (err) {
perror("bpf_object__open_file");
return -1;
}
prog = bpf_object__find_program_by_title(obj, "classifier");
if (!prog) {
fprintf(stderr, "Can't find classifier prog\n");
return -1;
}
bpf_program__set_type(prog, BPF_PROG_TYPE_SCHED_CLS);
qosify_fill_rodata(obj, flags);
err = bpf_object__load(obj);
if (err) {
perror("bpf_object__load");
return -1;
}
libbpf_set_print(NULL);
unlink(path);
err = bpf_program__pin(prog, path);
if (err) {
fprintf(stderr, "Failed to pin program to %s: %s\n",
path, strerror(-err));
}
bpf_object__close(obj);
return 0;
}
int qosify_loader_init(void)
{
static const struct {
const char *suffix;
uint32_t flags;
} progs[] = {
{ "egress_eth", 0 },
{ "egress_ip", QOSIFY_IP_ONLY },
{ "ingress_eth", QOSIFY_INGRESS },
{ "ingress_ip", QOSIFY_INGRESS | QOSIFY_IP_ONLY },
};
glob_t g;
int i;
if (glob(CLASSIFY_DATA_PATH "/*", 0, NULL, &g) == 0) {
for (i = 0; i < g.gl_pathc; i++)
unlink(g.gl_pathv[i]);
}
libbpf_set_print(qosify_bpf_pr);
qosify_init_env();
for (i = 0; i < ARRAY_SIZE(progs); i++) {
if (qosify_create_program(progs[i].suffix, progs[i].flags))
return -1;
}
return 0;
}

View File

@@ -1,72 +0,0 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <libubox/uloop.h>
#include "qosify.h"
static int usage(const char *progname)
{
fprintf(stderr, "Usage: %s [options]\n"
"Options:\n"
" -l <file> Load defaults from <file>\n"
" -o only load program/maps without running as daemon\n"
"\n", progname);
return 1;
}
int main(int argc, char **argv)
{
const char *load_file = NULL;
bool oneshot = false;
int ch;
while ((ch = getopt(argc, argv, "fl:o")) != -1) {
switch (ch) {
case 'f':
break;
case 'l':
load_file = optarg;
break;
case 'o':
oneshot = true;
break;
default:
return usage(argv[0]);
}
}
if (qosify_loader_init())
return 2;
if (qosify_map_init())
return 2;
if (qosify_map_load_file(load_file))
return 2;
if (oneshot)
return 0;
ulog_open(ULOG_SYSLOG, LOG_DAEMON, "qosify");
uloop_init();
if (qosify_ubus_init() ||
qosify_iface_init())
return 2;
uloop_run();
qosify_ubus_stop();
qosify_iface_stop();
uloop_done();
return 0;
}

View File

@@ -1,735 +0,0 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <libubox/uloop.h>
#include "qosify.h"
static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr);
static int qosify_map_fds[__CL_MAP_MAX];
static AVL_TREE(map_data, qosify_map_entry_cmp, false, NULL);
static LIST_HEAD(map_files);
static uint32_t next_timeout;
static uint8_t qosify_dscp_default[2] = { 0xff, 0xff };
int qosify_map_timeout;
int qosify_active_timeout;
struct qosify_config config;
struct qosify_map_file {
struct list_head list;
char filename[];
};
static const struct {
const char *name;
const char *type_name;
} qosify_map_info[] = {
[CL_MAP_TCP_PORTS] = { "tcp_ports", "tcp_port" },
[CL_MAP_UDP_PORTS] = { "udp_ports", "udp_port" },
[CL_MAP_IPV4_ADDR] = { "ipv4_map", "ipv4_addr" },
[CL_MAP_IPV6_ADDR] = { "ipv6_map", "ipv6_addr" },
[CL_MAP_CONFIG] = { "config", "config" },
[CL_MAP_DNS] = { "dns", "dns" },
};
static const struct {
const char name[5];
uint8_t val;
} codepoints[] = {
{ "CS0", 0 },
{ "CS1", 8 },
{ "CS2", 16 },
{ "CS3", 24 },
{ "CS4", 32 },
{ "CS5", 40 },
{ "CS6", 48 },
{ "CS7", 56 },
{ "AF11", 10 },
{ "AF12", 12 },
{ "AF13", 14 },
{ "AF21", 18 },
{ "AF22", 20 },
{ "AF22", 22 },
{ "AF31", 26 },
{ "AF32", 28 },
{ "AF33", 30 },
{ "AF41", 34 },
{ "AF42", 36 },
{ "AF43", 38 },
{ "EF", 46 },
{ "VA", 44 },
{ "LE", 1 },
{ "DF", 0 },
};
static void qosify_map_timer_cb(struct uloop_timeout *t)
{
qosify_map_gc();
}
static struct uloop_timeout qosify_map_timer = {
.cb = qosify_map_timer_cb,
};
static uint32_t qosify_gettime(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec;
}
static const char *
qosify_map_path(enum qosify_map_id id)
{
static char path[128];
const char *name;
if (id >= ARRAY_SIZE(qosify_map_info))
return NULL;
name = qosify_map_info[id].name;
if (!name)
return NULL;
snprintf(path, sizeof(path), "%s/%s", CLASSIFY_DATA_PATH, name);
return path;
}
static int qosify_map_get_fd(enum qosify_map_id id)
{
const char *path = qosify_map_path(id);
int fd;
if (!path)
return -1;
fd = bpf_obj_get(path);
if (fd < 0)
fprintf(stderr, "Failed to open map %s: %s\n", path, strerror(errno));
return fd;
}
static void qosify_map_clear_list(enum qosify_map_id id)
{
int fd = qosify_map_fds[id];
__u32 key[4] = {};
while (bpf_map_get_next_key(fd, &key, &key) != -1)
bpf_map_delete_elem(fd, &key);
}
static void __qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
{
struct qosify_map_data data = {
.id = id,
};
int fd = qosify_map_fds[id];
int i;
val |= QOSIFY_DSCP_DEFAULT_FLAG;
for (i = 0; i < (1 << 16); i++) {
data.addr.port = htons(i);
if (avl_find(&map_data, &data))
continue;
bpf_map_update_elem(fd, &data.addr, &val, BPF_ANY);
}
}
void qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val)
{
bool udp;
if (id == CL_MAP_TCP_PORTS)
udp = false;
else if (id == CL_MAP_UDP_PORTS)
udp = true;
else
return;
if (qosify_dscp_default[udp] == val)
return;
qosify_dscp_default[udp] = val;
__qosify_map_set_dscp_default(id, val);
}
int qosify_map_init(void)
{
int i;
for (i = 0; i < CL_MAP_DNS; i++) {
qosify_map_fds[i] = qosify_map_get_fd(i);
if (qosify_map_fds[i] < 0)
return -1;
}
qosify_map_clear_list(CL_MAP_IPV4_ADDR);
qosify_map_clear_list(CL_MAP_IPV6_ADDR);
qosify_map_reset_config();
return 0;
}
static char *str_skip(char *str, bool space)
{
while (*str && isspace(*str) == space)
str++;
return str;
}
static int
qosify_map_codepoint(const char *val)
{
int i;
for (i = 0; i < ARRAY_SIZE(codepoints); i++)
if (!strcmp(codepoints[i].name, val))
return codepoints[i].val;
return 0xff;
}
static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr)
{
const struct qosify_map_data *d1 = k1;
const struct qosify_map_data *d2 = k2;
if (d1->id != d2->id)
return d2->id - d1->id;
if (d1->id == CL_MAP_DNS)
return strcmp(d1->addr.dns.pattern, d2->addr.dns.pattern);
return memcmp(&d1->addr, &d2->addr, sizeof(d1->addr));
}
static struct qosify_map_entry *
__qosify_map_alloc_entry(struct qosify_map_data *data)
{
struct qosify_map_entry *e;
char *pattern;
if (data->id < CL_MAP_DNS) {
e = calloc(1, sizeof(*e));
memcpy(&e->data.addr, &data->addr, sizeof(e->data.addr));
return e;
}
e = calloc_a(sizeof(*e), &pattern, strlen(data->addr.dns.pattern) + 1);
strcpy(pattern, data->addr.dns.pattern);
e->data.addr.dns.pattern = pattern;
if (regcomp(&e->data.addr.dns.regex, pattern,
REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
free(e);
return NULL;
}
return e;
}
static void __qosify_map_set_entry(struct qosify_map_data *data)
{
int fd = qosify_map_fds[data->id];
struct qosify_map_entry *e;
bool file = data->file;
int32_t delta = 0;
bool add = data->dscp != 0xff;
uint8_t prev_dscp = 0xff;
e = avl_find_element(&map_data, data, e, avl);
if (!e) {
if (!add)
return;
e = __qosify_map_alloc_entry(data);
if (!e)
return;
e->avl.key = &e->data;
e->data.id = data->id;
avl_insert(&map_data, &e->avl);
} else {
prev_dscp = e->data.dscp;
}
if (file)
e->data.file = add;
else
e->data.user = add;
if (add) {
if (file)
e->data.file_dscp = data->dscp;
if (!e->data.user || !file)
e->data.dscp = data->dscp;
} else if (e->data.file && !file) {
e->data.dscp = e->data.file_dscp;
}
if (e->data.dscp != prev_dscp && data->id < CL_MAP_DNS) {
struct qosify_ip_map_val val = {
.dscp = e->data.dscp,
.seen = 1,
};
bpf_map_update_elem(fd, &data->addr, &val, BPF_ANY);
}
if (add) {
if (qosify_map_timeout == ~0 || file) {
e->timeout = ~0;
return;
}
e->timeout = qosify_gettime() + qosify_map_timeout;
delta = e->timeout - next_timeout;
if (next_timeout && delta >= 0)
return;
}
uloop_timeout_set(&qosify_map_timer, 1);
}
static int
qosify_map_set_port(struct qosify_map_data *data, const char *str)
{
unsigned long start_port, end_port;
char *err;
int i;
start_port = end_port = strtoul(str, &err, 0);
if (err && *err) {
if (*err == '-')
end_port = strtoul(err + 1, &err, 0);
if (*err)
return -1;
}
if (!start_port || end_port < start_port ||
end_port >= 65535)
return -1;
for (i = start_port; i <= end_port; i++) {
data->addr.port = htons(i);
__qosify_map_set_entry(data);
}
return 0;
}
static int
qosify_map_fill_ip(struct qosify_map_data *data, const char *str)
{
int af;
if (data->id == CL_MAP_IPV6_ADDR)
af = AF_INET6;
else
af = AF_INET;
if (inet_pton(af, str, &data->addr) != 1)
return -1;
return 0;
}
int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str, uint8_t dscp)
{
struct qosify_map_data data = {
.id = id,
.file = file,
.dscp = dscp,
};
switch (id) {
case CL_MAP_DNS:
data.addr.dns.pattern = str;
break;
case CL_MAP_TCP_PORTS:
case CL_MAP_UDP_PORTS:
return qosify_map_set_port(&data, str);
case CL_MAP_IPV4_ADDR:
case CL_MAP_IPV6_ADDR:
if (qosify_map_fill_ip(&data, str))
return -1;
break;
default:
return -1;
}
__qosify_map_set_entry(&data);
return 0;
}
int qosify_map_dscp_value(const char *val)
{
unsigned long dscp;
char *err;
bool fallback = false;
if (*val == '+') {
fallback = true;
val++;
}
dscp = strtoul(val, &err, 0);
if (err && *err)
dscp = qosify_map_codepoint(val);
if (dscp >= 64)
return -1;
return dscp + (fallback << 6);
}
static void
qosify_map_dscp_codepoint_str(char *dest, int len, uint8_t dscp)
{
int i;
if (dscp & QOSIFY_DSCP_FALLBACK_FLAG) {
*(dest++) = '+';
len--;
dscp &= ~QOSIFY_DSCP_FALLBACK_FLAG;
}
for (i = 0; i < ARRAY_SIZE(codepoints); i++) {
if (codepoints[i].val != dscp)
continue;
snprintf(dest, len, "%s", codepoints[i].name);
return;
}
snprintf(dest, len, "0x%x", dscp);
}
static void
qosify_map_parse_line(char *str)
{
const char *key, *value;
int dscp;
str = str_skip(str, true);
key = str;
str = str_skip(str, false);
if (!*str)
return;
*(str++) = 0;
str = str_skip(str, true);
value = str;
dscp = qosify_map_dscp_value(value);
if (dscp < 0)
return;
if (!strncmp(key, "dns:", 4))
qosify_map_set_entry(CL_MAP_DNS, true, key + 4, dscp);
if (!strncmp(key, "tcp:", 4))
qosify_map_set_entry(CL_MAP_TCP_PORTS, true, key + 4, dscp);
else if (!strncmp(key, "udp:", 4))
qosify_map_set_entry(CL_MAP_UDP_PORTS, true, key + 4, dscp);
else if (strchr(key, ':'))
qosify_map_set_entry(CL_MAP_IPV6_ADDR, true, key, dscp);
else if (strchr(key, '.'))
qosify_map_set_entry(CL_MAP_IPV4_ADDR, true, key, dscp);
}
static int __qosify_map_load_file(const char *file)
{
char line[1024];
char *cur;
FILE *f;
if (!file)
return 0;
f = fopen(file, "r");
if (!f) {
fprintf(stderr, "Can't open data file %s\n", file);
return -1;
}
while (fgets(line, sizeof(line), f)) {
cur = strchr(line, '#');
if (cur)
*cur = 0;
cur = line + strlen(line);
if (cur == line)
continue;
while (cur > line && isspace(cur[-1]))
cur--;
*cur = 0;
qosify_map_parse_line(line);
}
fclose(f);
return 0;
}
int qosify_map_load_file(const char *file)
{
struct qosify_map_file *f;
if (!file)
return 0;
f = calloc(1, sizeof(*f) + strlen(file) + 1);
strcpy(f->filename, file);
list_add_tail(&f->list, &map_files);
return __qosify_map_load_file(file);
}
static void qosify_map_reset_file_entries(void)
{
struct qosify_map_entry *e;
avl_for_each_element(&map_data, e, avl)
e->data.file = false;
}
void qosify_map_clear_files(void)
{
struct qosify_map_file *f, *tmp;
qosify_map_reset_file_entries();
list_for_each_entry_safe(f, tmp, &map_files, list) {
list_del(&f->list);
free(f);
}
}
void qosify_map_reset_config(void)
{
qosify_map_clear_files();
qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, 0);
qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, 0);
qosify_map_timeout = 3600;
qosify_active_timeout = 300;
memset(&config, 0, sizeof(config));
config.dscp_prio = 0xff;
config.dscp_bulk = 0xff;
config.dscp_icmp = 0xff;
}
void qosify_map_reload(void)
{
struct qosify_map_file *f;
qosify_map_reset_file_entries();
list_for_each_entry(f, &map_files, list)
__qosify_map_load_file(f->filename);
qosify_map_gc();
}
static void qosify_map_free_entry(struct qosify_map_entry *e)
{
int fd = qosify_map_fds[e->data.id];
avl_delete(&map_data, &e->avl);
if (e->data.id < CL_MAP_DNS)
bpf_map_delete_elem(fd, &e->data.addr);
free(e);
}
static bool
qosify_map_entry_refresh_timeout(struct qosify_map_entry *e)
{
struct qosify_ip_map_val val;
int fd = qosify_map_fds[e->data.id];
if (e->data.id != CL_MAP_IPV4_ADDR &&
e->data.id != CL_MAP_IPV6_ADDR)
return false;
if (bpf_map_lookup_elem(fd, &e->data.addr, &val))
return false;
if (!val.seen)
return false;
e->timeout = qosify_gettime() + qosify_active_timeout;
val.seen = 0;
bpf_map_update_elem(fd, &e->data.addr, &val, BPF_ANY);
return true;
}
void qosify_map_gc(void)
{
struct qosify_map_entry *e, *tmp;
int32_t timeout = 0;
uint32_t cur_time = qosify_gettime();
next_timeout = 0;
avl_for_each_element_safe(&map_data, e, avl, tmp) {
int32_t cur_timeout;
if (e->data.user && e->timeout != ~0) {
cur_timeout = e->timeout - cur_time;
if (cur_timeout <= 0 &&
qosify_map_entry_refresh_timeout(e))
cur_timeout = e->timeout - cur_time;
if (cur_timeout <= 0) {
e->data.user = false;
e->data.dscp = e->data.file_dscp;
} else if (!timeout || cur_timeout < timeout) {
timeout = cur_timeout;
next_timeout = e->timeout;
}
}
if (e->data.file || e->data.user)
continue;
qosify_map_free_entry(e);
}
if (!timeout)
return;
uloop_timeout_set(&qosify_map_timer, timeout * 1000);
}
int qosify_map_add_dns_host(const char *host, const char *addr, const char *type, int ttl)
{
struct qosify_map_data data = {
.id = CL_MAP_DNS,
.addr.dns.pattern = "",
};
struct qosify_map_entry *e;
int prev_timeout = qosify_map_timeout;
e = avl_find_ge_element(&map_data, &data, e, avl);
if (!e)
return 0;
memset(&data, 0, sizeof(data));
data.user = true;
if (!strcmp(type, "A"))
data.id = CL_MAP_IPV4_ADDR;
else if (!strcmp(type, "AAAA"))
data.id = CL_MAP_IPV6_ADDR;
else
return 0;
if (qosify_map_fill_ip(&data, addr))
return -1;
avl_for_element_to_last(&map_data, e, e, avl) {
regex_t *regex = &e->data.addr.dns.regex;
if (e->data.id != CL_MAP_DNS)
return 0;
if (regexec(regex, host, 0, NULL, 0) != 0)
continue;
if (ttl)
qosify_map_timeout = ttl;
data.dscp = e->data.dscp;
__qosify_map_set_entry(&data);
qosify_map_timeout = prev_timeout;
}
return 0;
}
void qosify_map_dump(struct blob_buf *b)
{
struct qosify_map_entry *e;
uint32_t cur_time = qosify_gettime();
int buf_len = INET6_ADDRSTRLEN + 1;
char *buf;
void *a;
int af;
a = blobmsg_open_array(b, "entries");
avl_for_each_element(&map_data, e, avl) {
void *c;
if (!e->data.file && !e->data.user)
continue;
c = blobmsg_open_table(b, NULL);
if (e->data.user && e->timeout != ~0) {
int32_t cur_timeout = e->timeout - cur_time;
if (cur_timeout < 0)
cur_timeout = 0;
blobmsg_add_u32(b, "timeout", cur_timeout);
}
blobmsg_add_u8(b, "file", e->data.file);
blobmsg_add_u8(b, "user", e->data.user);
buf = blobmsg_alloc_string_buffer(b, "dscp", buf_len);
qosify_map_dscp_codepoint_str(buf, buf_len, e->data.dscp);
blobmsg_add_string_buffer(b);
blobmsg_add_string(b, "type", qosify_map_info[e->data.id].type_name);
switch (e->data.id) {
case CL_MAP_TCP_PORTS:
case CL_MAP_UDP_PORTS:
blobmsg_printf(b, "addr", "%d", ntohs(e->data.addr.port));
break;
case CL_MAP_IPV4_ADDR:
case CL_MAP_IPV6_ADDR:
buf = blobmsg_alloc_string_buffer(b, "addr", buf_len);
af = e->data.id == CL_MAP_IPV6_ADDR ? AF_INET6 : AF_INET;
inet_ntop(af, &e->data.addr, buf, buf_len);
blobmsg_add_string_buffer(b);
break;
case CL_MAP_DNS:
blobmsg_add_string(b, "addr", e->data.addr.dns.pattern);
break;
default:
*buf = 0;
break;
}
blobmsg_close_table(b, c);
}
blobmsg_close_array(b, a);
}
void qosify_map_update_config(void)
{
int fd = qosify_map_fds[CL_MAP_CONFIG];
uint32_t key = 0;
bpf_map_update_elem(fd, &key, &config, BPF_ANY);
}

View File

@@ -1,464 +0,0 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#define KBUILD_MODNAME "foo"
#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/if_packet.h>
#include <uapi/linux/ip.h>
#include <uapi/linux/ipv6.h>
#include <uapi/linux/in.h>
#include <uapi/linux/tcp.h>
#include <uapi/linux/udp.h>
#include <uapi/linux/filter.h>
#include <uapi/linux/pkt_cls.h>
#include <linux/ip.h>
#include <net/ipv6.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include "qosify-bpf.h"
#define INET_ECN_MASK 3
#define FLOW_CHECK_INTERVAL ((u32)((1000000000ULL) >> 24))
#define FLOW_TIMEOUT ((u32)((30ULL * 1000000000ULL) >> 24))
#define FLOW_BULK_TIMEOUT 5
#define EWMA_SHIFT 12
const volatile static uint32_t module_flags = 0;
struct flow_bucket {
__u32 last_update;
__u32 pkt_len_avg;
__u16 pkt_count;
__u8 dscp;
__u8 bulk_timeout;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(pinning, 1);
__type(key, __u32);
__type(value, struct qosify_config);
__uint(max_entries, 1);
} config SEC(".maps");
typedef struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(pinning, 1);
__type(key, __u32);
__type(value, __u8);
__uint(max_entries, 1 << 16);
} port_array_t;
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(pinning, 1);
__type(key, __u32);
__uint(value_size, sizeof(struct flow_bucket));
__uint(max_entries, QOSIFY_FLOW_BUCKETS);
} flow_map SEC(".maps");
port_array_t tcp_ports SEC(".maps");
port_array_t udp_ports SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(pinning, 1);
__uint(key_size, sizeof(struct in_addr));
__type(value, struct qosify_ip_map_val);
__uint(max_entries, 100000);
__uint(map_flags, BPF_F_NO_PREALLOC);
} ipv4_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(pinning, 1);
__uint(key_size, sizeof(struct in6_addr));
__type(value, struct qosify_ip_map_val);
__uint(max_entries, 100000);
__uint(map_flags, BPF_F_NO_PREALLOC);
} ipv6_map SEC(".maps");
static struct qosify_config *get_config(void)
{
__u32 key = 0;
return bpf_map_lookup_elem(&config, &key);
}
static __always_inline int proto_is_vlan(__u16 h_proto)
{
return !!(h_proto == bpf_htons(ETH_P_8021Q) ||
h_proto == bpf_htons(ETH_P_8021AD));
}
static __always_inline int proto_is_ip(__u16 h_proto)
{
return !!(h_proto == bpf_htons(ETH_P_IP) ||
h_proto == bpf_htons(ETH_P_IPV6));
}
static __always_inline void *skb_ptr(struct __sk_buff *skb, __u32 offset)
{
void *start = (void *)(unsigned long long)skb->data;
return start + offset;
}
static __always_inline void *skb_end_ptr(struct __sk_buff *skb)
{
return (void *)(unsigned long long)skb->data_end;
}
static __always_inline int skb_check(struct __sk_buff *skb, void *ptr)
{
if (ptr > skb_end_ptr(skb))
return -1;
return 0;
}
static __always_inline __u32 cur_time(void)
{
__u32 val = bpf_ktime_get_ns() >> 24;
if (!val)
val = 1;
return val;
}
static __always_inline __u32 ewma(__u32 *avg, __u32 val)
{
if (*avg)
*avg = (*avg * 3) / 4 + (val << EWMA_SHIFT) / 4;
else
*avg = val << EWMA_SHIFT;
return *avg >> EWMA_SHIFT;
}
static __always_inline void
ipv4_change_dsfield(struct iphdr *iph, __u8 mask, __u8 value, bool force)
{
__u32 check = bpf_ntohs(iph->check);
__u8 dsfield;
if ((iph->tos & mask) && !force)
return;
dsfield = (iph->tos & mask) | value;
if (iph->tos == dsfield)
return;
check += iph->tos;
if ((check + 1) >> 16)
check = (check + 1) & 0xffff;
check -= dsfield;
check += check >> 16;
iph->check = bpf_htons(check);
iph->tos = dsfield;
}
static __always_inline void
ipv6_change_dsfield(struct ipv6hdr *ipv6h, __u8 mask, __u8 value, bool force)
{
__u16 *p = (__u16 *)ipv6h;
__u16 val;
if (((*p >> 4) & mask) && !force)
return;
val = (*p & bpf_htons((((__u16)mask << 4) | 0xf00f))) | bpf_htons((__u16)value << 4);
if (val == *p)
return;
*p = val;
}
static __always_inline int
parse_ethernet(struct __sk_buff *skb, __u32 *offset)
{
struct ethhdr *eth;
__u16 h_proto;
int i;
eth = skb_ptr(skb, *offset);
if (skb_check(skb, eth + 1))
return -1;
h_proto = eth->h_proto;
*offset += sizeof(*eth);
#pragma unroll
for (i = 0; i < 2; i++) {
struct vlan_hdr *vlh = skb_ptr(skb, *offset);
if (!proto_is_vlan(h_proto))
break;
if (skb_check(skb, vlh + 1))
return -1;
h_proto = vlh->h_vlan_encapsulated_proto;
*offset += sizeof(*vlh);
}
return h_proto;
}
static void
parse_l4proto(struct qosify_config *config, struct __sk_buff *skb,
__u32 offset, __u8 proto, __u8 *dscp_out)
{
struct udphdr *udp;
__u32 src, dest, key;
__u8 *value;
udp = skb_ptr(skb, offset);
if (skb_check(skb, &udp->len))
return;
if (config && (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6)) {
*dscp_out = config->dscp_icmp;
return;
}
src = udp->source;
dest = udp->dest;
if (module_flags & QOSIFY_INGRESS)
key = src;
else
key = dest;
if (proto == IPPROTO_TCP) {
value = bpf_map_lookup_elem(&tcp_ports, &key);
} else {
if (proto != IPPROTO_UDP)
key = 0;
value = bpf_map_lookup_elem(&udp_ports, &key);
}
if (!value)
return;
*dscp_out = *value;
}
static void
check_flow(struct qosify_config *config, struct __sk_buff *skb,
uint8_t *dscp)
{
struct flow_bucket flow_data;
struct flow_bucket *flow;
__s32 delta;
__u32 hash;
__u32 time;
if (!(*dscp & QOSIFY_DSCP_DEFAULT_FLAG))
return;
if (!config)
return;
if (!config->bulk_trigger_pps &&
!config->prio_max_avg_pkt_len)
return;
time = cur_time();
hash = bpf_get_hash_recalc(skb);
flow = bpf_map_lookup_elem(&flow_map, &hash);
if (!flow) {
memset(&flow_data, 0, sizeof(flow_data));
bpf_map_update_elem(&flow_map, &hash, &flow_data, BPF_ANY);
flow = bpf_map_lookup_elem(&flow_map, &hash);
if (!flow)
return;
}
if (!flow->last_update)
goto reset;
delta = time - flow->last_update;
if ((u32)delta > FLOW_TIMEOUT)
goto reset;
if (delta >= FLOW_CHECK_INTERVAL) {
if (flow->bulk_timeout) {
flow->bulk_timeout--;
if (!flow->bulk_timeout)
flow->dscp = 0xff;
}
goto clear;
}
if (flow->pkt_count < 0xffff)
flow->pkt_count++;
if (config->bulk_trigger_pps &&
flow->pkt_count > config->bulk_trigger_pps) {
flow->dscp = config->dscp_bulk;
flow->bulk_timeout = config->bulk_trigger_timeout;
}
out:
if (config->prio_max_avg_pkt_len &&
flow->dscp != config->dscp_bulk) {
if (ewma(&flow->pkt_len_avg, skb->len) <
config->prio_max_avg_pkt_len)
flow->dscp = config->dscp_prio;
else
flow->dscp = 0xff;
}
if (flow->dscp != 0xff)
*dscp = flow->dscp;
return;
reset:
flow->dscp = 0xff;
flow->pkt_len_avg = 0;
clear:
flow->pkt_count = 1;
flow->last_update = time;
goto out;
}
static __always_inline void
parse_ipv4(struct __sk_buff *skb, __u32 *offset)
{
struct qosify_config *config;
struct qosify_ip_map_val *ip_val;
const __u32 zero_port = 0;
struct iphdr *iph;
__u8 dscp = 0xff;
__u8 *value;
__u8 ipproto;
int hdr_len;
void *key;
bool force;
config = get_config();
iph = skb_ptr(skb, *offset);
if (skb_check(skb, iph + 1))
return;
hdr_len = iph->ihl * 4;
if (bpf_skb_pull_data(skb, *offset + hdr_len + sizeof(struct udphdr)))
return;
iph = skb_ptr(skb, *offset);
*offset += hdr_len;
if (skb_check(skb, (void *)(iph + 1)))
return;
ipproto = iph->protocol;
parse_l4proto(config, skb, *offset, ipproto, &dscp);
if (module_flags & QOSIFY_INGRESS)
key = &iph->saddr;
else
key = &iph->daddr;
ip_val = bpf_map_lookup_elem(&ipv4_map, key);
if (ip_val) {
if (!ip_val->seen)
ip_val->seen = 1;
dscp = ip_val->dscp;
} else if (dscp == 0xff) {
/* use udp port 0 entry as fallback for non-tcp/udp */
value = bpf_map_lookup_elem(&udp_ports, &zero_port);
if (value)
dscp = *value;
}
check_flow(config, skb, &dscp);
force = !(dscp & QOSIFY_DSCP_FALLBACK_FLAG);
dscp &= GENMASK(5, 0);
ipv4_change_dsfield(iph, INET_ECN_MASK, dscp << 2, force);
}
static __always_inline void
parse_ipv6(struct __sk_buff *skb, __u32 *offset)
{
struct qosify_config *config;
struct qosify_ip_map_val *ip_val;
const __u32 zero_port = 0;
struct ipv6hdr *iph;
__u8 dscp = 0;
__u8 *value;
__u8 ipproto;
void *key;
bool force;
config = get_config();
if (bpf_skb_pull_data(skb, *offset + sizeof(*iph) + sizeof(struct udphdr)))
return;
iph = skb_ptr(skb, *offset);
*offset += sizeof(*iph);
if (skb_check(skb, (void *)(iph + 1)))
return;
ipproto = iph->nexthdr;
if (module_flags & QOSIFY_INGRESS)
key = &iph->saddr;
else
key = &iph->daddr;
parse_l4proto(config, skb, *offset, ipproto, &dscp);
ip_val = bpf_map_lookup_elem(&ipv6_map, key);
if (ip_val) {
if (!ip_val->seen)
ip_val->seen = 1;
dscp = ip_val->dscp;
} else if (dscp == 0xff) {
/* use udp port 0 entry as fallback for non-tcp/udp */
value = bpf_map_lookup_elem(&udp_ports, &zero_port);
if (value)
dscp = *value;
}
check_flow(config, skb, &dscp);
force = !(dscp & QOSIFY_DSCP_FALLBACK_FLAG);
dscp &= GENMASK(5, 0);
ipv6_change_dsfield(iph, INET_ECN_MASK, dscp << 2, force);
}
SEC("classifier")
int classify(struct __sk_buff *skb)
{
__u32 offset = 0;
int type;
if (module_flags & QOSIFY_IP_ONLY)
type = skb->protocol;
else
type = parse_ethernet(skb, &offset);
if (type == bpf_htons(ETH_P_IP))
parse_ipv4(skb, &offset);
else if (type == bpf_htons(ETH_P_IPV6))
parse_ipv6(skb, &offset);
return TC_ACT_OK;
}
char _license[] SEC("license") = "GPL";

View File

@@ -1,39 +0,0 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#ifndef __BPF_QOSIFY_H
#define __BPF_QOSIFY_H
#ifndef QOSIFY_FLOW_BUCKET_SHIFT
#define QOSIFY_FLOW_BUCKET_SHIFT 13
#endif
#define QOSIFY_FLOW_BUCKETS (1 << QOSIFY_FLOW_BUCKET_SHIFT)
/* rodata per-instance flags */
#define QOSIFY_INGRESS (1 << 0)
#define QOSIFY_IP_ONLY (1 << 1)
#define QOSIFY_DSCP_FALLBACK_FLAG (1 << 6)
#define QOSIFY_DSCP_DEFAULT_FLAG (1 << 7)
/* global config data */
struct qosify_config {
uint8_t dscp_prio;
uint8_t dscp_bulk;
uint8_t dscp_icmp;
uint8_t bulk_trigger_timeout;
uint16_t bulk_trigger_pps;
uint16_t prio_max_avg_pkt_len;
};
struct qosify_ip_map_val {
uint8_t dscp; /* must be first */
uint8_t seen;
};
#endif

View File

@@ -1,95 +0,0 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#ifndef __QOS_CLASSIFY_H
#define __QOS_CLASSIFY_H
#include <stdbool.h>
#include <regex.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "qosify-bpf.h"
#include <libubox/utils.h>
#include <libubox/avl.h>
#include <libubox/blobmsg.h>
#include <libubox/ulog.h>
#include <netinet/in.h>
#define CLASSIFY_PROG_PATH "/lib/bpf/qosify-bpf.o"
#define CLASSIFY_PIN_PATH "/sys/fs/bpf/qosify"
#define CLASSIFY_DATA_PATH "/sys/fs/bpf/qosify_data"
enum qosify_map_id {
CL_MAP_TCP_PORTS,
CL_MAP_UDP_PORTS,
CL_MAP_IPV4_ADDR,
CL_MAP_IPV6_ADDR,
CL_MAP_CONFIG,
CL_MAP_DNS,
__CL_MAP_MAX,
};
struct qosify_map_data {
enum qosify_map_id id;
bool file : 1;
bool user : 1;
uint8_t dscp;
uint8_t file_dscp;
union {
uint32_t port;
struct in_addr ip;
struct in6_addr ip6;
struct {
const char *pattern;
regex_t regex;
} dns;
} addr;
};
struct qosify_map_entry {
struct avl_node avl;
uint32_t timeout;
struct qosify_map_data data;
};
extern int qosify_map_timeout;
extern int qosify_active_timeout;
extern struct qosify_config config;
int qosify_loader_init(void);
int qosify_map_init(void);
int qosify_map_dscp_value(const char *val);
int qosify_map_load_file(const char *file);
int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str, uint8_t dscp);
void qosify_map_reload(void);
void qosify_map_clear_files(void);
void qosify_map_gc(void);
void qosify_map_dump(struct blob_buf *b);
void qosify_map_set_dscp_default(enum qosify_map_id id, uint8_t val);
void qosify_map_reset_config(void);
void qosify_map_update_config(void);
int qosify_map_add_dns_host(const char *host, const char *addr, const char *type, int ttl);
int qosify_iface_init(void);
void qosify_iface_config_update(struct blob_attr *ifaces, struct blob_attr *devs);
void qosify_iface_check(void);
void qosify_iface_status(struct blob_buf *b);
void qosify_iface_stop(void);
int qosify_ubus_init(void);
void qosify_ubus_stop(void);
int qosify_ubus_check_interface(const char *name, char *ifname, int ifname_len);
#endif

View File

@@ -1,416 +0,0 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <libubus.h>
#include "qosify.h"
static struct blob_buf b;
static int
qosify_ubus_add_array(struct blob_attr *attr, uint8_t val, enum qosify_map_id id)
{
struct blob_attr *cur;
int rem;
if (blobmsg_check_array(attr, BLOBMSG_TYPE_STRING) < 0)
return UBUS_STATUS_INVALID_ARGUMENT;
blobmsg_for_each_attr(cur, attr, rem)
qosify_map_set_entry(id, false, blobmsg_get_string(cur), val);
return 0;
}
static int
qosify_ubus_set_files(struct blob_attr *attr)
{
struct blob_attr *cur;
int rem;
if (blobmsg_check_array(attr, BLOBMSG_TYPE_STRING) < 0)
return UBUS_STATUS_INVALID_ARGUMENT;
qosify_map_clear_files();
blobmsg_for_each_attr(cur, attr, rem)
qosify_map_load_file(blobmsg_get_string(cur));
qosify_map_gc();
return 0;
}
enum {
CL_ADD_DSCP,
CL_ADD_TIMEOUT,
CL_ADD_IPV4,
CL_ADD_IPV6,
CL_ADD_TCP_PORT,
CL_ADD_UDP_PORT,
CL_ADD_DNS,
__CL_ADD_MAX
};
static const struct blobmsg_policy qosify_add_policy[__CL_ADD_MAX] = {
[CL_ADD_DSCP] = { "dscp", BLOBMSG_TYPE_STRING },
[CL_ADD_TIMEOUT] = { "timeout", BLOBMSG_TYPE_INT32 },
[CL_ADD_IPV4] = { "ipv4", BLOBMSG_TYPE_ARRAY },
[CL_ADD_IPV6] = { "ipv6", BLOBMSG_TYPE_ARRAY },
[CL_ADD_TCP_PORT] = { "tcp_port", BLOBMSG_TYPE_ARRAY },
[CL_ADD_UDP_PORT] = { "udp_port", BLOBMSG_TYPE_ARRAY },
[CL_ADD_DNS] = { "dns", BLOBMSG_TYPE_ARRAY },
};
static int
qosify_ubus_reload(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
qosify_map_reload();
return 0;
}
static int
qosify_ubus_add(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
int prev_timemout = qosify_map_timeout;
struct blob_attr *tb[__CL_ADD_MAX];
struct blob_attr *cur;
int dscp = -1;
int ret;
blobmsg_parse(qosify_add_policy, __CL_ADD_MAX, tb,
blobmsg_data(msg), blobmsg_len(msg));
if (!strcmp(method, "add")) {
if ((cur = tb[CL_ADD_DSCP]) != NULL)
dscp = qosify_map_dscp_value(blobmsg_get_string(cur));
else
return UBUS_STATUS_INVALID_ARGUMENT;
if (dscp < 0)
return UBUS_STATUS_INVALID_ARGUMENT;
if ((cur = tb[CL_ADD_TIMEOUT]) != NULL)
qosify_map_timeout = blobmsg_get_u32(cur);
} else {
dscp = 0xff;
}
if ((cur = tb[CL_ADD_IPV4]) != NULL &&
(ret = qosify_ubus_add_array(cur, dscp, CL_MAP_IPV4_ADDR) != 0))
return ret;
if ((cur = tb[CL_ADD_IPV6]) != NULL &&
(ret = qosify_ubus_add_array(cur, dscp, CL_MAP_IPV6_ADDR) != 0))
return ret;
if ((cur = tb[CL_ADD_TCP_PORT]) != NULL &&
(ret = qosify_ubus_add_array(cur, dscp, CL_MAP_TCP_PORTS) != 0))
return ret;
if ((cur = tb[CL_ADD_UDP_PORT]) != NULL &&
(ret = qosify_ubus_add_array(cur, dscp, CL_MAP_UDP_PORTS) != 0))
return ret;
if ((cur = tb[CL_ADD_DNS]) != NULL &&
(ret = qosify_ubus_add_array(cur, dscp, CL_MAP_DNS) != 0))
return ret;
qosify_map_timeout = prev_timemout;
return 0;
}
enum {
CL_CONFIG_RESET,
CL_CONFIG_FILES,
CL_CONFIG_TIMEOUT,
CL_CONFIG_DSCP_UDP,
CL_CONFIG_DSCP_TCP,
CL_CONFIG_DSCP_PRIO,
CL_CONFIG_DSCP_BULK,
CL_CONFIG_DSCP_ICMP,
CL_CONFIG_BULK_TIMEOUT,
CL_CONFIG_BULK_PPS,
CL_CONFIG_PRIO_PKT_LEN,
CL_CONFIG_INTERFACES,
CL_CONFIG_DEVICES,
__CL_CONFIG_MAX
};
static const struct blobmsg_policy qosify_config_policy[__CL_CONFIG_MAX] = {
[CL_CONFIG_RESET] = { "reset", BLOBMSG_TYPE_BOOL },
[CL_CONFIG_FILES] = { "files", BLOBMSG_TYPE_ARRAY },
[CL_CONFIG_TIMEOUT] = { "timeout", BLOBMSG_TYPE_INT32 },
[CL_CONFIG_DSCP_UDP] = { "dscp_default_udp", BLOBMSG_TYPE_STRING },
[CL_CONFIG_DSCP_TCP] = { "dscp_default_tcp", BLOBMSG_TYPE_STRING },
[CL_CONFIG_DSCP_PRIO] = { "dscp_prio", BLOBMSG_TYPE_STRING },
[CL_CONFIG_DSCP_BULK] = { "dscp_bulk", BLOBMSG_TYPE_STRING },
[CL_CONFIG_DSCP_ICMP] = { "dscp_icmp", BLOBMSG_TYPE_STRING },
[CL_CONFIG_BULK_TIMEOUT] = { "bulk_trigger_timeout", BLOBMSG_TYPE_INT32 },
[CL_CONFIG_BULK_PPS] = { "bulk_trigger_pps", BLOBMSG_TYPE_INT32 },
[CL_CONFIG_PRIO_PKT_LEN] = { "prio_max_avg_pkt_len", BLOBMSG_TYPE_INT32 },
[CL_CONFIG_INTERFACES] = { "interfaces", BLOBMSG_TYPE_TABLE },
[CL_CONFIG_DEVICES] = { "devices", BLOBMSG_TYPE_TABLE },
};
static int __set_dscp(uint8_t *dest, struct blob_attr *attr, bool reset)
{
int dscp;
if (reset)
*dest = 0xff;
if (!attr)
return 0;
dscp = qosify_map_dscp_value(blobmsg_get_string(attr));
if (dscp < 0)
return -1;
*dest = dscp;
return 0;
}
static int
qosify_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[__CL_CONFIG_MAX];
struct blob_attr *cur;
uint8_t dscp;
bool reset = false;
int ret;
blobmsg_parse(qosify_config_policy, __CL_CONFIG_MAX, tb,
blobmsg_data(msg), blobmsg_len(msg));
if ((cur = tb[CL_CONFIG_RESET]) != NULL)
reset = blobmsg_get_bool(cur);
if (reset)
qosify_map_reset_config();
if ((cur = tb[CL_CONFIG_TIMEOUT]) != NULL)
qosify_map_timeout = blobmsg_get_u32(cur);
if ((cur = tb[CL_CONFIG_FILES]) != NULL &&
(ret = qosify_ubus_set_files(cur) != 0))
return ret;
__set_dscp(&dscp, tb[CL_CONFIG_DSCP_UDP], true);
if (dscp != 0xff)
qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, dscp);
__set_dscp(&dscp, tb[CL_CONFIG_DSCP_TCP], true);
if (dscp != 0xff)
qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, dscp);
__set_dscp(&config.dscp_prio, tb[CL_CONFIG_DSCP_PRIO], reset);
__set_dscp(&config.dscp_bulk, tb[CL_CONFIG_DSCP_BULK], reset);
__set_dscp(&config.dscp_icmp, tb[CL_CONFIG_DSCP_ICMP], reset);
if ((cur = tb[CL_CONFIG_BULK_TIMEOUT]) != NULL)
config.bulk_trigger_timeout = blobmsg_get_u32(cur);
if ((cur = tb[CL_CONFIG_BULK_PPS]) != NULL)
config.bulk_trigger_pps = blobmsg_get_u32(cur);
if ((cur = tb[CL_CONFIG_PRIO_PKT_LEN]) != NULL)
config.prio_max_avg_pkt_len = blobmsg_get_u32(cur);
qosify_map_update_config();
qosify_iface_config_update(tb[CL_CONFIG_INTERFACES], tb[CL_CONFIG_DEVICES]);
qosify_iface_check();
return 0;
}
static int
qosify_ubus_dump(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
blob_buf_init(&b, 0);
qosify_map_dump(&b);
ubus_send_reply(ctx, req, b.head);
blob_buf_free(&b);
return 0;
}
static int
qosify_ubus_status(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
blob_buf_init(&b, 0);
qosify_iface_status(&b);
ubus_send_reply(ctx, req, b.head);
blob_buf_free(&b);
return 0;
}
static int
qosify_ubus_check_devices(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
qosify_iface_check();
return 0;
}
enum {
CL_DNS_HOST_NAME,
CL_DNS_HOST_TYPE,
CL_DNS_HOST_ADDR,
CL_DNS_HOST_TTL,
__CL_DNS_HOST_MAX
};
static const struct blobmsg_policy qosify_dns_policy[__CL_DNS_HOST_MAX] = {
[CL_DNS_HOST_NAME] = { "name", BLOBMSG_TYPE_STRING },
[CL_DNS_HOST_TYPE] = { "type", BLOBMSG_TYPE_STRING },
[CL_DNS_HOST_ADDR] = { "address", BLOBMSG_TYPE_STRING },
[CL_DNS_HOST_TTL] = { "ttl", BLOBMSG_TYPE_INT32 },
};
static int
qosify_ubus_add_dns_host(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method,
struct blob_attr *msg)
{
struct blob_attr *tb[__CL_DNS_HOST_MAX];
struct blob_attr *cur;
uint32_t ttl = 0;
blobmsg_parse(qosify_dns_policy, __CL_DNS_HOST_MAX, tb,
blobmsg_data(msg), blobmsg_len(msg));
if (!tb[CL_DNS_HOST_NAME] || !tb[CL_DNS_HOST_TYPE] ||
!tb[CL_DNS_HOST_ADDR])
return UBUS_STATUS_INVALID_ARGUMENT;
if ((cur = tb[CL_DNS_HOST_TTL]) != NULL)
ttl = blobmsg_get_u32(cur);
if (qosify_map_add_dns_host(blobmsg_get_string(tb[CL_DNS_HOST_NAME]),
blobmsg_get_string(tb[CL_DNS_HOST_ADDR]),
blobmsg_get_string(tb[CL_DNS_HOST_TYPE]),
ttl))
return UBUS_STATUS_INVALID_ARGUMENT;
return 0;
}
static const struct ubus_method qosify_methods[] = {
UBUS_METHOD_NOARG("reload", qosify_ubus_reload),
UBUS_METHOD("add", qosify_ubus_add, qosify_add_policy),
UBUS_METHOD_MASK("remove", qosify_ubus_add, qosify_add_policy,
((1 << __CL_ADD_MAX) - 1) & ~(1 << CL_ADD_DSCP)),
UBUS_METHOD("config", qosify_ubus_config, qosify_config_policy),
UBUS_METHOD_NOARG("dump", qosify_ubus_dump),
UBUS_METHOD_NOARG("status", qosify_ubus_status),
UBUS_METHOD("add_dns_host", qosify_ubus_add_dns_host, qosify_dns_policy),
UBUS_METHOD_NOARG("check_devices", qosify_ubus_check_devices),
};
static struct ubus_object_type qosify_object_type =
UBUS_OBJECT_TYPE("qosify", qosify_methods);
static struct ubus_object qosify_object = {
.name = "qosify",
.type = &qosify_object_type,
.methods = qosify_methods,
.n_methods = ARRAY_SIZE(qosify_methods),
};
static void
ubus_connect_handler(struct ubus_context *ctx)
{
ubus_add_object(ctx, &qosify_object);
}
static struct ubus_auto_conn conn;
int qosify_ubus_init(void)
{
conn.cb = ubus_connect_handler;
ubus_auto_connect(&conn);
return 0;
}
void qosify_ubus_stop(void)
{
ubus_auto_shutdown(&conn);
}
struct iface_req {
char *name;
int len;
};
static void
netifd_if_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
struct iface_req *ifr = req->priv;
enum {
IFS_ATTR_UP,
IFS_ATTR_DEV,
__IFS_ATTR_MAX
};
static const struct blobmsg_policy policy[__IFS_ATTR_MAX] = {
[IFS_ATTR_UP] = { "up", BLOBMSG_TYPE_BOOL },
[IFS_ATTR_DEV] = { "l3_device", BLOBMSG_TYPE_STRING },
};
struct blob_attr *tb[__IFS_ATTR_MAX];
blobmsg_parse(policy, __IFS_ATTR_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
if (!tb[IFS_ATTR_UP] || !tb[IFS_ATTR_DEV])
return;
if (!blobmsg_get_bool(tb[IFS_ATTR_UP]))
return;
snprintf(ifr->name, ifr->len, "%s", blobmsg_get_string(tb[IFS_ATTR_DEV]));
}
int qosify_ubus_check_interface(const char *name, char *ifname, int ifname_len)
{
struct iface_req req = { ifname, ifname_len };
char *obj_name = "network.interface.";
uint32_t id;
#define PREFIX "network.interface."
obj_name = alloca(sizeof(PREFIX) + strlen(name) + 1);
sprintf(obj_name, PREFIX "%s", name);
#undef PREFIX
ifname[0] = 0;
if (ubus_lookup_id(&conn.ctx, obj_name, &id))
return -1;
ubus_invoke(&conn.ctx, id, "status", b.head, netifd_if_cb, &req, 1000);
if (!ifname[0])
return -1;
return 0;
}