ebpf sockops 代码解读

本文介绍了如何在TCP主流程中使用ebpf_sock_ops的tcp_call_bpf系列函数进行埋点,通过BPF_CGROUP_RUN_PROG_SOCK_OPS回调用户自定义的处理函数,实现对特定操作的监控和功能扩展。关键点包括sock_ops结构的初始化、参数传递和回调机制的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2032 static inline int tcp_call_bpf(struct sock *sk, int op, u32 nargs, u32 *args)
2033 {
2034         struct bpf_sock_ops_kern sock_ops;
2035         int ret;
2036
2037         memset(&sock_ops, 0, offsetof(struct bpf_sock_ops_kern, temp));
2038         if (sk_fullsock(sk)) {
2039                 sock_ops.is_fullsock = 1;
2040                 sock_owned_by_me(sk);
2041         }
2042
2043         sock_ops.sk = sk;
2044         sock_ops.op = op;
2045         if (nargs > 0)
2046                 memcpy(sock_ops.args, args, nargs * sizeof(*args));
2047
2048         ret = BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);
2049         if (ret == 0)
2050                 ret = sock_ops.reply;
2051         else
2052                 ret = -1;
2053         return ret;
2054 }
2055
2056 static inline int tcp_call_bpf_2arg(struct sock *sk, int op, u32 arg1, u32 arg2)
2057 {
2058         u32 args[2] = {arg1, arg2};
2059
2060         return tcp_call_bpf(sk, op, 2, args);
2061 }
2062
2063 static inline int tcp_call_bpf_3arg(struct sock *sk, int op, u32 arg1, u32 arg2,
2064                                     u32 arg3)
2065 {
2066         u32 args[3] = {arg1, arg2, arg3};
2067
2068         return tcp_call_bpf(sk, op, 3, args);
2069 }

ebpf sockops 的定义的埋点函数有三个:

tcp_call_bpf(struct sock *sk, int op, u32 nargs, u32 *args)

tcp_call_bpf_2arg(struct sock *sk, int op, u32 arg1, u32 arg2)

tcp_call_bpf_3arg(struct sock *sk, int op, u32 arg1, u32 arg2, u32 arg3)

在TCP的主路径流程中调用这个三个函数埋点,然后用户自己定义的处理函数挂载道相关的链表上,最终在这三个函数中遍历用户的定义的函数,最终实现功能。

tcp_call_bpf_2arg / tcp_call_bpf_3arg 参数个数有差异,最终参数封装为struct bpf_sock_ops_kern结构,传递给回调函数。

2032 static inline int tcp_call_bpf(struct sock *sk, int op, u32 nargs, u32 *args)
2033 {
2034         struct bpf_sock_ops_kern sock_ops;
2035         int ret;
2036
2037         memset(&sock_ops, 0, offsetof(struct bpf_sock_ops_kern, temp));
2038         if (sk_fullsock(sk)) {
2039                 sock_ops.is_fullsock = 1;
2040                 sock_owned_by_me(sk);
2041         }
2042
2043         sock_ops.sk = sk;
2044         sock_ops.op = op;
2045         if (nargs > 0)
2046                 memcpy(sock_ops.args, args, nargs * sizeof(*args));
2047
2048         ret = BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);
2049         if (ret == 0)
2050                 ret = sock_ops.reply;
2051         else
2052                 ret = -1;
2053         return ret;
2054 }

sock_ops是参数结构,2043、2044初始化记录了sk/op两个参数,2046则是拷贝了多个参数,最多支持四个参数。

2048行,BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops)遍历回调函数,同时将sock_ops作为参数传递给回调函数。

169 #define BPF_CGROUP_RUN_PROG_SOCK_OPS(sock_ops)                                 \
170 ({                                                                             \
171         int __ret = 0;                                                         \
172         if (cgroup_bpf_enabled && (sock_ops)->sk) {            \
173                 typeof(sk) __sk = sk_to_full_sk((sock_ops)->sk);               \
174                 if (__sk && sk_fullsock(__sk))                                 \
175                         __ret = __cgroup_bpf_run_filter_sock_ops(__sk,         \
176                                                                  sock_ops,     \
177                                                          BPF_CGROUP_SOCK_OPS); \
178         }                                                                      \
179         __ret;                                                                 \
180 })


612 int __cgroup_bpf_run_filter_sock_ops(struct sock *sk,
613                                      struct bpf_sock_ops_kern *sock_ops,
614                                      enum bpf_attach_type type)
615 {
616         struct cgroup *cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data);
617         int ret;
618
619         ret = BPF_PROG_RUN_ARRAY(cgrp->bpf.effective[type], sock_ops,
620                                  BPF_PROG_RUN);
621         return ret == 1 ? 0 : -EPERM;
622 }

392 #define BPF_PROG_RUN_ARRAY(array, ctx, func)            \
393         __BPF_PROG_RUN_ARRAY(array, ctx, func, false)


370
371 #define __BPF_PROG_RUN_ARRAY(array, ctx, func, check_non_null)  \
372         ({                                              \
373                 struct bpf_prog **_prog, *__prog;       \
374                 struct bpf_prog_array *_array;          \
375                 u32 _ret = 1;                           \
376                 preempt_disable();                      \
377                 rcu_read_lock();                        \
378                 _array = rcu_dereference(array);        \
379                 if (unlikely(check_non_null && !_array))\
380                         goto _out;                      \
381                 _prog = _array->progs;                  \
382                 while ((__prog = READ_ONCE(*_prog))) {  \
383                         _ret &= func(__prog, ctx);      \
384                         _prog++;                        \
385                 }                                       \
386 _out:                                                   \
387                 rcu_read_unlock();                      \
388                 preempt_enable_no_resched();            \
389                 _ret;                                   \
390          })

382                 while ((__prog = READ_ONCE(*_prog))) {  \
383                         _ret &= func(__prog, ctx);      \
384                         _prog++;                        \
385                 }   

上面的代码片段真正实现了注册的回调函数回调功能。ctx参数正式上面的sock_ops。

__BPF_PROG_RUN_ARRAY 宏实现中有一行代码至关重要:preempt_enable_no_resched()

区别常规的preempt_enable函数,这个函数带了no_resched,表示说执行当前代码片段后不能创造一个重新调度的抢占点。试想一下,如果preempt_enable,这时候会创造了一个重新调度抢占点,这时可能切换到其他高优先级的任务重,这时候是可能出现死锁的。 所以,最稳妥的办法是回调函数执行完后,继续回到原来的代码路径上,继续执行之前为执行的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值