loader/interface: attach bpf program directly using netlink
authorFelix Fietkau <nbd@nbd.name>
Tue, 5 Apr 2022 19:25:29 +0000 (21:25 +0200)
committerFelix Fietkau <nbd@nbd.name>
Tue, 5 Apr 2022 19:25:31 +0000 (21:25 +0200)
This makes it possible to replace the tc-full/tc-bpf dependency with a
simple tc dependency.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
CMakeLists.txt
interface.c
loader.c
qosify.h

index 328286688affb3e373d2bc22e8fcd8334c5f8883..8a8ea4042ed891de00422fb7890b8005646b0ab2 100644 (file)
@@ -6,9 +6,18 @@ ADD_DEFINITIONS(-Os -Wall -Wno-unknown-warning-option -Wno-array-bounds -Wno-for
 
 SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "")
 
+IF (NOT DEFINED LIBNL_LIBS)
+       include(FindPkgConfig)
+       pkg_search_module(LIBNL libnl-3.0 libnl-3 libnl nl-3 nl)
+       IF (LIBNL_FOUND)
+               include_directories(${LIBNL_INCLUDE_DIRS})
+               SET(LIBNL_LIBS ${LIBNL_LIBRARIES})
+       ENDIF()
+ENDIF()
+
 find_library(bpf NAMES bpf)
 ADD_EXECUTABLE(qosify main.c loader.c map.c ubus.c interface.c dns.c)
-TARGET_LINK_LIBRARIES(qosify ${bpf} ubox ubus)
+TARGET_LINK_LIBRARIES(qosify ${bpf} ubox ubus ${LIBNL_LIBS})
 
 INSTALL(TARGETS qosify
        RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}
index 46e36a3d4dea0ff9422326d690c49fa4e1176732..7979b21d71b822bf302e987169636938556da010 100644 (file)
@@ -2,15 +2,24 @@
 /*
  * 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>
@@ -24,6 +33,7 @@ static void interface_update_cb(struct vlist_tree *tree,
 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__)
 
@@ -211,15 +221,44 @@ prepare_filter_cmd(char *buf, int len, const char *dev, int prio, bool add, bool
 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
@@ -534,12 +573,60 @@ void qosify_iface_status(struct blob_buf *b)
        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;
 }
 
@@ -551,5 +638,7 @@ void qosify_iface_stop(void)
                interface_stop(iface);
        vlist_for_each_element(&devices, iface, node)
                interface_stop(iface);
+
+       nl_socket_free(rtnl_sock);
 }
 
index c50e739056199b56e983c89b64df621b3a83eecd..64797f0092c27abfe3714457b8be54ff2587eb45 100644 (file)
--- a/loader.c
+++ b/loader.c
 
 #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)
 {
@@ -38,8 +49,24 @@ static void qosify_fill_rodata(struct bpf_object *obj, uint32_t flags)
        }
 }
 
+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,
@@ -49,7 +76,7 @@ qosify_create_program(const char *suffix, uint32_t flags)
        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);
@@ -66,7 +93,7 @@ qosify_create_program(const char *suffix, uint32_t flags)
 
        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) {
@@ -81,24 +108,23 @@ qosify_create_program(const char *suffix, uint32_t flags)
        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;
 
@@ -107,13 +133,12 @@ int qosify_loader_init(void)
                        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;
        }
 
index 5f14d4a35524486ab493ceac2b49a0fcdeae4c5b..dab1ebb7e3fda2431fa50d7a7ceaeed727f5a394 100644 (file)
--- a/qosify.h
+++ b/qosify.h
@@ -78,6 +78,7 @@ extern struct qosify_flow_config flow_config;
 int qosify_run_cmd(char *cmd, bool ignore_error);
 
 int qosify_loader_init(void);
+const char *qosify_get_program(uint32_t flags, int *fd);
 
 int qosify_map_init(void);
 int qosify_map_dscp_value(const char *val, uint8_t *dscp);