mirror of
https://github.com/edk2-porting/linux-next.git
synced 2025-01-18 02:24:21 +08:00
c359821ac6
Introduce 4 new netlink-based XDP APIs for attaching, detaching, and querying XDP programs: - bpf_xdp_attach; - bpf_xdp_detach; - bpf_xdp_query; - bpf_xdp_query_id. These APIs replace bpf_set_link_xdp_fd, bpf_set_link_xdp_fd_opts, bpf_get_link_xdp_id, and bpf_get_link_xdp_info APIs ([0]). The latter don't follow a consistent naming pattern and some of them use non-extensible approaches (e.g., struct xdp_link_info which can't be modified without breaking libbpf ABI). The approach I took with these low-level XDP APIs is similar to what we did with low-level TC APIs. There is a nice duality of bpf_tc_attach vs bpf_xdp_attach, and so on. I left bpf_xdp_attach() to support detaching when -1 is specified for prog_fd for generality and convenience, but bpf_xdp_detach() is preferred due to clearer naming and associated semantics. Both bpf_xdp_attach() and bpf_xdp_detach() accept the same opts struct allowing to specify expected old_prog_fd. While doing the refactoring, I noticed that old APIs require users to specify opts with old_fd == -1 to declare "don't care about already attached XDP prog fd" condition. Otherwise, FD 0 is assumed, which is essentially never an intended behavior. So I made this behavior consistent with other kernel and libbpf APIs, in which zero FD means "no FD". This seems to be more in line with the latest thinking in BPF land and should cause less user confusion, hopefully. For querying, I left two APIs, both more generic bpf_xdp_query() allowing to query multiple IDs and attach mode, but also a specialization of it, bpf_xdp_query_id(), which returns only requested prog_id. Uses of prog_id returning bpf_get_link_xdp_id() were so prevalent across selftests and samples, that it seemed a very common use case and using bpf_xdp_query() for doing it felt very cumbersome with a highly branches if/else chain based on flags and attach mode. Old APIs are scheduled for deprecation in libbpf 0.8 release. [0] Closes: https://github.com/libbpf/libbpf/issues/309 Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Acked-by: Toke Høiland-Jørgensen <toke@redhat.com> Link: https://lore.kernel.org/r/20220120061422.2710637-2-andrii@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
810 lines
19 KiB
C
810 lines
19 KiB
C
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
|
/* Copyright (c) 2018 Facebook */
|
|
|
|
#include <stdlib.h>
|
|
#include <memory.h>
|
|
#include <unistd.h>
|
|
#include <arpa/inet.h>
|
|
#include <linux/bpf.h>
|
|
#include <linux/if_ether.h>
|
|
#include <linux/pkt_cls.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <sys/socket.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
|
|
#include "bpf.h"
|
|
#include "libbpf.h"
|
|
#include "libbpf_internal.h"
|
|
#include "nlattr.h"
|
|
|
|
#ifndef SOL_NETLINK
|
|
#define SOL_NETLINK 270
|
|
#endif
|
|
|
|
typedef int (*libbpf_dump_nlmsg_t)(void *cookie, void *msg, struct nlattr **tb);
|
|
|
|
typedef int (*__dump_nlmsg_t)(struct nlmsghdr *nlmsg, libbpf_dump_nlmsg_t,
|
|
void *cookie);
|
|
|
|
struct xdp_id_md {
|
|
int ifindex;
|
|
__u32 flags;
|
|
struct xdp_link_info info;
|
|
};
|
|
|
|
static int libbpf_netlink_open(__u32 *nl_pid)
|
|
{
|
|
struct sockaddr_nl sa;
|
|
socklen_t addrlen;
|
|
int one = 1, ret;
|
|
int sock;
|
|
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.nl_family = AF_NETLINK;
|
|
|
|
sock = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
|
|
if (sock < 0)
|
|
return -errno;
|
|
|
|
if (setsockopt(sock, SOL_NETLINK, NETLINK_EXT_ACK,
|
|
&one, sizeof(one)) < 0) {
|
|
pr_warn("Netlink error reporting not supported\n");
|
|
}
|
|
|
|
if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
|
|
ret = -errno;
|
|
goto cleanup;
|
|
}
|
|
|
|
addrlen = sizeof(sa);
|
|
if (getsockname(sock, (struct sockaddr *)&sa, &addrlen) < 0) {
|
|
ret = -errno;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (addrlen != sizeof(sa)) {
|
|
ret = -LIBBPF_ERRNO__INTERNAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
*nl_pid = sa.nl_pid;
|
|
return sock;
|
|
|
|
cleanup:
|
|
close(sock);
|
|
return ret;
|
|
}
|
|
|
|
static void libbpf_netlink_close(int sock)
|
|
{
|
|
close(sock);
|
|
}
|
|
|
|
enum {
|
|
NL_CONT,
|
|
NL_NEXT,
|
|
NL_DONE,
|
|
};
|
|
|
|
static int libbpf_netlink_recv(int sock, __u32 nl_pid, int seq,
|
|
__dump_nlmsg_t _fn, libbpf_dump_nlmsg_t fn,
|
|
void *cookie)
|
|
{
|
|
bool multipart = true;
|
|
struct nlmsgerr *err;
|
|
struct nlmsghdr *nh;
|
|
char buf[4096];
|
|
int len, ret;
|
|
|
|
while (multipart) {
|
|
start:
|
|
multipart = false;
|
|
len = recv(sock, buf, sizeof(buf), 0);
|
|
if (len < 0) {
|
|
ret = -errno;
|
|
goto done;
|
|
}
|
|
|
|
if (len == 0)
|
|
break;
|
|
|
|
for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len);
|
|
nh = NLMSG_NEXT(nh, len)) {
|
|
if (nh->nlmsg_pid != nl_pid) {
|
|
ret = -LIBBPF_ERRNO__WRNGPID;
|
|
goto done;
|
|
}
|
|
if (nh->nlmsg_seq != seq) {
|
|
ret = -LIBBPF_ERRNO__INVSEQ;
|
|
goto done;
|
|
}
|
|
if (nh->nlmsg_flags & NLM_F_MULTI)
|
|
multipart = true;
|
|
switch (nh->nlmsg_type) {
|
|
case NLMSG_ERROR:
|
|
err = (struct nlmsgerr *)NLMSG_DATA(nh);
|
|
if (!err->error)
|
|
continue;
|
|
ret = err->error;
|
|
libbpf_nla_dump_errormsg(nh);
|
|
goto done;
|
|
case NLMSG_DONE:
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
if (_fn) {
|
|
ret = _fn(nh, fn, cookie);
|
|
switch (ret) {
|
|
case NL_CONT:
|
|
break;
|
|
case NL_NEXT:
|
|
goto start;
|
|
case NL_DONE:
|
|
return 0;
|
|
default:
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ret = 0;
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static int libbpf_netlink_send_recv(struct libbpf_nla_req *req,
|
|
__dump_nlmsg_t parse_msg,
|
|
libbpf_dump_nlmsg_t parse_attr,
|
|
void *cookie)
|
|
{
|
|
__u32 nl_pid = 0;
|
|
int sock, ret;
|
|
|
|
sock = libbpf_netlink_open(&nl_pid);
|
|
if (sock < 0)
|
|
return sock;
|
|
|
|
req->nh.nlmsg_pid = 0;
|
|
req->nh.nlmsg_seq = time(NULL);
|
|
|
|
if (send(sock, req, req->nh.nlmsg_len, 0) < 0) {
|
|
ret = -errno;
|
|
goto out;
|
|
}
|
|
|
|
ret = libbpf_netlink_recv(sock, nl_pid, req->nh.nlmsg_seq,
|
|
parse_msg, parse_attr, cookie);
|
|
out:
|
|
libbpf_netlink_close(sock);
|
|
return ret;
|
|
}
|
|
|
|
static int __bpf_set_link_xdp_fd_replace(int ifindex, int fd, int old_fd,
|
|
__u32 flags)
|
|
{
|
|
struct nlattr *nla;
|
|
int ret;
|
|
struct libbpf_nla_req req;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
|
|
req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
req.nh.nlmsg_type = RTM_SETLINK;
|
|
req.ifinfo.ifi_family = AF_UNSPEC;
|
|
req.ifinfo.ifi_index = ifindex;
|
|
|
|
nla = nlattr_begin_nested(&req, IFLA_XDP);
|
|
if (!nla)
|
|
return -EMSGSIZE;
|
|
ret = nlattr_add(&req, IFLA_XDP_FD, &fd, sizeof(fd));
|
|
if (ret < 0)
|
|
return ret;
|
|
if (flags) {
|
|
ret = nlattr_add(&req, IFLA_XDP_FLAGS, &flags, sizeof(flags));
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
if (flags & XDP_FLAGS_REPLACE) {
|
|
ret = nlattr_add(&req, IFLA_XDP_EXPECTED_FD, &old_fd,
|
|
sizeof(old_fd));
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
nlattr_end_nested(&req, nla);
|
|
|
|
return libbpf_netlink_send_recv(&req, NULL, NULL, NULL);
|
|
}
|
|
|
|
int bpf_xdp_attach(int ifindex, int prog_fd, __u32 flags, const struct bpf_xdp_attach_opts *opts)
|
|
{
|
|
int old_prog_fd, err;
|
|
|
|
if (!OPTS_VALID(opts, bpf_xdp_attach_opts))
|
|
return libbpf_err(-EINVAL);
|
|
|
|
old_prog_fd = OPTS_GET(opts, old_prog_fd, 0);
|
|
if (old_prog_fd)
|
|
flags |= XDP_FLAGS_REPLACE;
|
|
else
|
|
old_prog_fd = -1;
|
|
|
|
err = __bpf_set_link_xdp_fd_replace(ifindex, prog_fd, old_prog_fd, flags);
|
|
return libbpf_err(err);
|
|
}
|
|
|
|
int bpf_xdp_detach(int ifindex, __u32 flags, const struct bpf_xdp_attach_opts *opts)
|
|
{
|
|
return bpf_xdp_attach(ifindex, -1, flags, opts);
|
|
}
|
|
|
|
int bpf_set_link_xdp_fd_opts(int ifindex, int fd, __u32 flags,
|
|
const struct bpf_xdp_set_link_opts *opts)
|
|
{
|
|
int old_fd = -1, ret;
|
|
|
|
if (!OPTS_VALID(opts, bpf_xdp_set_link_opts))
|
|
return libbpf_err(-EINVAL);
|
|
|
|
if (OPTS_HAS(opts, old_fd)) {
|
|
old_fd = OPTS_GET(opts, old_fd, -1);
|
|
flags |= XDP_FLAGS_REPLACE;
|
|
}
|
|
|
|
ret = __bpf_set_link_xdp_fd_replace(ifindex, fd, old_fd, flags);
|
|
return libbpf_err(ret);
|
|
}
|
|
|
|
int bpf_set_link_xdp_fd(int ifindex, int fd, __u32 flags)
|
|
{
|
|
int ret;
|
|
|
|
ret = __bpf_set_link_xdp_fd_replace(ifindex, fd, 0, flags);
|
|
return libbpf_err(ret);
|
|
}
|
|
|
|
static int __dump_link_nlmsg(struct nlmsghdr *nlh,
|
|
libbpf_dump_nlmsg_t dump_link_nlmsg, void *cookie)
|
|
{
|
|
struct nlattr *tb[IFLA_MAX + 1], *attr;
|
|
struct ifinfomsg *ifi = NLMSG_DATA(nlh);
|
|
int len;
|
|
|
|
len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
|
|
attr = (struct nlattr *) ((void *) ifi + NLMSG_ALIGN(sizeof(*ifi)));
|
|
|
|
if (libbpf_nla_parse(tb, IFLA_MAX, attr, len, NULL) != 0)
|
|
return -LIBBPF_ERRNO__NLPARSE;
|
|
|
|
return dump_link_nlmsg(cookie, ifi, tb);
|
|
}
|
|
|
|
static int get_xdp_info(void *cookie, void *msg, struct nlattr **tb)
|
|
{
|
|
struct nlattr *xdp_tb[IFLA_XDP_MAX + 1];
|
|
struct xdp_id_md *xdp_id = cookie;
|
|
struct ifinfomsg *ifinfo = msg;
|
|
int ret;
|
|
|
|
if (xdp_id->ifindex && xdp_id->ifindex != ifinfo->ifi_index)
|
|
return 0;
|
|
|
|
if (!tb[IFLA_XDP])
|
|
return 0;
|
|
|
|
ret = libbpf_nla_parse_nested(xdp_tb, IFLA_XDP_MAX, tb[IFLA_XDP], NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!xdp_tb[IFLA_XDP_ATTACHED])
|
|
return 0;
|
|
|
|
xdp_id->info.attach_mode = libbpf_nla_getattr_u8(
|
|
xdp_tb[IFLA_XDP_ATTACHED]);
|
|
|
|
if (xdp_id->info.attach_mode == XDP_ATTACHED_NONE)
|
|
return 0;
|
|
|
|
if (xdp_tb[IFLA_XDP_PROG_ID])
|
|
xdp_id->info.prog_id = libbpf_nla_getattr_u32(
|
|
xdp_tb[IFLA_XDP_PROG_ID]);
|
|
|
|
if (xdp_tb[IFLA_XDP_SKB_PROG_ID])
|
|
xdp_id->info.skb_prog_id = libbpf_nla_getattr_u32(
|
|
xdp_tb[IFLA_XDP_SKB_PROG_ID]);
|
|
|
|
if (xdp_tb[IFLA_XDP_DRV_PROG_ID])
|
|
xdp_id->info.drv_prog_id = libbpf_nla_getattr_u32(
|
|
xdp_tb[IFLA_XDP_DRV_PROG_ID]);
|
|
|
|
if (xdp_tb[IFLA_XDP_HW_PROG_ID])
|
|
xdp_id->info.hw_prog_id = libbpf_nla_getattr_u32(
|
|
xdp_tb[IFLA_XDP_HW_PROG_ID]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bpf_xdp_query(int ifindex, int xdp_flags, struct bpf_xdp_query_opts *opts)
|
|
{
|
|
struct libbpf_nla_req req = {
|
|
.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
|
|
.nh.nlmsg_type = RTM_GETLINK,
|
|
.nh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
|
|
.ifinfo.ifi_family = AF_PACKET,
|
|
};
|
|
struct xdp_id_md xdp_id = {};
|
|
int err;
|
|
|
|
if (!OPTS_VALID(opts, bpf_xdp_query_opts))
|
|
return libbpf_err(-EINVAL);
|
|
|
|
if (xdp_flags & ~XDP_FLAGS_MASK)
|
|
return libbpf_err(-EINVAL);
|
|
|
|
/* Check whether the single {HW,DRV,SKB} mode is set */
|
|
xdp_flags &= XDP_FLAGS_SKB_MODE | XDP_FLAGS_DRV_MODE | XDP_FLAGS_HW_MODE;
|
|
if (xdp_flags & (xdp_flags - 1))
|
|
return libbpf_err(-EINVAL);
|
|
|
|
xdp_id.ifindex = ifindex;
|
|
xdp_id.flags = xdp_flags;
|
|
|
|
err = libbpf_netlink_send_recv(&req, __dump_link_nlmsg,
|
|
get_xdp_info, &xdp_id);
|
|
if (err)
|
|
return libbpf_err(err);
|
|
|
|
OPTS_SET(opts, prog_id, xdp_id.info.prog_id);
|
|
OPTS_SET(opts, drv_prog_id, xdp_id.info.drv_prog_id);
|
|
OPTS_SET(opts, hw_prog_id, xdp_id.info.hw_prog_id);
|
|
OPTS_SET(opts, skb_prog_id, xdp_id.info.skb_prog_id);
|
|
OPTS_SET(opts, attach_mode, xdp_id.info.attach_mode);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bpf_get_link_xdp_info(int ifindex, struct xdp_link_info *info,
|
|
size_t info_size, __u32 flags)
|
|
{
|
|
LIBBPF_OPTS(bpf_xdp_query_opts, opts);
|
|
size_t sz;
|
|
int err;
|
|
|
|
if (!info_size)
|
|
return libbpf_err(-EINVAL);
|
|
|
|
err = bpf_xdp_query(ifindex, flags, &opts);
|
|
if (err)
|
|
return libbpf_err(err);
|
|
|
|
/* struct xdp_link_info field layout matches struct bpf_xdp_query_opts
|
|
* layout after sz field
|
|
*/
|
|
sz = min(info_size, offsetofend(struct xdp_link_info, attach_mode));
|
|
memcpy(info, &opts.prog_id, sz);
|
|
memset((void *)info + sz, 0, info_size - sz);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bpf_xdp_query_id(int ifindex, int flags, __u32 *prog_id)
|
|
{
|
|
LIBBPF_OPTS(bpf_xdp_query_opts, opts);
|
|
int ret;
|
|
|
|
ret = bpf_xdp_query(ifindex, flags, &opts);
|
|
if (ret)
|
|
return libbpf_err(ret);
|
|
|
|
flags &= XDP_FLAGS_MODES;
|
|
|
|
if (opts.attach_mode != XDP_ATTACHED_MULTI && !flags)
|
|
*prog_id = opts.prog_id;
|
|
else if (flags & XDP_FLAGS_DRV_MODE)
|
|
*prog_id = opts.drv_prog_id;
|
|
else if (flags & XDP_FLAGS_HW_MODE)
|
|
*prog_id = opts.hw_prog_id;
|
|
else if (flags & XDP_FLAGS_SKB_MODE)
|
|
*prog_id = opts.skb_prog_id;
|
|
else
|
|
*prog_id = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int bpf_get_link_xdp_id(int ifindex, __u32 *prog_id, __u32 flags)
|
|
{
|
|
return bpf_xdp_query_id(ifindex, flags, prog_id);
|
|
}
|
|
|
|
typedef int (*qdisc_config_t)(struct libbpf_nla_req *req);
|
|
|
|
static int clsact_config(struct libbpf_nla_req *req)
|
|
{
|
|
req->tc.tcm_parent = TC_H_CLSACT;
|
|
req->tc.tcm_handle = TC_H_MAKE(TC_H_CLSACT, 0);
|
|
|
|
return nlattr_add(req, TCA_KIND, "clsact", sizeof("clsact"));
|
|
}
|
|
|
|
static int attach_point_to_config(struct bpf_tc_hook *hook,
|
|
qdisc_config_t *config)
|
|
{
|
|
switch (OPTS_GET(hook, attach_point, 0)) {
|
|
case BPF_TC_INGRESS:
|
|
case BPF_TC_EGRESS:
|
|
case BPF_TC_INGRESS | BPF_TC_EGRESS:
|
|
if (OPTS_GET(hook, parent, 0))
|
|
return -EINVAL;
|
|
*config = &clsact_config;
|
|
return 0;
|
|
case BPF_TC_CUSTOM:
|
|
return -EOPNOTSUPP;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int tc_get_tcm_parent(enum bpf_tc_attach_point attach_point,
|
|
__u32 *parent)
|
|
{
|
|
switch (attach_point) {
|
|
case BPF_TC_INGRESS:
|
|
case BPF_TC_EGRESS:
|
|
if (*parent)
|
|
return -EINVAL;
|
|
*parent = TC_H_MAKE(TC_H_CLSACT,
|
|
attach_point == BPF_TC_INGRESS ?
|
|
TC_H_MIN_INGRESS : TC_H_MIN_EGRESS);
|
|
break;
|
|
case BPF_TC_CUSTOM:
|
|
if (!*parent)
|
|
return -EINVAL;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int tc_qdisc_modify(struct bpf_tc_hook *hook, int cmd, int flags)
|
|
{
|
|
qdisc_config_t config;
|
|
int ret;
|
|
struct libbpf_nla_req req;
|
|
|
|
ret = attach_point_to_config(hook, &config);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
|
|
req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags;
|
|
req.nh.nlmsg_type = cmd;
|
|
req.tc.tcm_family = AF_UNSPEC;
|
|
req.tc.tcm_ifindex = OPTS_GET(hook, ifindex, 0);
|
|
|
|
ret = config(&req);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return libbpf_netlink_send_recv(&req, NULL, NULL, NULL);
|
|
}
|
|
|
|
static int tc_qdisc_create_excl(struct bpf_tc_hook *hook)
|
|
{
|
|
return tc_qdisc_modify(hook, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_EXCL);
|
|
}
|
|
|
|
static int tc_qdisc_delete(struct bpf_tc_hook *hook)
|
|
{
|
|
return tc_qdisc_modify(hook, RTM_DELQDISC, 0);
|
|
}
|
|
|
|
int bpf_tc_hook_create(struct bpf_tc_hook *hook)
|
|
{
|
|
int ret;
|
|
|
|
if (!hook || !OPTS_VALID(hook, bpf_tc_hook) ||
|
|
OPTS_GET(hook, ifindex, 0) <= 0)
|
|
return libbpf_err(-EINVAL);
|
|
|
|
ret = tc_qdisc_create_excl(hook);
|
|
return libbpf_err(ret);
|
|
}
|
|
|
|
static int __bpf_tc_detach(const struct bpf_tc_hook *hook,
|
|
const struct bpf_tc_opts *opts,
|
|
const bool flush);
|
|
|
|
int bpf_tc_hook_destroy(struct bpf_tc_hook *hook)
|
|
{
|
|
if (!hook || !OPTS_VALID(hook, bpf_tc_hook) ||
|
|
OPTS_GET(hook, ifindex, 0) <= 0)
|
|
return libbpf_err(-EINVAL);
|
|
|
|
switch (OPTS_GET(hook, attach_point, 0)) {
|
|
case BPF_TC_INGRESS:
|
|
case BPF_TC_EGRESS:
|
|
return libbpf_err(__bpf_tc_detach(hook, NULL, true));
|
|
case BPF_TC_INGRESS | BPF_TC_EGRESS:
|
|
return libbpf_err(tc_qdisc_delete(hook));
|
|
case BPF_TC_CUSTOM:
|
|
return libbpf_err(-EOPNOTSUPP);
|
|
default:
|
|
return libbpf_err(-EINVAL);
|
|
}
|
|
}
|
|
|
|
struct bpf_cb_ctx {
|
|
struct bpf_tc_opts *opts;
|
|
bool processed;
|
|
};
|
|
|
|
static int __get_tc_info(void *cookie, struct tcmsg *tc, struct nlattr **tb,
|
|
bool unicast)
|
|
{
|
|
struct nlattr *tbb[TCA_BPF_MAX + 1];
|
|
struct bpf_cb_ctx *info = cookie;
|
|
|
|
if (!info || !info->opts)
|
|
return -EINVAL;
|
|
if (unicast && info->processed)
|
|
return -EINVAL;
|
|
if (!tb[TCA_OPTIONS])
|
|
return NL_CONT;
|
|
|
|
libbpf_nla_parse_nested(tbb, TCA_BPF_MAX, tb[TCA_OPTIONS], NULL);
|
|
if (!tbb[TCA_BPF_ID])
|
|
return -EINVAL;
|
|
|
|
OPTS_SET(info->opts, prog_id, libbpf_nla_getattr_u32(tbb[TCA_BPF_ID]));
|
|
OPTS_SET(info->opts, handle, tc->tcm_handle);
|
|
OPTS_SET(info->opts, priority, TC_H_MAJ(tc->tcm_info) >> 16);
|
|
|
|
info->processed = true;
|
|
return unicast ? NL_NEXT : NL_DONE;
|
|
}
|
|
|
|
static int get_tc_info(struct nlmsghdr *nh, libbpf_dump_nlmsg_t fn,
|
|
void *cookie)
|
|
{
|
|
struct tcmsg *tc = NLMSG_DATA(nh);
|
|
struct nlattr *tb[TCA_MAX + 1];
|
|
|
|
libbpf_nla_parse(tb, TCA_MAX,
|
|
(struct nlattr *)((void *)tc + NLMSG_ALIGN(sizeof(*tc))),
|
|
NLMSG_PAYLOAD(nh, sizeof(*tc)), NULL);
|
|
if (!tb[TCA_KIND])
|
|
return NL_CONT;
|
|
return __get_tc_info(cookie, tc, tb, nh->nlmsg_flags & NLM_F_ECHO);
|
|
}
|
|
|
|
static int tc_add_fd_and_name(struct libbpf_nla_req *req, int fd)
|
|
{
|
|
struct bpf_prog_info info = {};
|
|
__u32 info_len = sizeof(info);
|
|
char name[256];
|
|
int len, ret;
|
|
|
|
ret = bpf_obj_get_info_by_fd(fd, &info, &info_len);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = nlattr_add(req, TCA_BPF_FD, &fd, sizeof(fd));
|
|
if (ret < 0)
|
|
return ret;
|
|
len = snprintf(name, sizeof(name), "%s:[%u]", info.name, info.id);
|
|
if (len < 0)
|
|
return -errno;
|
|
if (len >= sizeof(name))
|
|
return -ENAMETOOLONG;
|
|
return nlattr_add(req, TCA_BPF_NAME, name, len + 1);
|
|
}
|
|
|
|
int bpf_tc_attach(const struct bpf_tc_hook *hook, struct bpf_tc_opts *opts)
|
|
{
|
|
__u32 protocol, bpf_flags, handle, priority, parent, prog_id, flags;
|
|
int ret, ifindex, attach_point, prog_fd;
|
|
struct bpf_cb_ctx info = {};
|
|
struct libbpf_nla_req req;
|
|
struct nlattr *nla;
|
|
|
|
if (!hook || !opts ||
|
|
!OPTS_VALID(hook, bpf_tc_hook) ||
|
|
!OPTS_VALID(opts, bpf_tc_opts))
|
|
return libbpf_err(-EINVAL);
|
|
|
|
ifindex = OPTS_GET(hook, ifindex, 0);
|
|
parent = OPTS_GET(hook, parent, 0);
|
|
attach_point = OPTS_GET(hook, attach_point, 0);
|
|
|
|
handle = OPTS_GET(opts, handle, 0);
|
|
priority = OPTS_GET(opts, priority, 0);
|
|
prog_fd = OPTS_GET(opts, prog_fd, 0);
|
|
prog_id = OPTS_GET(opts, prog_id, 0);
|
|
flags = OPTS_GET(opts, flags, 0);
|
|
|
|
if (ifindex <= 0 || !prog_fd || prog_id)
|
|
return libbpf_err(-EINVAL);
|
|
if (priority > UINT16_MAX)
|
|
return libbpf_err(-EINVAL);
|
|
if (flags & ~BPF_TC_F_REPLACE)
|
|
return libbpf_err(-EINVAL);
|
|
|
|
flags = (flags & BPF_TC_F_REPLACE) ? NLM_F_REPLACE : NLM_F_EXCL;
|
|
protocol = ETH_P_ALL;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
|
|
req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
|
|
NLM_F_ECHO | flags;
|
|
req.nh.nlmsg_type = RTM_NEWTFILTER;
|
|
req.tc.tcm_family = AF_UNSPEC;
|
|
req.tc.tcm_ifindex = ifindex;
|
|
req.tc.tcm_handle = handle;
|
|
req.tc.tcm_info = TC_H_MAKE(priority << 16, htons(protocol));
|
|
|
|
ret = tc_get_tcm_parent(attach_point, &parent);
|
|
if (ret < 0)
|
|
return libbpf_err(ret);
|
|
req.tc.tcm_parent = parent;
|
|
|
|
ret = nlattr_add(&req, TCA_KIND, "bpf", sizeof("bpf"));
|
|
if (ret < 0)
|
|
return libbpf_err(ret);
|
|
nla = nlattr_begin_nested(&req, TCA_OPTIONS);
|
|
if (!nla)
|
|
return libbpf_err(-EMSGSIZE);
|
|
ret = tc_add_fd_and_name(&req, prog_fd);
|
|
if (ret < 0)
|
|
return libbpf_err(ret);
|
|
bpf_flags = TCA_BPF_FLAG_ACT_DIRECT;
|
|
ret = nlattr_add(&req, TCA_BPF_FLAGS, &bpf_flags, sizeof(bpf_flags));
|
|
if (ret < 0)
|
|
return libbpf_err(ret);
|
|
nlattr_end_nested(&req, nla);
|
|
|
|
info.opts = opts;
|
|
|
|
ret = libbpf_netlink_send_recv(&req, get_tc_info, NULL, &info);
|
|
if (ret < 0)
|
|
return libbpf_err(ret);
|
|
if (!info.processed)
|
|
return libbpf_err(-ENOENT);
|
|
return ret;
|
|
}
|
|
|
|
static int __bpf_tc_detach(const struct bpf_tc_hook *hook,
|
|
const struct bpf_tc_opts *opts,
|
|
const bool flush)
|
|
{
|
|
__u32 protocol = 0, handle, priority, parent, prog_id, flags;
|
|
int ret, ifindex, attach_point, prog_fd;
|
|
struct libbpf_nla_req req;
|
|
|
|
if (!hook ||
|
|
!OPTS_VALID(hook, bpf_tc_hook) ||
|
|
!OPTS_VALID(opts, bpf_tc_opts))
|
|
return -EINVAL;
|
|
|
|
ifindex = OPTS_GET(hook, ifindex, 0);
|
|
parent = OPTS_GET(hook, parent, 0);
|
|
attach_point = OPTS_GET(hook, attach_point, 0);
|
|
|
|
handle = OPTS_GET(opts, handle, 0);
|
|
priority = OPTS_GET(opts, priority, 0);
|
|
prog_fd = OPTS_GET(opts, prog_fd, 0);
|
|
prog_id = OPTS_GET(opts, prog_id, 0);
|
|
flags = OPTS_GET(opts, flags, 0);
|
|
|
|
if (ifindex <= 0 || flags || prog_fd || prog_id)
|
|
return -EINVAL;
|
|
if (priority > UINT16_MAX)
|
|
return -EINVAL;
|
|
if (!flush) {
|
|
if (!handle || !priority)
|
|
return -EINVAL;
|
|
protocol = ETH_P_ALL;
|
|
} else {
|
|
if (handle || priority)
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
|
|
req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
|
|
req.nh.nlmsg_type = RTM_DELTFILTER;
|
|
req.tc.tcm_family = AF_UNSPEC;
|
|
req.tc.tcm_ifindex = ifindex;
|
|
if (!flush) {
|
|
req.tc.tcm_handle = handle;
|
|
req.tc.tcm_info = TC_H_MAKE(priority << 16, htons(protocol));
|
|
}
|
|
|
|
ret = tc_get_tcm_parent(attach_point, &parent);
|
|
if (ret < 0)
|
|
return ret;
|
|
req.tc.tcm_parent = parent;
|
|
|
|
if (!flush) {
|
|
ret = nlattr_add(&req, TCA_KIND, "bpf", sizeof("bpf"));
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
return libbpf_netlink_send_recv(&req, NULL, NULL, NULL);
|
|
}
|
|
|
|
int bpf_tc_detach(const struct bpf_tc_hook *hook,
|
|
const struct bpf_tc_opts *opts)
|
|
{
|
|
int ret;
|
|
|
|
if (!opts)
|
|
return libbpf_err(-EINVAL);
|
|
|
|
ret = __bpf_tc_detach(hook, opts, false);
|
|
return libbpf_err(ret);
|
|
}
|
|
|
|
int bpf_tc_query(const struct bpf_tc_hook *hook, struct bpf_tc_opts *opts)
|
|
{
|
|
__u32 protocol, handle, priority, parent, prog_id, flags;
|
|
int ret, ifindex, attach_point, prog_fd;
|
|
struct bpf_cb_ctx info = {};
|
|
struct libbpf_nla_req req;
|
|
|
|
if (!hook || !opts ||
|
|
!OPTS_VALID(hook, bpf_tc_hook) ||
|
|
!OPTS_VALID(opts, bpf_tc_opts))
|
|
return libbpf_err(-EINVAL);
|
|
|
|
ifindex = OPTS_GET(hook, ifindex, 0);
|
|
parent = OPTS_GET(hook, parent, 0);
|
|
attach_point = OPTS_GET(hook, attach_point, 0);
|
|
|
|
handle = OPTS_GET(opts, handle, 0);
|
|
priority = OPTS_GET(opts, priority, 0);
|
|
prog_fd = OPTS_GET(opts, prog_fd, 0);
|
|
prog_id = OPTS_GET(opts, prog_id, 0);
|
|
flags = OPTS_GET(opts, flags, 0);
|
|
|
|
if (ifindex <= 0 || flags || prog_fd || prog_id ||
|
|
!handle || !priority)
|
|
return libbpf_err(-EINVAL);
|
|
if (priority > UINT16_MAX)
|
|
return libbpf_err(-EINVAL);
|
|
|
|
protocol = ETH_P_ALL;
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
|
|
req.nh.nlmsg_flags = NLM_F_REQUEST;
|
|
req.nh.nlmsg_type = RTM_GETTFILTER;
|
|
req.tc.tcm_family = AF_UNSPEC;
|
|
req.tc.tcm_ifindex = ifindex;
|
|
req.tc.tcm_handle = handle;
|
|
req.tc.tcm_info = TC_H_MAKE(priority << 16, htons(protocol));
|
|
|
|
ret = tc_get_tcm_parent(attach_point, &parent);
|
|
if (ret < 0)
|
|
return libbpf_err(ret);
|
|
req.tc.tcm_parent = parent;
|
|
|
|
ret = nlattr_add(&req, TCA_KIND, "bpf", sizeof("bpf"));
|
|
if (ret < 0)
|
|
return libbpf_err(ret);
|
|
|
|
info.opts = opts;
|
|
|
|
ret = libbpf_netlink_send_recv(&req, get_tc_info, NULL, &info);
|
|
if (ret < 0)
|
|
return libbpf_err(ret);
|
|
if (!info.processed)
|
|
return libbpf_err(-ENOENT);
|
|
return ret;
|
|
}
|