bpf_xdp_attach
int bpf_xdp_attach(int ifindex, int prog_fd, __u32 flags, const struct bpf_xdp_attach_opts *opts)
{
int old_prog_fd, err;
//首先检查一下对应的flags是否合法
if (!OPTS_VALID(opts, bpf_xdp_attach_opts))
return libbpf_err(-EINVAL);
//从opts中获取fd值,
//如果old_prog_fd不为空,设置标志位位replace,替换掉原来的程序
//否则将old_prog_fd设置为-1
old_prog_fd = OPTS_GET(opts, old_prog_fd, 0);
if (old_prog_fd)
flags |= XDP_FLAGS_REPLACE;
else
old_prog_fd = -1;
//将BPF程序附加到指定的网络接口上
err = __bpf_set_link_xdp_fd_replace(ifindex, prog_fd, old_prog_fd, flags);
return libbpf_err(err);
}
首先我们来看一下对应的:
struct bpf_xdp_attach_opts {
size_t sz;
int old_prog_fd;
size_t :0;
};
我们看一下如何使用上面这个函数, 这个函数的第一个参数是ifindex, -1表示对应的
bpf_xdp_attach(ifindex, -1, flags, opts);
关闭对应的程序- 我们可以看一下对应的flags
#define XDP_FLAGS_UPDATE_IF_NOEXIST (1U << 0)
#define XDP_FLAGS_SKB_MODE (1U << 1)
#define XDP_FLAGS_DRV_MODE (1U << 2)
#define XDP_FLAGS_HW_MODE (1U << 3)
#define XDP_FLAGS_REPLACE (1U << 4)
XDP_FLAGS_UPDATE_IF_NOEXIST
:如果设置此标志,并且网络接口上没有已附加的XDP程序,则更新该接口的XDP程序。XDP_FLAGS_SKB_MODE
:设置此标志表示XDP程序将在skb(socket buffer)模式下运行,这是XDP的传统运行模式。XDP_FLAGS_DRV_MODE
:设置此标志表示XDP程序将在驱动模式下运行,这通常用于某些特定的硬件或驱动程序。XDP_FLAGS_HW_MODE
:设置此标志表示XDP程序将在硬件模式下运行,这要求硬件支持XDP。XDP_FLAGS_REPLACE
:设置此标志表示如果网络接口上已经有一个XDP程序,那么新的XDP程序将替换它。
然后是这个函数 __bpf_set_link_xdp_fd_replace(ifindex, prog_fd, old_prog_fd, flags)
挂载对应的程序
然后是下面这个函数,我们也来分析一下,函数参数我们在之前已经说的的比较清晰了, 下面这个函数最终会调用一个libbpf_netlink_send_recv
函数, 这个函数是通过netlink这个套接字来完成对应的功能的, 那么什么东西是net link呢, net link是一个提供用户空间进程与内核之间的通信方法, 这种方式是比ioctl要好很多的,
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, NETLINK_ROUTE, NULL, NULL, NULL);
}
我们的程序开始之前, 我们会初始化一下请求结构体, 在这个过程中我们会设置Netlink消息头的字段,包括消息长度、标志、类型和地址族,然后使用一下下面这两个方法:
//辅助函数
static inline struct nlattr *req_tail(struct libbpf_nla_req *req)
{
return (struct nlattr *)((void *)req + NLMSG_ALIGN(req->nh.nlmsg_len));
}
static inline int nlattr_add(struct libbpf_nla_req *req, int type,
const void *data, int len)
{
struct nlattr *nla;
if (NLMSG_ALIGN(req->nh.nlmsg_len) + NLA_ALIGN(NLA_HDRLEN + len) > sizeof(*req))
return -EMSGSIZE;
if (!!data != !!len)
return -EINVAL;
nla = req_tail(req);
nla->nla_type = type;
nla->nla_len = NLA_HDRLEN + len;
if (data)
memcpy(nla_data(nla), data, len);
req->nh.nlmsg_len = NLMSG_ALIGN(req->nh.nlmsg_len) + NLA_ALIGN(nla->nla_len);
return 0;
}
/*
取请求结构体尾部的指针tail。
调用nlattr_add函数添加一个带有NLA_F_NESTED标志的属性,
这表示开始一个嵌套属性。如果添加失败,返回NULL。
返回嵌套属性的尾部指针tail,后续属性将添加在这个嵌套属性内部。
*/
static inline struct nlattr *nlattr_begin_nested(struct libbpf_nla_req *req, int type)
{
struct nlattr *tail;
tail = req_tail(req);
if (nlattr_add(req, type | NLA_F_NESTED, NULL, 0))
return NULL;
return tail;
}
然后是对应下面这个函数:
static int libbpf_netlink_send_recv(struct libbpf_nla_req *req,
int proto, __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, proto);
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 libbpf_netlink_recv(int sock, __u32 nl_pid, int seq,
__dump_nlmsg_t _fn, libbpf_dump_nlmsg_t fn,
void *cookie)
{
struct iovec iov = {};
struct msghdr mhdr = {
.msg_iov = &iov,
.msg_iovlen = 1,
};
bool multipart = true;
struct nlmsgerr *err;
struct nlmsghdr *nh;
int len, ret;
ret = alloc_iov(&iov, 4096);
if (ret)
goto done;
while (multipart) {
start:
multipart = false;
len = netlink_recvmsg(sock, &mhdr, MSG_PEEK | MSG_TRUNC);
if (len < 0) {
ret = len;
goto done;
}
if (len > iov.iov_len) {
ret = alloc_iov(&iov, len);
if (ret)
goto done;
}
len = netlink_recvmsg(sock, &mhdr, 0);
if (len < 0) {
ret = len;
goto done;
}
if (len == 0)
break;
for (nh = (struct nlmsghdr *)iov.iov_base; 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:
ret = 0;
goto done;
default:
break;
}
if (_fn) {
ret = _fn(nh, fn, cookie);
switch (ret) {
case NL_CONT:
break;
case NL_NEXT:
goto start;
case NL_DONE:
ret = 0;
goto done;
default:
goto done;
}
}
}
}
ret = 0;
done:
free(iov.iov_base);
return ret;
}
这段代码是一个C语言函数,libbpf_netlink_recv
,用于接收通过Netlink套接字发送的消息。Netlink是Linux内核提供的一种IPC机制,用于内核与用户空间程序之间的通信。以下是函数的详细解释:
函数参数:
int sock
:Netlink套接字的文件描述符。__u32 nl_pid
:Netlink消息的源PID,用于匹配发送者。int seq
:Netlink消息的序列号,用于匹配请求和响应。__dump_nlmsg_t _fn
:一个函数指针,用于处理接收到的Netlink消息。libbpf_dump_nlmsg_t fn
:另一个函数指针,用于打印或处理Netlink消息。void *cookie
:传递给_fn
和fn
函数的自定义数据。
函数流程:
- 初始化
iovec
和msghdr
结构体,准备接收Netlink消息。 - 调用
alloc_iov
函数分配一个初始大小为4096字节的缓冲区。 - 进入循环,接收消息直到没有更多的消息或发生错误。
- 使用
netlink_recvmsg
函数尝试接收消息,首先使用MSG_PEEK | MSG_TRUNC
标志进行窥视,以确定消息的实际大小。 - 如果需要,重新分配更大的缓冲区。
- 再次使用
netlink_recvmsg
接收消息,这次不使用窥视标志。 - 检查接收到的消息,如果消息的PID或序列号不匹配,返回错误。
- 如果消息标志包含
NLM_F_MULTI
,则表示还有更多的消息需要接收,设置multipart
为true
。 - 根据消息类型处理消息:
NLMSG_ERROR
:表示发生了错误,打印错误消息并返回错误代码。NLMSG_DONE
:表示消息接收完成,返回0。- 其他类型:调用
_fn
函数处理消息。
- 如果
_fn
函数返回NL_NEXT
,重新启动循环以接收下一条消息。 - 处理完成后,释放分配的缓冲区并返回结果。
关键点:
netlink_recvmsg
:用于从Netlink套接字接收消息的函数。NLMSG_OK
:宏,用于检查Netlink消息是否有效。NLMSG_ERROR
和NLMSG_DONE
:Netlink消息类型,分别表示错误和完成。NLM_F_MULTI
:Netlink消息标志,表示消息是多部分的。__dump_nlmsg_t
和libbpf_dump_nlmsg_t
:函数类型,用于处理或打印Netlink消息。alloc_iov
和free
:用于分配和释放缓冲区的函数。
这个函数是内核编程和网络编程的一部分,通常用于处理来自内核的异步事件或响应。如果你需要进一步的帮助或有具体问题,请随时提问。