在ubuntu16.04系统利用eBPF获取TCP网络状态信息

一,点总结

        学习内核模块的一个比较好的方法,是直接找内核源码samples目录对应的实例编译运行:比如想要了解 connector 模块以 netlink 协议通信的流程,可以找 samples/connector 例子编译.  学习 eBPF也是,通过查看 samples/bpf 说明(Makefile)以及源码,会收获良多。
        初步折腾的比较多了,大概也就会发现eBPF也就是 跟踪的函数、函数的参数及返回值、参数及返回值的内核结构体、map、perf event、ring buf、bpf_helpers.h,CORE 等这几个东西。如果需要在进一步,就需要阅读内核源码了。

二,知识点预热

        在 X86_64 架构下,定义了6个寄存器保存函数参数:%rdi, %rsi, %rdx, %rcx,%r8, %r9 如果函数参数多于6个,则多余的参数按照32位架构方式以从左到右的顺序压栈:

在ubuntu16.04系统利用eBPF获取TCP网络状态信息_第1张图片

三,代码来源

        https://github.com/ehids/ehids-agent.git

四,代码备注解释

SEC("kprobe/tcp_set_state")  //以 kprobe 方式跟踪内核态 tcp_set_state 函数
int kprobe__tcp_set_state(struct pt_regs *ctx) {
    //暂且认为ctx是linux内核进入 tcp_set_state 函数时的寄存器状态
    //#define PT_REGS_PARM1(x) ((x)->di), 寄存器 di 存储的是 tcp_set_state 函数的第一个参数值(指针)

    struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);

    if (!sk) return 0;

    //#define PT_REGS_PARM1(x) ((x)->si), 寄存器 si 存储的是 tcp_set_state 函数的第二个参数值
    int state = (int)PT_REGS_PARM2(ctx);

   ....

    if (state == TCP_SYN_SENT) {
       ....

    //通过bpf_helpers.h函数获取sock源目的地址
    bpf_probe_read(&data.lport, sizeof(data.lport), &sk->__sk_common.skc_num);
    bpf_probe_read(&data.rport, sizeof(data.rport), &sk->__sk_common.skc_dport);

    //通过perf event发送到应用层
    bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data, sizeof(data));
    ...
}

五,结合内核函数说明

        看下图,如果之前不明白的话,期望现在你有种豁然开朗的感觉. 

在ubuntu16.04系统利用eBPF获取TCP网络状态信息_第2张图片

        以kprobe方式为例,除了 tcp_set_state 函数外,linux内核还有哪些可以跟踪?

在ubuntu16.04系统利用eBPF获取TCP网络状态信息_第3张图片

        这么多的函数,eBPF真的可以为所欲为了! 

六,编译运行

        当前很多文章或者github上所要求的eBPF编译内核版本都比较高,一般都是 linux 5.X 版本。因为bpf代码会涉及内核定义的结构体,但是不同linux内核版本的结构体可能会有差异,这样就引出了内核版本兼容的问题。
        因为项目环境的原因,需要在ubuntu 16.04 的 linux 4.9.X 版本上编译eBPF,文中从github上拿来的bpf代码 tcp_set_state_kern.c,在 编译确实会遇到很多头文件引用的问题。
        这里有三种解决方案,一是 使用BPF CORE特性(4.9版本不支持BTF,也就不考虑了);二是 使用系统头文件;三是 直接拷贝所需的结构体并做简化修改。

七,一些经验教训

        前期使用的是第三种方法,eBPF程序也确实运行起来了,但是该种方法破坏了生产环境工程规范:易出错,难拓展,难维护。
        遇到这个问题其实就需要找人商讨解决了,大BOSS是不认同改结构体的方法的,我也觉的此法甚搓,经过大BOSS亲自上阵不断编译尝试,最终成功回归到第二种方法的正确轨道上来。
        其实我知道不修改结构体直接引用系统头文件是条正确的道路,但是还是有取巧的心理直接拷贝修改结构体。所以坚持、确信走该走的路的这种品质,还是要多多注意培养。

        

你可能感兴趣的:(Linux内核,eBPF,linux内核,x86_64寄存器,eBPF编译,CORE特性)