• XDP入门--通过用户态程序自动加载与卸载eBPF程序字节码到网卡


    通过文章XDP入门–之hello world 我们知道,可以通过iproute2的ip工具向网卡去加载和卸载eBPF程序的字节码。但这个使用起来不太方便。而且在需要网卡恢复正常工作时,还需要输入相应的命令去手动卸载eBPF程序的字节码。
    更为通用和可行的做法是创建一个常规的用户态程序做为让用户运行使用的管理控制程序。

    • 当用户态程序启动时,自动去加载eBPF程序的字节码到网卡
    • 当用户态程序退出时,自动去卸载eBPF程序的字节码,让相应网卡恢复正常工作模式
    • 当用户态程序运行时,用于对eBPF程序的字节码的运行做出配置与控制

    所以本文章就介绍如上所述的自动加载和卸载的代码实现。

    1、测试环境

    硬件:基于树莓派Zero w + 带二个以太网卡的扩展底板----图中的RPi
    网络:如下图所示

                                                         +- RPi -------+          +- old pc1----+
                                                         |         Eth0+----------+ Eth0        |    
                     +- Router ----+                     |  DHCP server|          | 10.0.0.10   |
                     | Firewall    |                     |   10.0.0.1  |          |             |
    (Internet)---WAN-+ DHCP server +-WLAN AP-+-)))   (((-+ WLAN        |          +-------------+
                     | 192.168.3.1 |                     |             |          
                     +-------------+                     |             |          +- old pc2----+
                                                         |         Eth1+----------+ Eth0        |   
                                                         |             |          | 10.0.0.4    |                                                       
                                                         +-------------+          |             |
                                                                                  +-------------+
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    在这里插入图片描述

    1、eBPF字节码的源代码实现

    这个字节码实现的功能:每进来一个报文就检测是不是IPV4的报文,如果是,则打印本报文的源IP地址和目标IP地址

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #ifndef __section
    # define __section(NAME)                  \
       __attribute__((section(NAME), used))
    #endif
    
    __section("prog")
    int xdp_ip_filter(struct xdp_md *ctx)
    {
        void *end = (void *)(long)ctx->data_end;
        void *data = (void *)(long)ctx->data;
        int ip_src;
        int ip_dst;
        long int offset;
        short int eth_type;
    
        char info_fmt1[] = "Dst Addr: %pi4";
        char info_fmt2[] = "Src Addr: %pi4";
        char info_fmt3[] ="-----------------";
       
        struct ethhdr *eth = data;
        offset = sizeof(*eth);
    
        if (data + offset > end) {
        return XDP_ABORTED;
        }
        eth_type = eth->h_proto;
    
        /* 只处理 IPv4 地址*/
        if (eth_type == ntohs(ETH_P_IPV6)) {
            return XDP_PASS;
        }
    
        struct iphdr *iph = data + offset;
        offset += sizeof(struct iphdr);
        /*  在读取之前,确保你要读取的子节在数据包的长度范围内  */
        if (iph + 1 > end) {
            return XDP_ABORTED;
        }
        ip_src = iph->saddr;
        ip_dst = iph->daddr;
    
        bpf_trace_printk(info_fmt3, sizeof(info_fmt3));
        bpf_trace_printk(info_fmt2, sizeof(info_fmt2), &ip_src);
        bpf_trace_printk(info_fmt1, sizeof(info_fmt1), &ip_dst);
    
        return XDP_PASS;
    }
    
    char __license[] __section("license") = "GPL";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    3、用户态应用程度实现简介

    注意,

    1. 因为实验测试用的树梅派只有二个以太网卡,所以代码里就写死二个网卡了。
    2. 这个用户态程序不会自动退出,只能以ctrl+c的方式强制退出
    3. 编程运行环境为32位linux,所以如果你的环境是64位的,char, short, int, long, long long的长度需要进行调整
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "/usr/src/linux-6.1/tools/testing/selftests/bpf/bpf_util.h"
    
    int flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
    static int *ifindex_list;
    
    // 退出时自动卸载eBPF字节码的函数
    static void uninstall_exit(int sig)
    {
            int i = 0;
            for (i = 0; i < 2; i++) {
                    bpf_set_link_xdp_fd(ifindex_list[i], -1, 0);
            }
            exit(0);
    }
    // 以下是用户态程序入口
    int main(int argc, char *argv[])
    {
            int i;
            char filename[64];
            struct bpf_object *obj;
            struct bpf_prog_load_attr prog_load_attr = {
                    .prog_type      = BPF_PROG_TYPE_XDP,
            };
            int prog_fd;
    
            // 以下bridge.o依赖于你编译出来的.o文件名,做修改,.o要和当前代码编译出来的可执行程序放在同一个目录下
            snprintf(filename, sizeof(filename), "bridge.o");
            prog_load_attr.file = filename;
    
            // 从文件中载入eBPF字节码
            if (bpf_prog_load_xattr(&prog_load_attr, &obj, &prog_fd)) {
                    return 1;
            }
    
            ifindex_list = (int *)calloc(2, sizeof(int *));
    
            //注意,运行时,需要输入二个网卡的ifname, 就是eth0 eth1这种,依赖于系统不一样,可能名字会不同,下面的代码会把ifname转换在ifindex。
            ifindex_list[0] = if_nametoindex(argv[1]);
            ifindex_list[1] = if_nametoindex(argv[2]);
    
            for (i = 0; i < 2; i++) {
                    // 将eBPF字节码安装到指定的网卡
                    if (bpf_set_link_xdp_fd(ifindex_list[i], prog_fd, flags) < 0) {
                            printf("install xdp fd failed\n");
                            return 1;
                    }
            }
            //设置程序退出时,自动卸载eBPF字节码的函数
            signal(SIGINT, uninstall_exit);
    
            // 进入运行循环,什么都不做,只打印一个working...
            while(1){
                i++;
                sleep(1);
                printf("working...%d\r\n", i);
            }
    
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    4、编译与运行

    将用户态程序存成文件main.c, 将eBPF程序存成bridge.c, 分别用以下命令进行编译

    gcc main.c -lbpf
    sudo clang -O2 -Wall -target bpf -c bridge.c -o bridge.o
    
    • 1
    • 2

    会在当前目录下生成a.out和bridge.o二个文件

    然后用以下命令运行:

    sudo ./a.out eth0 eth1
    
    • 1

    5、运行状态验证

    1. 用户态程序已经正常运行(这里有个告警打印,不影响功能,忽略则可)
    
    meihualing@raspberrypi:~/userloadprint $ sudo ./a.out eth0 eth1
    libbpf: elf: skipping unrecognized data section(4) .rodata.str1.1
    working...3
    working...4
    working...5
    working...6
    working...7
    working...8
    working...9
    working...10
    working...11
    working...12
    working...13
    working...14
    working...15
    working...16
    working...17
    working...18
    working...19
    working...20
    working...21
    working...22
    working...23
    working...24
    working...25
    working...26
    working...27
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    2,然后eBPF字节码运行的输出如下(如需要详细的原理,可参见XDP入门–BPF程序如何打印log, printf log,打印日志)

    因为eth0,eth1接着的电脑后台程序在和互联网交互,所以不需要任何东西就可以看到字节码的打印输出,你的linux特别干净,没有后台运行的东西,则可以从10.0.0.4 ping 10.0.0.10后,得到类似下方的输出。

    sudo cat /sys/kernel/debug/tracing/trace_pipe
    
    
    • 1
    • 2
      kworker/u3:0-60      [000] d.s..  7613.615253: bpf_trace_printk: -----------------
        kworker/u3:0-60      [000] d.s..  7613.615287: bpf_trace_printk: Src Addr: 010.000.000.004
        kworker/u3:0-60      [000] d.s..  7613.615299: bpf_trace_printk: Dst Addr: 115.223.009.115
        kworker/u3:0-60      [000] d.s..  7613.615756: bpf_trace_printk: -----------------
        kworker/u3:0-60      [000] d.s..  7613.615784: bpf_trace_printk: Src Addr: 010.000.000.004
        kworker/u3:0-60      [000] d.s..  7613.615795: bpf_trace_printk: Dst Addr: 115.223.009.115
        kworker/u3:0-60      [000] d.s..  7613.617634: bpf_trace_printk: -----------------
        kworker/u3:0-60      [000] d.s..  7613.617668: bpf_trace_printk: Src Addr: 010.000.000.004
        kworker/u3:0-60      [000] d.s..  7613.617679: bpf_trace_printk: Dst Addr: 115.223.009.115
                sshd-1291    [000] d.s..  7613.620971: bpf_trace_printk: -----------------
                sshd-1291    [000] d.s..  7613.621009: bpf_trace_printk: Src Addr: 010.000.000.004
                sshd-1291    [000] d.s..  7613.621022: bpf_trace_printk: Dst Addr: 192.168.003.190
              <idle>-0       [000] d.s..  7613.628747: bpf_trace_printk: -----------------
              <idle>-0       [000] d.s..  7613.628785: bpf_trace_printk: Src Addr: 010.000.000.004
              <idle>-0       [000] d.s..  7613.628797: bpf_trace_printk: Dst Addr: 192.168.003.190
              <idle>-0       [000] d.s..  7613.839358: bpf_trace_printk: -----------------
              <idle>-0       [000] d.s..  7613.839397: bpf_trace_printk: Src Addr: 010.000.000.004
              <idle>-0       [000] d.s..  7613.839409: bpf_trace_printk: Dst Addr: 192.168.003.190
              <idle>-0       [000] d.s..  7613.849433: bpf_trace_printk: -----------------
              <idle>-0       [000] d.s..  7613.849473: bpf_trace_printk: Src Addr: 010.000.000.004
              <idle>-0       [000] d.s..  7613.849484: bpf_trace_printk: Dst Addr: 192.168.003.190
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    Life and Its Markovian Symphony
    大功率光伏应用不同多电平变换器拓扑的比较研究(Simulink)
    log4j-slf4j-impl cannot be present with log4j-to-slf4j 之类的问题,解决maven依赖冲突
    Camtasia2023屏幕录制和视频剪辑标杆软件,制作微课/游戏视频必备工具
    【服务注册框架1】Eureka&nacos 两者的区别
    设计模式系列详解 -- 迭代器模式
    计算机毕业设计Java宠物寄养预约系统(源码+系统+mysql数据库+lw文档)
    php伪协议详解
    关于爬虫API常见的技术问题和解答
    代码随想录算法训练营第三十天| LeetCode332. 重新安排行程、LeetCode51. N 皇后、LeetCode37. 解数独
  • 原文地址:https://blog.csdn.net/meihualing/article/details/130904108