mirror of
				https://github.com/Telecominfraproject/wlan-ap.git
				synced 2025-11-04 04:18:07 +00:00 
			
		
		
		
	qosify: update to latest HEAD
Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
		
							
								
								
									
										112
									
								
								feeds/ucentral/qosify/src/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								feeds/ucentral/qosify/src/README
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
// 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>
 | 
			
		||||
@@ -130,8 +134,9 @@ static const char *check_str(struct blob_attr *attr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 *cur;
 | 
			
		||||
 | 
			
		||||
@@ -145,6 +150,7 @@ iface_config_set(struct qosify_iface_config *cfg, struct blob_attr *attr)
 | 
			
		||||
	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);
 | 
			
		||||
@@ -386,7 +392,7 @@ 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);
 | 
			
		||||
	iface_config_set(iface, iface->config_data);
 | 
			
		||||
	interface_start(iface);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
// 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>
 | 
			
		||||
@@ -35,24 +39,17 @@ static void qosify_fill_rodata(struct bpf_object *obj, uint32_t flags)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
		.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);
 | 
			
		||||
@@ -91,7 +88,7 @@ qosify_create_program(const char *suffix, uint32_t flags, bool *force_init)
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int qosify_loader_init(bool force_init)
 | 
			
		||||
int qosify_loader_init(void)
 | 
			
		||||
{
 | 
			
		||||
	static const struct {
 | 
			
		||||
		const char *suffix;
 | 
			
		||||
@@ -105,8 +102,7 @@ int qosify_loader_init(bool force_init)
 | 
			
		||||
	glob_t g;
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	if (force_init &&
 | 
			
		||||
	    glob(CLASSIFY_DATA_PATH "/*", 0, NULL, &g) == 0) {
 | 
			
		||||
	if (glob(CLASSIFY_DATA_PATH "/*", 0, NULL, &g) == 0) {
 | 
			
		||||
		for (i = 0; i < g.gl_pathc; i++)
 | 
			
		||||
			unlink(g.gl_pathv[i]);
 | 
			
		||||
	}
 | 
			
		||||
@@ -117,8 +113,7 @@ int qosify_loader_init(bool force_init)
 | 
			
		||||
	qosify_init_env();
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < ARRAY_SIZE(progs); i++) {
 | 
			
		||||
		if (qosify_create_program(progs[i].suffix, progs[i].flags,
 | 
			
		||||
				      &force_init))
 | 
			
		||||
		if (qosify_create_program(progs[i].suffix, progs[i].flags))
 | 
			
		||||
			return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
 */
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <unistd.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
@@ -10,7 +14,6 @@ static int usage(const char *progname)
 | 
			
		||||
{
 | 
			
		||||
	fprintf(stderr, "Usage: %s [options]\n"
 | 
			
		||||
		"Options:\n"
 | 
			
		||||
		"	-f:		force reload of BPF programs\n"
 | 
			
		||||
		"	-l <file>	Load defaults from <file>\n"
 | 
			
		||||
		"	-o		only load program/maps without running as daemon\n"
 | 
			
		||||
		"\n", progname);
 | 
			
		||||
@@ -21,14 +24,12 @@ static int usage(const char *progname)
 | 
			
		||||
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;
 | 
			
		||||
@@ -41,7 +42,7 @@ int main(int argc, char **argv)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (qosify_loader_init(force_init))
 | 
			
		||||
	if (qosify_loader_init())
 | 
			
		||||
		return 2;
 | 
			
		||||
 | 
			
		||||
	if (qosify_map_init())
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
 */
 | 
			
		||||
#include <arpa/inet.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 uint32_t next_timeout;
 | 
			
		||||
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_map_file {
 | 
			
		||||
@@ -34,6 +39,7 @@ static const struct {
 | 
			
		||||
	[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 {
 | 
			
		||||
@@ -62,6 +68,8 @@ static const struct {
 | 
			
		||||
	{ "AF43", 38 },
 | 
			
		||||
	{ "EF", 46 },
 | 
			
		||||
	{ "VA", 44 },
 | 
			
		||||
	{ "LE", 1 },
 | 
			
		||||
	{ "DF", 0 },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void qosify_map_timer_cb(struct uloop_timeout *t)
 | 
			
		||||
@@ -165,7 +173,7 @@ int qosify_map_init(void)
 | 
			
		||||
{
 | 
			
		||||
	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);
 | 
			
		||||
		if (qosify_map_fds[i] < 0)
 | 
			
		||||
			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)
 | 
			
		||||
		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];
 | 
			
		||||
@@ -223,10 +259,12 @@ static void __qosify_map_set_entry(struct qosify_map_data *data)
 | 
			
		||||
		if (!add)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		e = calloc(1, sizeof(*e));
 | 
			
		||||
		e = __qosify_map_alloc_entry(data);
 | 
			
		||||
		if (!e)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		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;
 | 
			
		||||
@@ -246,8 +284,14 @@ static void __qosify_map_set_entry(struct qosify_map_data *data)
 | 
			
		||||
		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 (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) {
 | 
			
		||||
@@ -316,6 +360,9 @@ int qosify_map_set_entry(enum qosify_map_id id, bool file, const char *str, uint
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
@@ -397,6 +444,8 @@ qosify_map_parse_line(char *str)
 | 
			
		||||
	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))
 | 
			
		||||
@@ -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_UDP_PORTS, 0);
 | 
			
		||||
	qosify_map_timeout = 3600;
 | 
			
		||||
	qosify_active_timeout = 300;
 | 
			
		||||
 | 
			
		||||
	memset(&config, 0, sizeof(config));
 | 
			
		||||
	config.dscp_prio = 0xff;
 | 
			
		||||
@@ -502,12 +552,44 @@ void qosify_map_reload(void)
 | 
			
		||||
	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();
 | 
			
		||||
	int fd;
 | 
			
		||||
 | 
			
		||||
	next_timeout = 0;
 | 
			
		||||
	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) {
 | 
			
		||||
			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;
 | 
			
		||||
@@ -527,10 +612,7 @@ void qosify_map_gc(void)
 | 
			
		||||
		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);
 | 
			
		||||
		qosify_map_free_entry(e);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!timeout)
 | 
			
		||||
@@ -539,6 +621,52 @@ void qosify_map_gc(void)
 | 
			
		||||
	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;
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
		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));
 | 
			
		||||
			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_add_string_buffer(b);
 | 
			
		||||
		blobmsg_close_table(b, c);
 | 
			
		||||
	}
 | 
			
		||||
	blobmsg_close_array(b, a);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
// 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>
 | 
			
		||||
@@ -64,7 +68,7 @@ struct {
 | 
			
		||||
	__uint(type, BPF_MAP_TYPE_HASH);
 | 
			
		||||
	__uint(pinning, 1);
 | 
			
		||||
	__uint(key_size, sizeof(struct in_addr));
 | 
			
		||||
	__type(value, __u8);
 | 
			
		||||
	__type(value, struct qosify_ip_map_val);
 | 
			
		||||
	__uint(max_entries, 100000);
 | 
			
		||||
	__uint(map_flags, BPF_F_NO_PREALLOC);
 | 
			
		||||
} ipv4_map SEC(".maps");
 | 
			
		||||
@@ -73,7 +77,7 @@ struct {
 | 
			
		||||
	__uint(type, BPF_MAP_TYPE_HASH);
 | 
			
		||||
	__uint(pinning, 1);
 | 
			
		||||
	__uint(key_size, sizeof(struct in6_addr));
 | 
			
		||||
	__type(value, __u8);
 | 
			
		||||
	__type(value, struct qosify_ip_map_val);
 | 
			
		||||
	__uint(max_entries, 100000);
 | 
			
		||||
	__uint(map_flags, BPF_F_NO_PREALLOC);
 | 
			
		||||
} ipv6_map SEC(".maps");
 | 
			
		||||
@@ -332,6 +336,7 @@ 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;
 | 
			
		||||
@@ -365,12 +370,17 @@ parse_ipv4(struct __sk_buff *skb, __u32 *offset)
 | 
			
		||||
	else
 | 
			
		||||
		key = &iph->daddr;
 | 
			
		||||
 | 
			
		||||
	value = bpf_map_lookup_elem(&ipv4_map, key);
 | 
			
		||||
	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 */
 | 
			
		||||
	if (!value && dscp == 0xff)
 | 
			
		||||
		value = bpf_map_lookup_elem(&udp_ports, &zero_port);
 | 
			
		||||
		if (value)
 | 
			
		||||
			dscp = *value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	check_flow(config, skb, &dscp);
 | 
			
		||||
 | 
			
		||||
@@ -384,6 +394,7 @@ 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;
 | 
			
		||||
@@ -411,13 +422,17 @@ parse_ipv6(struct __sk_buff *skb, __u32 *offset)
 | 
			
		||||
 | 
			
		||||
	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) {
 | 
			
		||||
		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 */
 | 
			
		||||
	if (!value)
 | 
			
		||||
		value = bpf_map_lookup_elem(&udp_ports, &zero_port);
 | 
			
		||||
		if (value)
 | 
			
		||||
			dscp = *value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	check_flow(config, skb, &dscp);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
 */
 | 
			
		||||
#ifndef __BPF_QOSIFY_H
 | 
			
		||||
#define __BPF_QOSIFY_H
 | 
			
		||||
 | 
			
		||||
@@ -27,4 +31,9 @@ struct qosify_config {
 | 
			
		||||
	uint16_t prio_max_avg_pkt_len;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct qosify_ip_map_val {
 | 
			
		||||
	uint8_t dscp; /* must be first */
 | 
			
		||||
	uint8_t seen;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,12 @@
 | 
			
		||||
// 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>
 | 
			
		||||
@@ -25,6 +30,7 @@ enum qosify_map_id {
 | 
			
		||||
	CL_MAP_IPV4_ADDR,
 | 
			
		||||
	CL_MAP_IPV6_ADDR,
 | 
			
		||||
	CL_MAP_CONFIG,
 | 
			
		||||
	CL_MAP_DNS,
 | 
			
		||||
	__CL_MAP_MAX,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +47,10 @@ struct qosify_map_data {
 | 
			
		||||
		uint32_t port;
 | 
			
		||||
		struct in_addr ip;
 | 
			
		||||
		struct in6_addr ip6;
 | 
			
		||||
		struct {
 | 
			
		||||
			const char *pattern;
 | 
			
		||||
			regex_t regex;
 | 
			
		||||
		} dns;
 | 
			
		||||
	} addr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -54,9 +64,10 @@ struct qosify_map_entry {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
extern int qosify_map_timeout;
 | 
			
		||||
extern int qosify_active_timeout;
 | 
			
		||||
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_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_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);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
// SPDX-License-Identifier: GPL-2.0+
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
 | 
			
		||||
 */
 | 
			
		||||
#include <libubus.h>
 | 
			
		||||
 | 
			
		||||
#include "qosify.h"
 | 
			
		||||
@@ -46,6 +50,7 @@ enum {
 | 
			
		||||
	CL_ADD_IPV6,
 | 
			
		||||
	CL_ADD_TCP_PORT,
 | 
			
		||||
	CL_ADD_UDP_PORT,
 | 
			
		||||
	CL_ADD_DNS,
 | 
			
		||||
	__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_TCP_PORT] = { "tcp_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))
 | 
			
		||||
		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;
 | 
			
		||||
@@ -254,12 +264,6 @@ qosify_ubus_status(struct ubus_context *ctx, struct ubus_object *obj,
 | 
			
		||||
	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,
 | 
			
		||||
@@ -270,6 +274,48 @@ qosify_ubus_check_devices(struct ubus_context *ctx, struct ubus_object *obj,
 | 
			
		||||
	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),
 | 
			
		||||
@@ -279,6 +325,7 @@ static const struct ubus_method qosify_methods[] = {
 | 
			
		||||
	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),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user