bpf_xdp_attach源码分析

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)
  1. XDP_FLAGS_UPDATE_IF_NOEXIST:如果设置此标志,并且网络接口上没有已附加的XDP程序,则更新该接口的XDP程序。
  2. XDP_FLAGS_SKB_MODE:设置此标志表示XDP程序将在skb(socket buffer)模式下运行,这是XDP的传统运行模式。
  3. XDP_FLAGS_DRV_MODE:设置此标志表示XDP程序将在驱动模式下运行,这通常用于某些特定的硬件或驱动程序。
  4. XDP_FLAGS_HW_MODE:设置此标志表示XDP程序将在硬件模式下运行,这要求硬件支持XDP。
  5. 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:传递给_fnfn函数的自定义数据。

函数流程:

  1. 初始化iovecmsghdr结构体,准备接收Netlink消息。
  2. 调用alloc_iov函数分配一个初始大小为4096字节的缓冲区。
  3. 进入循环,接收消息直到没有更多的消息或发生错误。
  4. 使用netlink_recvmsg函数尝试接收消息,首先使用MSG_PEEK | MSG_TRUNC标志进行窥视,以确定消息的实际大小。
  5. 如果需要,重新分配更大的缓冲区。
  6. 再次使用netlink_recvmsg接收消息,这次不使用窥视标志。
  7. 检查接收到的消息,如果消息的PID或序列号不匹配,返回错误。
  8. 如果消息标志包含NLM_F_MULTI,则表示还有更多的消息需要接收,设置multiparttrue
  9. 根据消息类型处理消息:
    • NLMSG_ERROR:表示发生了错误,打印错误消息并返回错误代码。
    • NLMSG_DONE:表示消息接收完成,返回0。
    • 其他类型:调用_fn函数处理消息。
  10. 如果_fn函数返回NL_NEXT,重新启动循环以接收下一条消息。
  11. 处理完成后,释放分配的缓冲区并返回结果。

关键点:

  • netlink_recvmsg:用于从Netlink套接字接收消息的函数。
  • NLMSG_OK:宏,用于检查Netlink消息是否有效。
  • NLMSG_ERRORNLMSG_DONE:Netlink消息类型,分别表示错误和完成。
  • NLM_F_MULTI:Netlink消息标志,表示消息是多部分的。
  • __dump_nlmsg_tlibbpf_dump_nlmsg_t:函数类型,用于处理或打印Netlink消息。
  • alloc_iovfree:用于分配和释放缓冲区的函数。

这个函数是内核编程和网络编程的一部分,通常用于处理来自内核的异步事件或响应。如果你需要进一步的帮助或有具体问题,请随时提问。

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值