[eBPF] sockops类型运行梳理

[eBPF] sockops类型运行梳理

BPF_PROG_TYPE_SOCK_OPS程序类型

内核中socket事件(本质是各函数)中的埋点,能够在socket的生命周期每个节点执行BPF程序。

  • 进入Linux内核的commit
  • session定义SEC_DEF("sockops", SOCK_OPS, BPF_CGROUP_SOCK_OPS, SEC_ATTACHABLE_OPT)
  • 参数,由BPF_PROG_TYPE(BPF_PROG_TYPE_SOCK_OPS, sock_ops, struct bpf_sock_ops, struct bpf_sock_ops_kern)可知:bpf程序的入参是bpf_sock_ops,对应内核中的上下文是bpf_sock_ops_kern。
  • 使用场景:参考博客

示例代码

__section("sockops") // 加载到 ELF 中的 `sockops` 区域,有 socket operations 时触发执行
int bpf_sockmap(struct bpf_sock_ops *skops)
{
    switch (skops->op) {
        case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB: // 被动建连
        case BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB:  // 主动建连
            if (skops->family == 2) {             // AF_INET
                bpf_sock_ops_ipv4(skops);         // 将 socket 信息记录到到 sockmap
            }
            break;
        default:
            break;
    }
    return 0;
}

Hook位置梳理

  1. tcp_output.c,中核心函数int tcp_connect(struct sock *sk)一开始就埋了点:
// https://elixir.bootlin.com/linux/v6.6.4/source/net/ipv4/tcp_output.c#L3946
/* Build a SYN and send it off. */
int tcp_connect(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct sk_buff *buff;
	int err;

	tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_CONNECT_CB, 0, NULL);//此处埋点,去执行挂载在BPF_SOCK_OPS_TCP_CONNECT_CB下的bpf程序。

	if (inet_csk(sk)->icsk_af_ops->rebuild_header(sk))
		return -EHOSTUNREACH; /* Routing failure or similar. */
...
  1. BPF_SOCK_OPS_*宏:
    在源码中位置
    由源码中注释可知,这里是“已知 BPF sock_ops 运算符的列表”。
    在socket的阶段宏表示,可以在
    上面的BPF_SOCK_OPS_TCP_CONNECT_CB是“在初始化活动连接之前调用 BPF 程序”,符合直觉。
    由示例bpf程序可知,这个宏被包装在了上下文结构体中,op字段。在内核态ebpf程序中可以获取到。

  2. tcp_call_bpf(struct sock *sk, int op, u32 nargs, u32 *args)函数
    用于在内核socket处理链路中埋点。

// https://elixir.bootlin.com/linux/v6.6.4/source/include/net/tcp.h#L2409
static inline int tcp_call_bpf(struct sock *sk, int op, u32 nargs, u32 *args)
{
	struct bpf_sock_ops_kern sock_ops;
	int ret;

	memset(&sock_ops, 0, offsetof(struct bpf_sock_ops_kern, temp));
	if (sk_fullsock(sk)) {
		sock_ops.is_fullsock = 1;
		sock_owned_by_me(sk);
	}

	sock_ops.sk = sk;
	sock_ops.op = op;
	if (nargs > 0)
		memcpy(sock_ops.args, args, nargs * sizeof(*args));

	ret = BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);
	if (ret == 0)
		ret = sock_ops.reply;
	else
		ret = -1;
	return ret;
}

其核心逻辑是将上下文封装成bpf_sock_ops_kern结构体,再调用BPF_CGROUP_RUN_PROG_SOCK_OPS宏(顾名思义,运行挂载的SOCK_OPS程序)。
后续链路:

// https://elixir.bootlin.com/linux/v6.6.4/source/include/linux/bpf-cgroup.h#L328
#define BPF_CGROUP_RUN_PROG_SOCK_OPS(sock_ops)				       \
({									       \
	int __ret = 0;							       \
	if (cgroup_bpf_enabled(CGROUP_SOCK_OPS) && (sock_ops)->sk) {       \
		typeof(sk) __sk = sk_to_full_sk((sock_ops)->sk);	       \
		if (__sk && sk_fullsock(__sk))				       \
			__ret = __cgroup_bpf_run_filter_sock_ops(__sk,	       \
								 sock_ops,     \
							 CGROUP_SOCK_OPS); \
	}								       \
	__ret;								       \
})

// https://elixir.bootlin.com/linux/v6.6.4/source/kernel/bpf/cgroup.c#L1510
int __cgroup_bpf_run_filter_sock_ops(struct sock *sk,
				     struct bpf_sock_ops_kern *sock_ops,
				     enum cgroup_bpf_attach_type atype)
{
	struct cgroup *cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data);

	return bpf_prog_run_array_cg(&cgrp->bpf, atype, sock_ops, bpf_prog_run,
				     0, NULL);
}

// https://elixir.bootlin.com/linux/v6.6.4/source/kernel/bpf/cgroup.c#L31
static __always_inline int
bpf_prog_run_array_cg(const struct cgroup_bpf *cgrp,
		      enum cgroup_bpf_attach_type atype,
		      const void *ctx, bpf_prog_run_fn run_prog,
		      int retval, u32 *ret_flags)
{
	const struct bpf_prog_array_item *item;
	const struct bpf_prog *prog;
	const struct bpf_prog_array *array;
	struct bpf_run_ctx *old_run_ctx;
	struct bpf_cg_run_ctx run_ctx;
	u32 func_ret;

	run_ctx.retval = retval;
	migrate_disable();
	rcu_read_lock();
	array = rcu_dereference(cgrp->effective[atype]);
	item = &array->items[0];
	old_run_ctx = bpf_set_run_ctx(&run_ctx.run_ctx);
	while ((prog = READ_ONCE(item->prog))) {
		run_ctx.prog_item = item;
		func_ret = run_prog(prog, ctx);//!!!执行挂载在此处的ebpf程序
		if (ret_flags) {
			*(ret_flags) |= (func_ret >> 1);
			func_ret &= 1;
		}
		if (!func_ret && !IS_ERR_VALUE((long)run_ctx.retval))
			run_ctx.retval = -EPERM;
		item++;
	}
	bpf_reset_run_ctx(old_run_ctx);
	rcu_read_unlock();
	migrate_enable();
	return run_ctx.retval;
}

总结

BPF_PROG_TYPE_SOCK_OPS程序类型能够在socket处理流程中的关键路径上执行ebpf程序。

  • 基础:在内核源码中静态预埋点,在socket处理函数中调用tcp_call_bpf函数,最终执行挂载的bpf逻辑。
  • 内核对外透露出的信息(bpf程序的权限):tcp_call_bpf函数将上下文信息(sock结构体)封装成bpf_sock_ops_kern,最终透传出去,ebpf程序能够访问bpf_sock_ops中的字段。
  • 应用:可以重定向socket,绕过TCP/IP协议栈;修改socket参数(通过辅助函数)…
  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值