You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

eBPF追踪中如何在kprobe:tcp_v4_connect获取文件描述符

问题解答

在tcp_v4_connect上下文获取对应文件描述符的方法

tcp_v4_connect本身的入参不直接携带文件描述符(fd),可以通过两种稳定方式拿到:

  • 结构体顺藤摸瓜法(最准确,无兼容问题)
    1. 用PT_REGS_PARM1(ctx)从寄存器拿到tcp_v4_connect的第一个参数,即struct sock *sk指针
    2. 顺着内核结构体偏移读:sk->sk_socket拿到对应struct socket *sock,再读sock->file拿到struct file *file指针
    3. 通过bpf_get_current_task_btf()拿到当前进程的task_struct,遍历进程files->fdt->fd数组,找到值等于上述file指针的数组下标,就是当前连接对应的fd。
      遍历的时候可以加最大遍历长度限制(比如1024),客户端场景下连接用的fd普遍很小,遍历开销可以忽略。
  • 双挂载点映射法(性能更高)
    同时挂载tracepoint:syscalls:sys_enter_connect,这个tracepoint的参数直接携带fd、目的地址/端口信息。你可以创建一个以<tgid, pid>为key的临时map存当前线程正在发起connect的fd和五元组信息——线程执行connect的路径很短,从syscall入口到tcp_v4_connect的流程中不会出现跨线程的信息干扰,等执行到tcp_v4_connect时,直接用当前线程的tgid+pid查这个临时map,和当前sk读出的源/目的IP、端口做简单校验就能拿到对应fd,读取完成后删掉临时map的对应条目即可,省去遍历fd数组的开销。

客户端请求全链路追踪的更优实现方案

你当前用tracepoint:syscalls:write挂载的方案有两个明显缺陷:一是只能抓到write类调用,会漏掉send、sendmsg、writev等其他发送数据的系统调用;二是靠fd做关联键容易因为fd复用、多线程共享fd出现串流问题。更稳定的实现可以参考以下思路:

  • 用struct sock *sk指针作为连接的唯一关联键
    内核中每个TCP连接对应的sk指针在连接生命周期内是唯一的,连接释放前不会被复用,比fd作为关联键可靠得多,完全不需要在各个追踪点之间传递、匹配fd。
  • 选择覆盖全、兼容性好的挂载点
    • 连接阶段:优先用tracepoint:sock:inet_sock_set_state替代kprobetcp_v4_connect。这个tracepoint是TCP状态变更的统一钩子,同时覆盖IPv4/IPv6的主动连接、被动连接场景,参数直接携带sk指针、新旧TCP状态、源/目的IP和端口,不用分别适配不同版本的v4/v6 connect函数,跨内核版本兼容性更好。当连接状态转为TCP_SYN_SENT时记录连接发起事件,转为TCP_ESTABLISHED时记录连接建立成功事件。
    • 数据收发阶段:直接挂kprobetcp_sendmsg和tcp_recvmsg,不要挂系统调用层的write/read。这两个函数是TCP协议栈收发数据的统一入口,上层不管用write、send、sendmsg还是writev,最终都会走到这两个函数,不会漏抓流量。而且这两个函数的第一个参数就是struct sock *sk,直接拿sk当key查连接信息,完全绕开fd关联的问题。
    • 连接关闭阶段:还是复用inet_sock_set_state tracepoint,当状态转为TCP_CLOSE时删除map中对应sk的条目,避免map无限膨胀、出现脏数据。
  • 可选的用户态上下文关联
    如果需要把内核侧的网络事件和用户态的全链路追踪ID(比如OpenTelemetry的TraceID)关联,可以配合uprobe挂载到用户态使用的网络库入口(比如libc的connect、send函数,或者语言自带的网络库实现),拿到用户态的trace上下文后,以<进程tgid, sk指针>为key存在map中,内核侧的各个追踪点可以直接读取对应上下文,实现端到端的链路串联。
  • 避坑提示
    • 不要用线程ID(pid)作为map key的组成部分,多线程进程内的线程共享文件描述符和sock结构,用进程tgid+sk指针作为key即可,避免多线程场景下匹配不到连接。
    • 抓应用层载荷时,直接在tcp_sendmsg/tcp_recvmsg中读取iov缓冲区的内容即可,比在系统调用层读取适配成本更低,覆盖更全。

内容的提问来源于stack exchange,提问作者dalek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 05:12:35