qosify: update to latest HEAD

Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
John Crispin
2021-11-04 11:28:38 +01:00
parent a9fd11ed8a
commit 04d78d3334
9 changed files with 381 additions and 53 deletions

View File

@@ -0,0 +1,112 @@
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,3 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/wait.h> #include <sys/wait.h>
@@ -130,8 +134,9 @@ static const char *check_str(struct blob_attr *attr)
} }
static void static void
iface_config_set(struct qosify_iface_config *cfg, struct blob_attr *attr) 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 *tb[__IFACE_ATTR_MAX];
struct blob_attr *cur; struct blob_attr *cur;
@@ -145,6 +150,7 @@ iface_config_set(struct qosify_iface_config *cfg, struct blob_attr *attr)
cfg->egress = true; cfg->egress = true;
cfg->host_isolate = true; cfg->host_isolate = true;
cfg->autorate_ingress = true; cfg->autorate_ingress = true;
cfg->nat = !iface->device;
if ((cur = tb[IFACE_ATTR_BW_UP]) != NULL) if ((cur = tb[IFACE_ATTR_BW_UP]) != NULL)
cfg->bandwidth_up = check_str(cur); cfg->bandwidth_up = check_str(cur);
@@ -386,7 +392,7 @@ static void
interface_set_config(struct qosify_iface *iface, struct blob_attr *config) interface_set_config(struct qosify_iface *iface, struct blob_attr *config)
{ {
iface->config_data = blob_memdup(config); iface->config_data = blob_memdup(config);
iface_config_set(&iface->config, iface->config_data); iface_config_set(iface, iface->config_data);
interface_start(iface); interface_start(iface);
} }

View File

@@ -1,3 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <sys/resource.h> #include <sys/resource.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <arpa/inet.h> #include <arpa/inet.h>
@@ -35,24 +39,17 @@ static void qosify_fill_rodata(struct bpf_object *obj, uint32_t flags)
} }
static int static int
qosify_create_program(const char *suffix, uint32_t flags, bool *force_init) qosify_create_program(const char *suffix, uint32_t flags)
{ {
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts, DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
.pin_root_path = CLASSIFY_DATA_PATH, .pin_root_path = CLASSIFY_DATA_PATH,
); );
struct bpf_program *prog; struct bpf_program *prog;
struct bpf_object *obj; struct bpf_object *obj;
struct stat st;
char path[256]; char path[256];
int err; int err;
snprintf(path, sizeof(path), CLASSIFY_PIN_PATH "_" "%s", suffix); 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); obj = bpf_object__open_file(CLASSIFY_PROG_PATH, &opts);
err = libbpf_get_error(obj); err = libbpf_get_error(obj);
@@ -91,7 +88,7 @@ qosify_create_program(const char *suffix, uint32_t flags, bool *force_init)
return 0; return 0;
} }
int qosify_loader_init(bool force_init) int qosify_loader_init(void)
{ {
static const struct { static const struct {
const char *suffix; const char *suffix;
@@ -105,8 +102,7 @@ int qosify_loader_init(bool force_init)
glob_t g; glob_t g;
int i; int i;
if (force_init && if (glob(CLASSIFY_DATA_PATH "/*", 0, NULL, &g) == 0) {
glob(CLASSIFY_DATA_PATH "/*", 0, NULL, &g) == 0) {
for (i = 0; i < g.gl_pathc; i++) for (i = 0; i < g.gl_pathc; i++)
unlink(g.gl_pathv[i]); unlink(g.gl_pathv[i]);
} }
@@ -117,8 +113,7 @@ int qosify_loader_init(bool force_init)
qosify_init_env(); qosify_init_env();
for (i = 0; i < ARRAY_SIZE(progs); i++) { for (i = 0; i < ARRAY_SIZE(progs); i++) {
if (qosify_create_program(progs[i].suffix, progs[i].flags, if (qosify_create_program(progs[i].suffix, progs[i].flags))
&force_init))
return -1; return -1;
} }

View File

@@ -1,3 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdint.h> #include <stdint.h>
@@ -10,7 +14,6 @@ static int usage(const char *progname)
{ {
fprintf(stderr, "Usage: %s [options]\n" fprintf(stderr, "Usage: %s [options]\n"
"Options:\n" "Options:\n"
" -f: force reload of BPF programs\n"
" -l <file> Load defaults from <file>\n" " -l <file> Load defaults from <file>\n"
" -o only load program/maps without running as daemon\n" " -o only load program/maps without running as daemon\n"
"\n", progname); "\n", progname);
@@ -21,14 +24,12 @@ static int usage(const char *progname)
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
const char *load_file = NULL; const char *load_file = NULL;
bool force_init = false;
bool oneshot = false; bool oneshot = false;
int ch; int ch;
while ((ch = getopt(argc, argv, "fl:o")) != -1) { while ((ch = getopt(argc, argv, "fl:o")) != -1) {
switch (ch) { switch (ch) {
case 'f': case 'f':
force_init = true;
break; break;
case 'l': case 'l':
load_file = optarg; load_file = optarg;
@@ -41,7 +42,7 @@ int main(int argc, char **argv)
} }
} }
if (qosify_loader_init(force_init)) if (qosify_loader_init())
return 2; return 2;
if (qosify_map_init()) if (qosify_map_init())

View File

@@ -1,3 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <arpa/inet.h> #include <arpa/inet.h>
#include <errno.h> #include <errno.h>
@@ -17,7 +21,8 @@ static AVL_TREE(map_data, qosify_map_entry_cmp, false, NULL);
static LIST_HEAD(map_files); static LIST_HEAD(map_files);
static uint32_t next_timeout; static uint32_t next_timeout;
static uint8_t qosify_dscp_default[2] = { 0xff, 0xff }; static uint8_t qosify_dscp_default[2] = { 0xff, 0xff };
int qosify_map_timeout = 3600; int qosify_map_timeout;
int qosify_active_timeout;
struct qosify_config config; struct qosify_config config;
struct qosify_map_file { struct qosify_map_file {
@@ -34,6 +39,7 @@ static const struct {
[CL_MAP_IPV4_ADDR] = { "ipv4_map", "ipv4_addr" }, [CL_MAP_IPV4_ADDR] = { "ipv4_map", "ipv4_addr" },
[CL_MAP_IPV6_ADDR] = { "ipv6_map", "ipv6_addr" }, [CL_MAP_IPV6_ADDR] = { "ipv6_map", "ipv6_addr" },
[CL_MAP_CONFIG] = { "config", "config" }, [CL_MAP_CONFIG] = { "config", "config" },
[CL_MAP_DNS] = { "dns", "dns" },
}; };
static const struct { static const struct {
@@ -62,6 +68,8 @@ static const struct {
{ "AF43", 38 }, { "AF43", 38 },
{ "EF", 46 }, { "EF", 46 },
{ "VA", 44 }, { "VA", 44 },
{ "LE", 1 },
{ "DF", 0 },
}; };
static void qosify_map_timer_cb(struct uloop_timeout *t) static void qosify_map_timer_cb(struct uloop_timeout *t)
@@ -165,7 +173,7 @@ int qosify_map_init(void)
{ {
int i; int i;
for (i = 0; i < ARRAY_SIZE(qosify_map_fds); i++) { for (i = 0; i < CL_MAP_DNS; i++) {
qosify_map_fds[i] = qosify_map_get_fd(i); qosify_map_fds[i] = qosify_map_get_fd(i);
if (qosify_map_fds[i] < 0) if (qosify_map_fds[i] < 0)
return -1; return -1;
@@ -206,9 +214,37 @@ static int qosify_map_entry_cmp(const void *k1, const void *k2, void *ptr)
if (d1->id != d2->id) if (d1->id != d2->id)
return d2->id - d1->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)); 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) static void __qosify_map_set_entry(struct qosify_map_data *data)
{ {
int fd = qosify_map_fds[data->id]; int fd = qosify_map_fds[data->id];
@@ -223,10 +259,12 @@ static void __qosify_map_set_entry(struct qosify_map_data *data)
if (!add) if (!add)
return; return;
e = calloc(1, sizeof(*e)); e = __qosify_map_alloc_entry(data);
if (!e)
return;
e->avl.key = &e->data; e->avl.key = &e->data;
e->data.id = data->id; e->data.id = data->id;
memcpy(&e->data.addr, &data->addr, sizeof(e->data.addr));
avl_insert(&map_data, &e->avl); avl_insert(&map_data, &e->avl);
} else { } else {
prev_dscp = e->data.dscp; prev_dscp = e->data.dscp;
@@ -246,8 +284,14 @@ static void __qosify_map_set_entry(struct qosify_map_data *data)
e->data.dscp = e->data.file_dscp; e->data.dscp = e->data.file_dscp;
} }
if (e->data.dscp != prev_dscp) if (e->data.dscp != prev_dscp && data->id < CL_MAP_DNS) {
bpf_map_update_elem(fd, &data->addr, &e->data.dscp, BPF_ANY); 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 (add) {
if (qosify_map_timeout == ~0 || file) { if (qosify_map_timeout == ~0 || file) {
@@ -316,6 +360,9 @@ int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str, uint
}; };
switch (id) { switch (id) {
case CL_MAP_DNS:
data.addr.dns.pattern = str;
break;
case CL_MAP_TCP_PORTS: case CL_MAP_TCP_PORTS:
case CL_MAP_UDP_PORTS: case CL_MAP_UDP_PORTS:
return qosify_map_set_port(&data, str); return qosify_map_set_port(&data, str);
@@ -397,6 +444,8 @@ qosify_map_parse_line(char *str)
if (dscp < 0) if (dscp < 0)
return; return;
if (!strncmp(key, "dns:", 4))
qosify_map_set_entry(CL_MAP_DNS, true, key + 4, dscp);
if (!strncmp(key, "tcp:", 4)) if (!strncmp(key, "tcp:", 4))
qosify_map_set_entry(CL_MAP_TCP_PORTS, true, key + 4, dscp); qosify_map_set_entry(CL_MAP_TCP_PORTS, true, key + 4, dscp);
else if (!strncmp(key, "udp:", 4)) else if (!strncmp(key, "udp:", 4))
@@ -483,6 +532,7 @@ void qosify_map_reset_config(void)
qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, 0); qosify_map_set_dscp_default(CL_MAP_TCP_PORTS, 0);
qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, 0); qosify_map_set_dscp_default(CL_MAP_UDP_PORTS, 0);
qosify_map_timeout = 3600; qosify_map_timeout = 3600;
qosify_active_timeout = 300;
memset(&config, 0, sizeof(config)); memset(&config, 0, sizeof(config));
config.dscp_prio = 0xff; config.dscp_prio = 0xff;
@@ -502,12 +552,44 @@ void qosify_map_reload(void)
qosify_map_gc(); 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) void qosify_map_gc(void)
{ {
struct qosify_map_entry *e, *tmp; struct qosify_map_entry *e, *tmp;
int32_t timeout = 0; int32_t timeout = 0;
uint32_t cur_time = qosify_gettime(); uint32_t cur_time = qosify_gettime();
int fd;
next_timeout = 0; next_timeout = 0;
avl_for_each_element_safe(&map_data, e, avl, tmp) { avl_for_each_element_safe(&map_data, e, avl, tmp) {
@@ -515,6 +597,9 @@ void qosify_map_gc(void)
if (e->data.user && e->timeout != ~0) { if (e->data.user && e->timeout != ~0) {
cur_timeout = e->timeout - cur_time; 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) { if (cur_timeout <= 0) {
e->data.user = false; e->data.user = false;
e->data.dscp = e->data.file_dscp; e->data.dscp = e->data.file_dscp;
@@ -527,10 +612,7 @@ void qosify_map_gc(void)
if (e->data.file || e->data.user) if (e->data.file || e->data.user)
continue; continue;
avl_delete(&map_data, &e->avl); qosify_map_free_entry(e);
fd = qosify_map_fds[e->data.id];
bpf_map_delete_elem(fd, &e->data.addr);
free(e);
} }
if (!timeout) if (!timeout)
@@ -539,6 +621,52 @@ void qosify_map_gc(void)
uloop_timeout_set(&qosify_map_timer, timeout * 1000); 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) void qosify_map_dump(struct blob_buf *b)
{ {
struct qosify_map_entry *e; struct qosify_map_entry *e;
@@ -574,22 +702,25 @@ void qosify_map_dump(struct blob_buf *b)
blobmsg_add_string(b, "type", qosify_map_info[e->data.id].type_name); 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) { switch (e->data.id) {
case CL_MAP_TCP_PORTS: case CL_MAP_TCP_PORTS:
case CL_MAP_UDP_PORTS: case CL_MAP_UDP_PORTS:
snprintf(buf, buf_len, "%d", ntohs(e->data.addr.port)); blobmsg_printf(b, "addr", "%d", ntohs(e->data.addr.port));
break; break;
case CL_MAP_IPV4_ADDR: case CL_MAP_IPV4_ADDR:
case CL_MAP_IPV6_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; af = e->data.id == CL_MAP_IPV6_ADDR ? AF_INET6 : AF_INET;
inet_ntop(af, &e->data.addr, buf, buf_len); 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; break;
default: default:
*buf = 0; *buf = 0;
break; break;
} }
blobmsg_add_string_buffer(b);
blobmsg_close_table(b, c); blobmsg_close_table(b, c);
} }
blobmsg_close_array(b, a); blobmsg_close_array(b, a);

View File

@@ -1,3 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#define KBUILD_MODNAME "foo" #define KBUILD_MODNAME "foo"
#include <uapi/linux/bpf.h> #include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h> #include <uapi/linux/if_ether.h>
@@ -64,7 +68,7 @@ struct {
__uint(type, BPF_MAP_TYPE_HASH); __uint(type, BPF_MAP_TYPE_HASH);
__uint(pinning, 1); __uint(pinning, 1);
__uint(key_size, sizeof(struct in_addr)); __uint(key_size, sizeof(struct in_addr));
__type(value, __u8); __type(value, struct qosify_ip_map_val);
__uint(max_entries, 100000); __uint(max_entries, 100000);
__uint(map_flags, BPF_F_NO_PREALLOC); __uint(map_flags, BPF_F_NO_PREALLOC);
} ipv4_map SEC(".maps"); } ipv4_map SEC(".maps");
@@ -73,7 +77,7 @@ struct {
__uint(type, BPF_MAP_TYPE_HASH); __uint(type, BPF_MAP_TYPE_HASH);
__uint(pinning, 1); __uint(pinning, 1);
__uint(key_size, sizeof(struct in6_addr)); __uint(key_size, sizeof(struct in6_addr));
__type(value, __u8); __type(value, struct qosify_ip_map_val);
__uint(max_entries, 100000); __uint(max_entries, 100000);
__uint(map_flags, BPF_F_NO_PREALLOC); __uint(map_flags, BPF_F_NO_PREALLOC);
} ipv6_map SEC(".maps"); } ipv6_map SEC(".maps");
@@ -332,6 +336,7 @@ static __always_inline void
parse_ipv4(struct __sk_buff *skb, __u32 *offset) parse_ipv4(struct __sk_buff *skb, __u32 *offset)
{ {
struct qosify_config *config; struct qosify_config *config;
struct qosify_ip_map_val *ip_val;
const __u32 zero_port = 0; const __u32 zero_port = 0;
struct iphdr *iph; struct iphdr *iph;
__u8 dscp = 0xff; __u8 dscp = 0xff;
@@ -365,12 +370,17 @@ parse_ipv4(struct __sk_buff *skb, __u32 *offset)
else else
key = &iph->daddr; key = &iph->daddr;
value = bpf_map_lookup_elem(&ipv4_map, key); ip_val = bpf_map_lookup_elem(&ipv4_map, key);
/* use udp port 0 entry as fallback for non-tcp/udp */ if (ip_val) {
if (!value && dscp == 0xff) 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); value = bpf_map_lookup_elem(&udp_ports, &zero_port);
if (value) if (value)
dscp = *value; dscp = *value;
}
check_flow(config, skb, &dscp); check_flow(config, skb, &dscp);
@@ -384,6 +394,7 @@ static __always_inline void
parse_ipv6(struct __sk_buff *skb, __u32 *offset) parse_ipv6(struct __sk_buff *skb, __u32 *offset)
{ {
struct qosify_config *config; struct qosify_config *config;
struct qosify_ip_map_val *ip_val;
const __u32 zero_port = 0; const __u32 zero_port = 0;
struct ipv6hdr *iph; struct ipv6hdr *iph;
__u8 dscp = 0; __u8 dscp = 0;
@@ -411,13 +422,17 @@ parse_ipv6(struct __sk_buff *skb, __u32 *offset)
parse_l4proto(config, skb, *offset, ipproto, &dscp); parse_l4proto(config, skb, *offset, ipproto, &dscp);
value = bpf_map_lookup_elem(&ipv6_map, key); ip_val = bpf_map_lookup_elem(&ipv6_map, key);
if (ip_val) {
/* use udp port 0 entry as fallback for non-tcp/udp */ if (!ip_val->seen)
if (!value) 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); value = bpf_map_lookup_elem(&udp_ports, &zero_port);
if (value) if (value)
dscp = *value; dscp = *value;
}
check_flow(config, skb, &dscp); check_flow(config, skb, &dscp);

View File

@@ -1,3 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#ifndef __BPF_QOSIFY_H #ifndef __BPF_QOSIFY_H
#define __BPF_QOSIFY_H #define __BPF_QOSIFY_H
@@ -27,4 +31,9 @@ struct qosify_config {
uint16_t prio_max_avg_pkt_len; uint16_t prio_max_avg_pkt_len;
}; };
struct qosify_ip_map_val {
uint8_t dscp; /* must be first */
uint8_t seen;
};
#endif #endif

View File

@@ -1,7 +1,12 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#ifndef __QOS_CLASSIFY_H #ifndef __QOS_CLASSIFY_H
#define __QOS_CLASSIFY_H #define __QOS_CLASSIFY_H
#include <stdbool.h> #include <stdbool.h>
#include <regex.h>
#include <bpf/bpf.h> #include <bpf/bpf.h>
#include <bpf/libbpf.h> #include <bpf/libbpf.h>
@@ -25,6 +30,7 @@ enum qosify_map_id {
CL_MAP_IPV4_ADDR, CL_MAP_IPV4_ADDR,
CL_MAP_IPV6_ADDR, CL_MAP_IPV6_ADDR,
CL_MAP_CONFIG, CL_MAP_CONFIG,
CL_MAP_DNS,
__CL_MAP_MAX, __CL_MAP_MAX,
}; };
@@ -41,6 +47,10 @@ struct qosify_map_data {
uint32_t port; uint32_t port;
struct in_addr ip; struct in_addr ip;
struct in6_addr ip6; struct in6_addr ip6;
struct {
const char *pattern;
regex_t regex;
} dns;
} addr; } addr;
}; };
@@ -54,9 +64,10 @@ struct qosify_map_entry {
extern int qosify_map_timeout; extern int qosify_map_timeout;
extern int qosify_active_timeout;
extern struct qosify_config config; extern struct qosify_config config;
int qosify_loader_init(bool force_init); int qosify_loader_init(void);
int qosify_map_init(void); int qosify_map_init(void);
int qosify_map_dscp_value(const char *val); int qosify_map_dscp_value(const char *val);
@@ -69,6 +80,7 @@ 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_set_dscp_default(enum qosify_map_id id, uint8_t val);
void qosify_map_reset_config(void); void qosify_map_reset_config(void);
void qosify_map_update_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); int qosify_iface_init(void);
void qosify_iface_config_update(struct blob_attr *ifaces, struct blob_attr *devs); void qosify_iface_config_update(struct blob_attr *ifaces, struct blob_attr *devs);

View File

@@ -1,3 +1,7 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
#include <libubus.h> #include <libubus.h>
#include "qosify.h" #include "qosify.h"
@@ -46,6 +50,7 @@ enum {
CL_ADD_IPV6, CL_ADD_IPV6,
CL_ADD_TCP_PORT, CL_ADD_TCP_PORT,
CL_ADD_UDP_PORT, CL_ADD_UDP_PORT,
CL_ADD_DNS,
__CL_ADD_MAX __CL_ADD_MAX
}; };
@@ -56,6 +61,7 @@ static const struct blobmsg_policy qosify_add_policy[__CL_ADD_MAX] = {
[CL_ADD_IPV6] = { "ipv6", BLOBMSG_TYPE_ARRAY }, [CL_ADD_IPV6] = { "ipv6", BLOBMSG_TYPE_ARRAY },
[CL_ADD_TCP_PORT] = { "tcp_port", BLOBMSG_TYPE_ARRAY }, [CL_ADD_TCP_PORT] = { "tcp_port", BLOBMSG_TYPE_ARRAY },
[CL_ADD_UDP_PORT] = { "udp_port", BLOBMSG_TYPE_ARRAY }, [CL_ADD_UDP_PORT] = { "udp_port", BLOBMSG_TYPE_ARRAY },
[CL_ADD_DNS] = { "dns", BLOBMSG_TYPE_ARRAY },
}; };
@@ -113,6 +119,10 @@ qosify_ubus_add(struct ubus_context *ctx, struct ubus_object *obj,
(ret = qosify_ubus_add_array(cur, dscp, CL_MAP_UDP_PORTS) != 0)) (ret = qosify_ubus_add_array(cur, dscp, CL_MAP_UDP_PORTS) != 0))
return ret; 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; qosify_map_timeout = prev_timemout;
return 0; return 0;
@@ -254,12 +264,6 @@ qosify_ubus_status(struct ubus_context *ctx, struct ubus_object *obj,
return 0; return 0;
} }
enum {
CL_DEV_EVENT_NAME,
CL_DEV_EVENT_ADD,
__CL_DEV_EVENT_MAX,
};
static int static int
qosify_ubus_check_devices(struct ubus_context *ctx, struct ubus_object *obj, qosify_ubus_check_devices(struct ubus_context *ctx, struct ubus_object *obj,
struct ubus_request_data *req, const char *method, struct ubus_request_data *req, const char *method,
@@ -270,6 +274,48 @@ qosify_ubus_check_devices(struct ubus_context *ctx, struct ubus_object *obj,
return 0; 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[] = { static const struct ubus_method qosify_methods[] = {
UBUS_METHOD_NOARG("reload", qosify_ubus_reload), UBUS_METHOD_NOARG("reload", qosify_ubus_reload),
@@ -279,6 +325,7 @@ static const struct ubus_method qosify_methods[] = {
UBUS_METHOD("config", qosify_ubus_config, qosify_config_policy), UBUS_METHOD("config", qosify_ubus_config, qosify_config_policy),
UBUS_METHOD_NOARG("dump", qosify_ubus_dump), UBUS_METHOD_NOARG("dump", qosify_ubus_dump),
UBUS_METHOD_NOARG("status", qosify_ubus_status), 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), UBUS_METHOD_NOARG("check_devices", qosify_ubus_check_devices),
}; };