From 4a3d4f56091091f42bd332692055a959a608f863 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Sun, 24 Oct 2021 15:00:19 +0200 Subject: [PATCH] qosify: add new QoS package Signed-off-by: John Crispin --- feeds/ucentral/qosify/Makefile | 54 ++ feeds/ucentral/qosify/files/qosify-bpf.o | Bin 0 -> 19824 bytes .../qosify/files/qosify-defaults.conf | 17 + feeds/ucentral/qosify/files/qosify.conf | 28 + feeds/ucentral/qosify/files/qosify.hotplug | 2 + feeds/ucentral/qosify/files/qosify.init | 103 ++++ feeds/ucentral/qosify/src/CMakeLists.txt | 15 + feeds/ucentral/qosify/src/interface.c | 557 +++++++++++++++++ feeds/ucentral/qosify/src/loader.c | 126 ++++ feeds/ucentral/qosify/src/main.c | 70 +++ feeds/ucentral/qosify/src/map.c | 575 ++++++++++++++++++ feeds/ucentral/qosify/src/qosify-bpf.c | 433 +++++++++++++ feeds/ucentral/qosify/src/qosify-bpf.h | 26 + feeds/ucentral/qosify/src/qosify.h | 82 +++ feeds/ucentral/qosify/src/ubus.c | 364 +++++++++++ .../files/etc/ucentral/examples/qos.json | 136 +++++ .../files/etc/uci-defaults/99-ucentral-qos | 5 + 17 files changed, 2593 insertions(+) create mode 100644 feeds/ucentral/qosify/Makefile create mode 100644 feeds/ucentral/qosify/files/qosify-bpf.o create mode 100644 feeds/ucentral/qosify/files/qosify-defaults.conf create mode 100644 feeds/ucentral/qosify/files/qosify.conf create mode 100644 feeds/ucentral/qosify/files/qosify.hotplug create mode 100644 feeds/ucentral/qosify/files/qosify.init create mode 100644 feeds/ucentral/qosify/src/CMakeLists.txt create mode 100644 feeds/ucentral/qosify/src/interface.c create mode 100644 feeds/ucentral/qosify/src/loader.c create mode 100644 feeds/ucentral/qosify/src/main.c create mode 100644 feeds/ucentral/qosify/src/map.c create mode 100644 feeds/ucentral/qosify/src/qosify-bpf.c create mode 100644 feeds/ucentral/qosify/src/qosify-bpf.h create mode 100644 feeds/ucentral/qosify/src/qosify.h create mode 100644 feeds/ucentral/qosify/src/ubus.c create mode 100644 feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/qos.json create mode 100644 feeds/ucentral/ucentral-schema/files/etc/uci-defaults/99-ucentral-qos diff --git a/feeds/ucentral/qosify/Makefile b/feeds/ucentral/qosify/Makefile new file mode 100644 index 000000000..61c2d4179 --- /dev/null +++ b/feeds/ucentral/qosify/Makefile @@ -0,0 +1,54 @@ +# +# Copyright (C) 2008-2012 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:=qosify +PKG_RELEASE:=1 +PKG_LICENSE:=GPL-2.0 + +PKG_BUILD_DEPENDS:=bpf-headers + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk +#include $(INCLUDE_DIR)/bpf.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 +endef + +#BPF_DOC = $(wildcard $(patsubst %,$(BPF_HEADERS_DIR)/scripts/%.py,bpf_doc bpf_helpers_doc)) + +TARGET_CFLAGS += -I$(BPF_HEADERS_DIR)/user_headers/include + +define Build/Compile +# $(call CompileBPF,$(PKG_BUILD_DIR)/qosify-bpf.c) + $(Build/Compile/Default) +endef + +define Package/qosify/conffiles +/etc/config/qosify +/etc/qosify-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_DATA) ./files/qosify-bpf.o $(1)/lib/bpf + $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/qosify $(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.conf $(1)/etc/config/qosify + $(INSTALL_DATA) ./files/qosify.hotplug $(1)/etc/hotplug.d/net/10-qosify +endef + +$(eval $(call BuildPackage,qosify)) diff --git a/feeds/ucentral/qosify/files/qosify-bpf.o b/feeds/ucentral/qosify/files/qosify-bpf.o new file mode 100644 index 0000000000000000000000000000000000000000..2181ff1f6f70b240a28c68a771a8db8a0c95e41d GIT binary patch literal 19824 zcmd^`e{7vsdB=~h?Q17ZV<)X!C$#Xoq2VTt6Wd9RX`A$>Y0_k*jcJl5-4DIKv9Ikr z|GK{Sx^bXvZdXuXFc~N!+sg3PP717JMq?z}5Z4;j6trnFf}v;>utYaSTS3-Vz#1Lh z_xqgZT%Vk{KPIuic9i!$=kq+z`Te}-ocsFCZ|^^RAW>CiT&OZXGt1Whtb zz-Y((#m3b^K1=`t_4o(9Ex()*vG@)Bsn}@8!I!iD#YQ_$#U8as^(>*-!_qK*30q%o z+_tN!O8Z;1`tm!)%-p(bt0XeNV>y$s?QTlkDuQ>R8n⪻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 literal 0 HcmV?d00001 diff --git a/feeds/ucentral/qosify/files/qosify-defaults.conf b/feeds/ucentral/qosify/files/qosify-defaults.conf new file mode 100644 index 000000000..23408d54c --- /dev/null +++ b/feeds/ucentral/qosify/files/qosify-defaults.conf @@ -0,0 +1,17 @@ +# DNS +tcp:53 CS5 +tcp:5353 CS5 +udp:53 CS5 +udp:5353 CS5 + +# NTP +udp:123 CS6 + +# SSH +tcp:22 +CS4 + +# HTTP/QUIC +tcp:80 +CS3 +tcp:443 +CS3 +udp:80 +CS3 +udp:443 +CS3 diff --git a/feeds/ucentral/qosify/files/qosify.conf b/feeds/ucentral/qosify/files/qosify.conf new file mode 100644 index 000000000..d24b56e61 --- /dev/null +++ b/feeds/ucentral/qosify/files/qosify.conf @@ -0,0 +1,28 @@ +config defaults + list defaults /etc/qosify-defaults.conf + option dscp_prio CS5 + option dscp_bulk CS0 + option dscp_default_udp CS4 + option bulk_trigger_timeout 5 + option bulk_trigger_pps 100 + +config interface wan + option name wan + option disabled 1 + option bandwidth_up 100mbit + option bandwidth_down 100mbit + # defaults: + option ingress 1 + option egress 1 + option mode diffserv4 + option host_isolate 1 + option autorate_ingress 1 + option ingress_options "" + option egress_options "" + option options "" + +config device wandev + option disabled 1 + option name wan + option bandwidth 100mbit + diff --git a/feeds/ucentral/qosify/files/qosify.hotplug b/feeds/ucentral/qosify/files/qosify.hotplug new file mode 100644 index 000000000..950812c03 --- /dev/null +++ b/feeds/ucentral/qosify/files/qosify.hotplug @@ -0,0 +1,2 @@ +#!/bin/sh +ubus call qosify check_devices diff --git a/feeds/ucentral/qosify/files/qosify.init b/feeds/ucentral/qosify/files/qosify.init new file mode 100644 index 000000000..8252fe381 --- /dev/null +++ b/feeds/ucentral/qosify/files/qosify.init @@ -0,0 +1,103 @@ +#!/bin/sh /etc/rc.common +# Copyright (c) 2014 OpenWrt.org + +START=19 + +USE_PROCD=1 +PROG=/usr/sbin/qosify + +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 + + config_get files "$cfg" files + json_add_array files + for i in $files; do + json_add_string "" "$i" + done + json_close_array + + add_option int timeout + add_option string dscp_prio + add_option string dscp_bulk + 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_interface() { + local cfg="$1" + + config_get_bool disabled "$cfg" disabled 0 + [ "$disabled" -gt 0 ] && return + + config_get name "$cfg" name + json_add_object "$name" + + config_get bw "$cfg" bandwidth + + config_get bw_up "$cfg" bandwidth_up + bw_up="${bw_up:-$bw}" + [ -n "$bw_up" ] && json_add_string bandwidth_up "$bw_up" + + config_get bw_down "$cfg" bandwidth_down + bw_down="${bw_down:-$bw}" + [ -n "$bw_down" ] && json_add_string bandwidth_down "$bw_down" + + add_option string bandwidth + add_option boolean ingress + add_option boolean egress + add_option string mode + add_option boolean host_isolate + add_option boolean autorate_ingress + add_option string ingress_options + add_option string egress_options + add_option string options + + json_close_object +} + +reload_service() { + json_init + + config_load qosify + + config_foreach add_defaults defaults + + json_add_object interfaces + config_foreach add_interface interface + json_close_object + + json_add_object devices + config_foreach add_interface device + json_close_object + + ubus call qosify config "$(json_dump)" +} + +service_triggers() { + procd_add_reload_trigger qosify +} + +start_service() { + procd_open_instance + procd_set_param command "$PROG" + procd_set_param respawn + procd_close_instance +} + +service_started() { + ubus -t 10 wait_for qosify + [ $? = 0 ] && reload_service +} diff --git a/feeds/ucentral/qosify/src/CMakeLists.txt b/feeds/ucentral/qosify/src/CMakeLists.txt new file mode 100644 index 000000000..3d73a60a0 --- /dev/null +++ b/feeds/ucentral/qosify/src/CMakeLists.txt @@ -0,0 +1,15 @@ +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} +) diff --git a/feeds/ucentral/qosify/src/interface.c b/feeds/ucentral/qosify/src/interface.c new file mode 100644 index 000000000..3886c78f3 --- /dev/null +++ b/feeds/ucentral/qosify/src/interface.c @@ -0,0 +1,557 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#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_config *cfg, struct blob_attr *attr) +{ + 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; + + 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->config, 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); +} + diff --git a/feeds/ucentral/qosify/src/loader.c b/feeds/ucentral/qosify/src/loader.c new file mode 100644 index 000000000..b601efc36 --- /dev/null +++ b/feeds/ucentral/qosify/src/loader.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include + +#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, bool *force_init) +{ + DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts, + .pin_root_path = CLASSIFY_DATA_PATH, + ); + struct bpf_program *prog; + struct bpf_object *obj; + struct stat st; + char path[256]; + int err; + + snprintf(path, sizeof(path), CLASSIFY_PIN_PATH "_" "%s", suffix); + if (!*force_init) { + if (stat(path, &st) == 0) + return 0; + + *force_init = true; + } + + 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(bool force_init) +{ + 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 (force_init && + 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, + &force_init)) + return -1; + } + + return 0; +} diff --git a/feeds/ucentral/qosify/src/main.c b/feeds/ucentral/qosify/src/main.c new file mode 100644 index 000000000..8fa9ec12f --- /dev/null +++ b/feeds/ucentral/qosify/src/main.c @@ -0,0 +1,70 @@ +#include +#include +#include + +#include + +#include "qosify.h" + +static int usage(const char *progname) +{ + fprintf(stderr, "Usage: %s [options]\n" + "Options:\n" + " -f: force reload of BPF programs\n" + " -l Load defaults from \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 force_init = false; + bool oneshot = false; + int ch; + + while ((ch = getopt(argc, argv, "fl:o")) != -1) { + switch (ch) { + case 'f': + force_init = true; + break; + case 'l': + load_file = optarg; + break; + case 'o': + oneshot = true; + break; + default: + return usage(argv[0]); + } + } + + if (qosify_loader_init(force_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_iface_stop(); + + uloop_done(); + + return 0; +} diff --git a/feeds/ucentral/qosify/src/map.c b/feeds/ucentral/qosify/src/map.c new file mode 100644 index 000000000..9d23caee1 --- /dev/null +++ b/feeds/ucentral/qosify/src/map.c @@ -0,0 +1,575 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#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 = 3600; +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" }, +}; + +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; + + 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 < ARRAY_SIZE(qosify_map_fds); 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) +{ + static const struct { + const char name[5]; + uint8_t val; + } cp[] = { + { "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 }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(cp); i++) + if (!strcmp(cp[i].name, val)) + return cp[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; + + return memcmp(&d1->addr, &d2->addr, sizeof(d1->addr)); +} + +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 = calloc(1, sizeof(*e)); + e->avl.key = &e->data; + e->data.id = data->id; + memcpy(&e->data.addr, &data->addr, sizeof(e->data.addr)); + 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) + bpf_map_update_elem(fd, &data->addr, &e->data.dscp, 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_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_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, "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; + + 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(); +} + +void qosify_map_gc(void) +{ + struct qosify_map_entry *e, *tmp; + int32_t timeout = 0; + uint32_t cur_time = qosify_gettime(); + int fd; + + 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) { + 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; + + avl_delete(&map_data, &e->avl); + fd = qosify_map_fds[e->data.id]; + bpf_map_delete_elem(fd, &e->data.addr); + free(e); + } + + if (!timeout) + return; + + uloop_timeout_set(&qosify_map_timer, timeout * 1000); +} + +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); + + blobmsg_add_string(b, "type", qosify_map_info[e->data.id].type_name); + + buf = blobmsg_alloc_string_buffer(b, "value", buf_len); + switch (e->data.id) { + case CL_MAP_TCP_PORTS: + case CL_MAP_UDP_PORTS: + snprintf(buf, buf_len, "%d", ntohs(e->data.addr.port)); + break; + case CL_MAP_IPV4_ADDR: + case CL_MAP_IPV6_ADDR: + af = e->data.id == CL_MAP_IPV6_ADDR ? AF_INET6 : AF_INET; + inet_ntop(af, &e->data.addr, buf, buf_len); + break; + default: + *buf = 0; + break; + } + blobmsg_add_string_buffer(b); + 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); +} diff --git a/feeds/ucentral/qosify/src/qosify-bpf.c b/feeds/ucentral/qosify/src/qosify-bpf.c new file mode 100644 index 000000000..ff40d766d --- /dev/null +++ b/feeds/ucentral/qosify/src/qosify-bpf.c @@ -0,0 +1,433 @@ +#define KBUILD_MODNAME "foo" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qosify-bpf.h" + +#define INET_ECN_MASK 3 +#define DSCP_FALLBACK_FLAG BIT(6) + +#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, __u8); + __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, __u8); + __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 = skb_ptr(skb, offset); + __u32 key; + __u8 *value; + + if (skb_check(skb, &udp->len)) + return; + + if (module_flags & QOSIFY_INGRESS) + key = udp->source; + else + key = udp->dest; + + if (proto == IPPROTO_TCP) + value = bpf_map_lookup_elem(&tcp_ports, &key); + else if (proto == IPPROTO_UDP) + value = bpf_map_lookup_elem(&udp_ports, &key); + else { + if ((proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6) && + config && config->dscp_icmp != 0xff) + *dscp_out = config->dscp_icmp; + return; + } + + if (!value) + return; + + if ((*value & DSCP_FALLBACK_FLAG) && *dscp_out) + *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 (!config) + 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 (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 & DSCP_FALLBACK_FLAG))) + *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; + const __u32 zero_port = 0; + struct iphdr *iph; + __u8 dscp = 0; + __u8 *value; + 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)) + return; + + iph = skb_ptr(skb, *offset); + *offset += hdr_len; + + if (skb_check(skb, (void *)(iph + 1))) + return; + + parse_l4proto(config, skb, *offset, iph->protocol, &dscp); + + if (module_flags & QOSIFY_INGRESS) + key = &iph->saddr; + else + key = &iph->daddr; + + value = bpf_map_lookup_elem(&ipv4_map, key); + /* use udp port 0 entry as fallback for non-tcp/udp */ + if (!value) + value = bpf_map_lookup_elem(&udp_ports, &zero_port); + if (value) + dscp = *value; + + check_flow(config, skb, &dscp); + + force = !(dscp & 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; + const __u32 zero_port = 0; + struct ipv6hdr *iph; + __u8 dscp = 0; + __u8 *value; + void *key; + bool force; + + config = get_config(); + + if (bpf_skb_pull_data(skb, *offset + sizeof(*iph))) + return; + + iph = skb_ptr(skb, *offset); + *offset += sizeof(*iph); + + if (skb_check(skb, (void *)(iph + 1))) + return; + + if (module_flags & QOSIFY_INGRESS) + key = &iph->saddr; + else + key = &iph->daddr; + + parse_l4proto(config, skb, *offset, iph->nexthdr, &dscp); + + value = bpf_map_lookup_elem(&ipv6_map, key); + + /* use udp port 0 entry as fallback for non-tcp/udp */ + if (!value) + value = bpf_map_lookup_elem(&udp_ports, &zero_port); + if (value) + dscp = *value; + + check_flow(config, skb, &dscp); + + force = !(dscp & 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"; diff --git a/feeds/ucentral/qosify/src/qosify-bpf.h b/feeds/ucentral/qosify/src/qosify-bpf.h new file mode 100644 index 000000000..b7b4f2aad --- /dev/null +++ b/feeds/ucentral/qosify/src/qosify-bpf.h @@ -0,0 +1,26 @@ +#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) + +/* 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; +}; + +#endif diff --git a/feeds/ucentral/qosify/src/qosify.h b/feeds/ucentral/qosify/src/qosify.h new file mode 100644 index 000000000..764d3189a --- /dev/null +++ b/feeds/ucentral/qosify/src/qosify.h @@ -0,0 +1,82 @@ +#ifndef __QOS_CLASSIFY_H +#define __QOS_CLASSIFY_H + +#include + +#include +#include + +#include "qosify-bpf.h" + +#include +#include +#include +#include + +#include + +#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_MAX, +}; + +struct qosify_map_data { + enum qosify_map_id id; + + bool file : 1; + bool user : 1; + + uint8_t dscp; + uint8_t file_dscp; + + union { + uint16_t port; + struct in_addr ip; + struct in6_addr ip6; + } addr; +}; + +struct qosify_map_entry { + struct avl_node avl; + + uint32_t timeout; + + struct qosify_map_data data; +}; + + +extern int qosify_map_timeout; +extern struct qosify_config config; + +int qosify_loader_init(bool force_init); + +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_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); +int qosify_ubus_check_interface(const char *name, char *ifname, int ifname_len); + +#endif diff --git a/feeds/ucentral/qosify/src/ubus.c b/feeds/ucentral/qosify/src/ubus.c new file mode 100644 index 000000000..ae1994041 --- /dev/null +++ b/feeds/ucentral/qosify/src/ubus.c @@ -0,0 +1,364 @@ +#include + +#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_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 }, +}; + + +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; + + 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_tcp", BLOBMSG_TYPE_STRING }, + [CL_CONFIG_DSCP_TCP] = { "dscp_default_udp", 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; +} + +enum { + CL_DEV_EVENT_NAME, + CL_DEV_EVENT_ADD, + __CL_DEV_EVENT_MAX, +}; + +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; +} + + +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_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; +} + +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; +} diff --git a/feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/qos.json b/feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/qos.json new file mode 100644 index 000000000..a098a5aee --- /dev/null +++ b/feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/qos.json @@ -0,0 +1,136 @@ +{ + "uuid": 2, + "globals": { + "wireless-multimedia": { + "UP0": [ "DF"], + "UP1": [ "CS1" ], + "UP2": [ "AF11", "AF12", "AF13" ], + "UP3": [ "CS2", "AF21", "AF22", "AF23" ], + "UP4": [ "CS3", "AF31", "AF32", "AF33" ], + "UP5": [ "CS5", "AF41", "AF42", "AF43" ], + "UP6": [ "CS4", "EF" ], + "UP7": [ "CS6" ] + } + }, + "radios": [ + { + "band": "2G", + "country": "CA", + "channel-mode": "HE", + "channel-width": 80, + "channel": 32 + } + ], + + "interfaces": [ + { + "name": "WAN", + "role": "upstream", + "services": [ "lldp" ], + "ethernet": [ + { + "select-ports": [ + "WAN*" + ] + } + ], + "ipv4": { + "addressing": "dynamic" + }, + "ssids": [ + { + "name": "OpenWifi", + "wifi-bands": [ + "2G" + ], + "bss-mode": "ap", + "encryption": { + "proto": "psk2", + "key": "OpenWifi", + "ieee80211w": "optional" + } + } + ] + }, + { + "name": "LAN", + "role": "downstream", + "services": [ "ssh", "lldp" ], + "ethernet": [ + { + "select-ports": [ + "LAN*" + ] + } + ], + "ipv4": { + "addressing": "static", + "subnet": "192.168.1.1/24", + "dhcp": { + "lease-first": 10, + "lease-count": 100, + "lease-time": "6h" + } + }, + "ssids": [ + { + "name": "OpenWifi", + "wifi-bands": [ + "2G" + ], + "bss-mode": "ap", + "encryption": { + "proto": "psk2", + "key": "OpenWifi", + "ieee80211w": "optional" + } + } + ] + + } + ], + "metrics": { + "statistics": { + "interval": 120, + "types": [ "ssids", "lldp", "clients" ] + }, + "health": { + "interval": 120 + } + }, + "services": { + "lldp": { + "describe": "uCentral", + "location": "universe" + }, + "ssh": { + "port": 22 + }, + "quality-of-service": { + "select-ports": [ "WAN" ], + "bandwidth_up": 1000, + "bandwidth_down": 1000, + "classifier": [ + { + "dscp": "CS0", + "ports": [ + { "protocol": "any", "port": 53 }, + { "protocol": "tcp", "port": 80 } + ], + "dns": [ + "telecominfraproject.com" + ] + }, { + "dscp": "CS1", + "ports": [ + { "protocol": "any", "port": 53, "range-end": 80 }, + { "protocol": "udp", "port": 80, "reclassify": true } + ], + "dns": [ + "telecominfraproject.com" + ] + } + ] + } + } +} diff --git a/feeds/ucentral/ucentral-schema/files/etc/uci-defaults/99-ucentral-qos b/feeds/ucentral/ucentral-schema/files/etc/uci-defaults/99-ucentral-qos new file mode 100644 index 000000000..37d555399 --- /dev/null +++ b/feeds/ucentral/ucentral-schema/files/etc/uci-defaults/99-ucentral-qos @@ -0,0 +1,5 @@ +#!/bin/sh + +uci delete qosify.wan +uci delete qosify.wandev +uci set qosify.@defaults[-1].defaults=/tmp/qosify.conf