BPF探测listen_sock的qlen失败求助:tcp_synack_timer等函数调用问题
咱们先拆解你遇到的问题,分析可能的原因,再给出几个可行的替代方案:
一、先排查现有代码的问题
1. 函数探测失败的常见原因
你提到已经在available_filtered_functions里找到了目标函数,但还是探测不到,大概率是这两个原因:
函数被编译器内联优化:内核编译时如果开启了
CONFIG_OPTIMIZE_INLINING,很多小函数会被内联到调用者中,导致kprobe无法挂钩(因为符号对应的独立函数代码不存在)。你可以用bpftool probe list --functions | grep tcp_synack_timer确认,如果输出为空,基本可以确定是被内联了。结构体类型转换错误:你的第二段代码里的结构体层级解析完全错了,导致访问非法内存,程序可能静默失败。正确的结构体关联逻辑是:
struct sock→struct inet_connection_sock(包含icsk_accept_queue成员)→struct request_sock_queue(包含listen_opt成员)→struct listen_sock(包含qlen字段)修正后的代码应该是这样:
#!/usr/local/bin/bpftrace #include <net/sock.h> #include <net/inet_connection_sock.h> #include <net/request_sock.h> kprobe:inet_csk_reqsk_queue_prune { $sk = (struct sock *)arg0; $inet = (struct inet_connection_sock *)$sk; $rsq = &$inet->icsk_accept_queue; // 正确获取request_sock_queue指针 $ls = $rsq->listen_opt; // 从队列中取出listen_sock if ($ls != 0) { // 避免空指针访问导致的探测失败 printf("-------------------\n"); printf("inet_csk_reqsk_queue_prune: %s pid: %d\n", comm, pid); printf("listen_sock qlen: %d\n", $ls->qlen); printf("--------------------\n"); } }
二、替代探测方案
如果上述修正后还是无法正常探测,试试下面这些更稳定的方法:
1. 换用更适合的TCP函数探测点
有些TCP核心函数的探测成功率更高,且能直接获取SYN队列长度:
tcp_v4_syn_recv_sock:处理SYN包并创建request_sock时触发,可在kretprobe中获取返回的request_sock,进而关联到listen_sock:#!/usr/local/bin/bpftrace #include <net/sock.h> #include <net/request_sock.h> kretprobe:tcp_v4_syn_recv_sock { $req = (struct request_sock *)retval; if ($req != 0) { $ls = $req->rsk_listener; printf("SYN queue qlen after new SYN: %d\n", $ls->qlen); } }inet_csk_accept:应用层调用accept()时触发,可直接读取listen_sock的qlen:#!/usr/local/bin/bpftrace #include <net/sock.h> #include <net/inet_connection_sock.h> #include <net/request_sock.h> kprobe:inet_csk_accept { $sk = (struct sock *)arg0; $inet = (struct inet_connection_sock *)$sk; $rsq = &$inet->icsk_accept_queue; $ls = $rsq->listen_opt; if ($ls != 0) { printf("Accept triggered, current SYN queue qlen: %d\n", $ls->qlen); } }
2. 用fprobe替代kprobe(内核5.15+支持)
fprobe是内核5.15新增的探测机制,支持探测内联函数和更多隐藏符号。如果你的内核版本足够新,可以试试:
#!/usr/local/bin/bpftrace #include <net/sock.h> #include <net/inet_connection_sock.h> #include <net/request_sock.h> fprobe:tcp_synack_timer { // tcp_synack_timer的参数是struct sock *sk $sk = (struct sock *)arg0; $inet = (struct inet_connection_sock *)$sk; $rsq = &$inet->icsk_accept_queue; $ls = $rsq->listen_opt; if ($ls != 0) { printf("tcp_synack_timer triggered, SYN queue qlen: %d\n", $ls->qlen); } }
3. 直接监控qlen字段的内存变化
如果函数探测都不行,还可以监控listen_sock中qlen字段的读写操作。首先需要获取字段的内存偏移量:
bpftool btf dump file /sys/kernel/btf/vmlinux format c | grep -n "qlen" | grep "struct listen_sock"
假设偏移量是0x10,可以用原子操作的探测点来监控:
#!/usr/local/bin/bpftrace kprobe:__atomic_add_fetch { // 过滤操作的内存地址是否匹配listen_sock的qlen字段偏移 if (arg1 == 0x10) { // 替换为实际获取到的偏移量 $qlen = *(int *)arg0; printf("SYN queue qlen updated to: %d\n", $qlen); } }
这种方法精度稍低,需要结合业务场景过滤无关操作,但在极端情况下可以作为备选。
三、验证探测有效性
每次修改代码后,用bpftrace -v运行,查看是否有could not attach probe或内存访问错误的提示。另外,可以用hping3或tcpdump发送SYN包触发探测点,验证输出是否符合预期。
内容的提问来源于stack exchange,提问作者Panagiotis Papoulidis

