Getting Started with XDP--Automatically load and unload eBPF program bytecode to network card through user mode program

Through the article XDP Getting Started – hello world , we know that the bytecode of the eBPF program can be loaded and unloaded to the network card through the ip tool of iproute2. But this is not very convenient to use. Moreover, when the network card needs to resume normal operation, it is also necessary to enter the corresponding command to manually uninstall the bytecode of the eBPF program.
A more general and feasible approach is to create a regular user-mode program as an administrative control program for users to run.

  • When the user mode program starts, automatically load the bytecode of the eBPF program to the network card
  • When the user mode program exits, the bytecode of the eBPF program is automatically unloaded, and the corresponding network card returns to the normal working mode
  • When the user mode program is running, it is used to configure and control the operation of the bytecode of the eBPF program

So this article introduces the code implementation of automatic loading and unloading as mentioned above.

1. Test environment

Hardware: Based on Raspberry Pi Zero w + expansion baseboard with two Ethernet cards ---- RPi network in the figure
: as shown in the figure below

                                                     +- 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    |                                                       
                                                     +-------------+          |             |
                                                                              +-------------+

insert image description here
insert image description here

1. Source code implementation of eBPF bytecode

The function realized by this bytecode: every time a message comes in, check whether it is an IPV4 message, and if it is, print the source IP address and destination IP address of this message

#include <stdio.h>
#include <linux/bpf.h>
#include <net/ethernet.h>
#include <linux/if_vlan.h>
#include <netinet/in.h>
#include <linux/ip.h>
#include <bpf/bpf_helpers.h>

#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";

3. Introduction to the implementation of user mode application level

Notice,

  1. Because the Raspberry Pi used in the experimental test only has two Ethernet cards, the two network cards are written to death in the code.
  2. This user mode program will not exit automatically, it can only be forced to exit by ctrl+c
  3. The programming operating environment is 32-bit linux, so if your environment is 64-bit, the length of char, short, int, long, long long needs to be adjusted

#include <stdio.h>
#include <signal.h>
#include <sys/socket.h>
#include <net/if.h>
#include <bpf/bpf.h>
#include <linux/bpf.h>
#include <linux/rtnetlink.h>
#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);
        }

}


4. Compile and run

Save the user mode program as file main.c, save the eBPF program as bridge.c, and compile them with the following commands respectively

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

Two files a.out and bridge.o will be generated in the current directory

Then run it with:

sudo ./a.out eth0 eth1

5. Running status verification

  1. The user mode program has been running normally (here there is a warning print, which does not affect the function, just ignore it)

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

2. Then the output of eBPF bytecode operation is as follows (if you need detailed principles, please refer to Getting Started with XDP – How BPF Programs Print Log, printf log, and print logs )

Because eth0, the computer background program connected to eth1 is interacting with the Internet, so you can see the bytecode printout without anything. Your linux is very clean, and there is nothing running in the background, so you can ping 10.0 from 10.0.0.4 After .0.10, you get an output similar to the one below.

sudo cat /sys/kernel/debug/tracing/trace_pipe

  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

Guess you like

Origin blog.csdn.net/meihualing/article/details/130904108