/*
* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name>
*/
+#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <net/if.h>
+#include <netinet/if_ether.h>
#include <unistd.h>
#include <errno.h>
+#include <netlink/msg.h>
+#include <netlink/attr.h>
+#include <netlink/socket.h>
+
+#include <linux/rtnetlink.h>
+#include <linux/pkt_cls.h>
+
#include <libubox/vlist.h>
#include <libubox/avl-cmp.h>
#include <libubox/uloop.h>
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;
+static struct nl_sock *rtnl_sock;
#define APPEND(_buf, _ofs, _format, ...) _ofs += snprintf(_buf + _ofs, sizeof(_buf) - _ofs, _format, ##__VA_ARGS__)
static int
cmd_add_bpf_filter(const char *ifname, int prio, bool egress, bool eth)
{
- char buf[512];
- int ofs;
+ struct tcmsg tcmsg = {
+ .tcm_family = AF_UNSPEC,
+ .tcm_ifindex = if_nametoindex(ifname),
+ };
+ struct nl_msg *msg;
+ struct nlattr *opts;
+ const char *suffix;
+ int prog_fd = -1;
+ char name[32];
+
+ suffix = qosify_get_program(!egress * QOSIFY_INGRESS + !eth * QOSIFY_IP_ONLY, &prog_fd);
+ if (!suffix)
+ return -1;
- ofs = prepare_filter_cmd(buf, sizeof(buf), ifname, prio, true, egress);
- APPEND(buf, ofs, " bpf object-pinned /sys/fs/bpf/qosify_%sgress_%s verbose direct-action",
- egress ? "e" : "in",
- eth ? "eth" : "ip");
+ snprintf(name, sizeof(name), "qosify_%s", suffix);
- return qosify_run_cmd(buf, false);
+ if (egress)
+ tcmsg.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS);
+ else
+ tcmsg.tcm_parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS);
+
+ tcmsg.tcm_info = TC_H_MAKE(prio << 16, htons(ETH_P_ALL));
+
+ msg = nlmsg_alloc_simple(RTM_NEWTFILTER, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL);
+ nlmsg_append(msg, &tcmsg, sizeof(tcmsg), NLMSG_ALIGNTO);
+ nla_put_string(msg, TCA_KIND, "bpf");
+
+ opts = nla_nest_start(msg, TCA_OPTIONS);
+ nla_put_u32(msg, TCA_BPF_FD, prog_fd);
+ nla_put_string(msg, TCA_BPF_NAME, name);
+ nla_put_u32(msg, TCA_BPF_FLAGS, TCA_BPF_FLAG_ACT_DIRECT);
+ nla_put_u32(msg, TCA_BPF_FLAGS_GEN, TCA_CLS_FLAGS_SKIP_HW);
+ nla_nest_end(msg, opts);
+
+ nl_send_auto_complete(rtnl_sock, msg);
+ nlmsg_free(msg);
+
+ return nl_wait_for_ack(rtnl_sock);
}
static int
blobmsg_close_table(b, c);
}
+static int
+qosify_nl_error_cb(struct sockaddr_nl *nla, struct nlmsgerr *err,
+ void *arg)
+{
+ struct nlmsghdr *nlh = (struct nlmsghdr *) err - 1;
+ struct nlattr *tb[NLMSGERR_ATTR_MAX + 1];
+ struct nlattr *attrs;
+ int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);
+ int len = nlh->nlmsg_len;
+ const char *errstr = "(unknown)";
+
+ if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
+ return NL_STOP;
+
+ if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
+ ack_len += err->msg.nlmsg_len - sizeof(*nlh);
+
+ attrs = (void *) ((unsigned char *) nlh + ack_len);
+ len -= ack_len;
+
+ nla_parse(tb, NLMSGERR_ATTR_MAX, attrs, len, NULL);
+ if (tb[NLMSGERR_ATTR_MSG])
+ errstr = nla_data(tb[NLMSGERR_ATTR_MSG]);
+
+ ULOG_ERR("Netlink error(%d): %s\n", err->error, errstr);
+
+ return NL_STOP;
+}
+
int qosify_iface_init(void)
{
+ int fd, opt;
+
socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (socket < 0)
return -1;
+ rtnl_sock = nl_socket_alloc();
+ if (!rtnl_sock)
+ return -1;
+
+ if (nl_connect(rtnl_sock, NETLINK_ROUTE))
+ return -1;
+
+ nl_cb_err(nl_socket_get_cb(rtnl_sock), NL_CB_CUSTOM,
+ qosify_nl_error_cb, NULL);
+
+ fd = nl_socket_get_fd(rtnl_sock);
+ opt = 1;
+ setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &opt, sizeof(opt));
+
+ opt = 1;
+ setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &opt, sizeof(opt));
+
return 0;
}
interface_stop(iface);
vlist_for_each_element(&devices, iface, node)
interface_stop(iface);
+
+ nl_socket_free(rtnl_sock);
}
#include "qosify.h"
+static struct {
+ const char *suffix;
+ uint32_t flags;
+ int fd;
+} bpf_progs[] = {
+ { "egress_eth", 0 },
+ { "egress_ip", QOSIFY_IP_ONLY },
+ { "ingress_eth", QOSIFY_INGRESS },
+ { "ingress_ip", QOSIFY_INGRESS | QOSIFY_IP_ONLY },
+};
+
static int qosify_bpf_pr(enum libbpf_print_level level, const char *format,
va_list args)
{
}
}
+const char *qosify_get_program(uint32_t flags, int *fd)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(bpf_progs); i++) {
+ if (bpf_progs[i].flags != flags)
+ continue;
+
+ *fd = bpf_progs[i].fd;
+ return bpf_progs[i].suffix;
+ }
+
+ return NULL;
+}
+
+
static int
-qosify_create_program(const char *suffix, uint32_t flags)
+qosify_create_program(int idx)
{
DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts,
.pin_root_path = CLASSIFY_DATA_PATH,
char path[256];
int err;
- snprintf(path, sizeof(path), CLASSIFY_PIN_PATH "_" "%s", suffix);
+ snprintf(path, sizeof(path), CLASSIFY_PIN_PATH "_" "%s", bpf_progs[idx].suffix);
obj = bpf_object__open_file(CLASSIFY_PROG_PATH, &opts);
err = libbpf_get_error(obj);
bpf_program__set_type(prog, BPF_PROG_TYPE_SCHED_CLS);
- qosify_fill_rodata(obj, flags);
+ qosify_fill_rodata(obj, bpf_progs[idx].flags);
err = bpf_object__load(obj);
if (err) {
if (err) {
fprintf(stderr, "Failed to pin program to %s: %s\n",
path, strerror(-err));
+ return -1;
}
bpf_object__close(obj);
+ err = bpf_obj_get(path);
+ if (err < 0) {
+ fprintf(stderr, "Failed to load pinned program %s: %s\n",
+ path, strerror(errno));
+ }
+ bpf_progs[idx].fd = err;
+
return 0;
}
int qosify_loader_init(void)
{
- static const struct {
- const char *suffix;
- uint32_t flags;
- } progs[] = {
- { "egress_eth", 0 },
- { "egress_ip", QOSIFY_IP_ONLY },
- { "ingress_eth", QOSIFY_INGRESS },
- { "ingress_ip", QOSIFY_INGRESS | QOSIFY_IP_ONLY },
- };
glob_t g;
int i;
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))
+ for (i = 0; i < ARRAY_SIZE(bpf_progs); i++) {
+ if (qosify_create_program(i))
return -1;
}