ebpf sockops 代码解读

ebpf sockops 代码解读

首页休闲益智2048 Fishing更新时间:2024-05-11

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 } 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 }) 391 392 #define BPF_PROG_RUN_ARRAY(array, ctx, func) \ 393 __BPF_PROG_RUN_ARRAY(array, ctx, func, false)

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(); \

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

__BPF_PROG_RUN_ARRAY 宏实现中有一行代码非常重要:

preempt_enable_no_resched()

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

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved