Introducción a XDP: el programa eBPF realiza la función de reenvío de conmutador de puente/capa 2

Hemos descrito en la parte avanzada de este artículo o este artículo cómo configurar un puente Linux y agregar varias interfaces Ethernet al puente para realizar la función de conmutación y reenvío de capa 2 de un conmutador de capa 2 más básico. El puente Linux puede aprender la dirección MAC de cada dispositivo conectado a cada puerto del puente y generar una tabla de asignación de puertos/MAC y luego realizar el reenvío de capa 2 de los paquetes de datos recibidos de acuerdo con la tabla de asignación de puertos/MAC.

Principalmente dividido en los siguientes dos procesos:

  • Aprendizaje y envejecimiento de las direcciones MAC, generación y actualización de la tabla de asignación de puertos/MAC (proceso del plano de control)
  • De acuerdo con la tabla de asignación de puertos/MAC, reenvíe el mensaje recibido desde el puerto correcto (proceso del plano de datos)

Sabemos que después de configurar el puente Linux, automáticamente tendrá esta función, pero sabemos que si se usa el puente Linux para realizar esta función, cada paquete de datos debe pasar a través de la capa de enlace de XDP, Qdisc, Bridge_check, netfilter Después del procesamiento de cada tabla/cadena ( consulte este artículo para obtener más detalles ), se puede transferir a la salida correcta. El enlace de reenvío es muy largo y la eficiencia y el rendimiento son relativamente bajos.

Aquí vamos a utilizar las funciones básicas del puente de Linux implementadas por el marco BPF para omitir los controladores de pila de protocolos del núcleo, como Qdisc, bridge_check, netfilter, etc., y realizar un puente de Linux con un mejor rendimiento.
En este ejemplo se realizará la separación del plano de datos y el plano de control (la premisa es que el lector ha entendido cómo cargar y descargar automáticamente bytecodes eBPF con programas en modo usuario ):

  • El programa BPF consulta la dirección MAC y la tabla de correspondencia de puertos en el marco XDP para realizar el reenvío de mensajes.
  • La actualización y el envejecimiento de la dirección MAC y la tabla de correspondencia de puertos son administrados por el programa de aplicación que se ejecuta en el modo de usuario.

Podemos tomar esto para comprender mejor BPF, el marco del mapa BPF y el principio de funcionamiento del programa BPF.

1. Entorno de prueba

El entorno de prueba es el mismo que el del artículo anterior Primeros pasos con XDP: carga y descarga automática de códigos de bytes del programa eBPF en tarjetas de red a través de programas de modo de usuario .

Hardware: Basado en Raspberry Pi Zero w + placa base de expansión con dos tarjetas Ethernet ---- Red RPi en la figura
: como se muestra en la figura a continuación

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

inserte la descripción de la imagen aquí

2. Implementación del código fuente del bytecode eBPF

El principio de implementación es muy simple, verifique la dirección mac de destino de cada mensaje recibido y consulte en la tabla de correspondencia de dirección MAC-puerto:

  1. Si no se encuentra, se enviará a la pila de protocolos tcp/ip del kernel para su procesamiento.
  2. Si lo encuentra, reenvíe el mensaje directamente según el puerto correspondiente a la dirección mac de destino
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include "/usr/include/bpf/bpf_helpers.h"

#ifndef __section
# define __section(NAME)                  \
   __attribute__((section(NAME), used))
#endif


// mac_port_map保存该目标MAC地址/端口的映射关系表,以目标MAC地址为key, 以端口为value
struct bpf_map_def __section("maps") mac_port_map = {
    
    
        .type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(long long),
        .value_size = sizeof(int),
        .max_entries = 100,
};

__section("prog")
int xdp_bridge_prog(struct xdp_md *ctx)
{
    
    
        void *data_end = (void *)(long)ctx->data_end;
        void *data = (void *)(long)ctx->data;
        long dst_mac = 0;
        int in_index = ctx->ingress_ifindex, *out_index;
        // data即数据包开始位置
        struct ethhdr *eth = (struct ethhdr *)data;
        char info_fmt[] = "Dst Addr:0x%llx From:[%d]---Redirect to:[%d]\r\n";
        char info_fmt1[] = "xdp_pass";
        char info_fmt2[] = "xdp_drop";
        
        // 错误包检查,必选 
        if (data + sizeof(struct ethhdr) > data_end) {
    
    
                return XDP_DROP;
        }

        // 获取目标MAC地址
        __builtin_memcpy(&dst_mac, eth->h_dest, 6);

        // 目标MAC地址/端口的映射表里查找目标端口
        out_index = bpf_map_lookup_elem(&mac_port_map, &dst_mac);
        if (out_index == 0) {
    
    
                // 如若找不到,则上传到内核TCP/IP协议栈处理
                bpf_trace_printk(info_fmt1, sizeof(info_fmt1));
                return XDP_PASS;
        }
        // 错误报文,进出同一个端口的,丢弃
        if (in_index == *out_index) {
    
    
                bpf_trace_printk(info_fmt2, sizeof(info_fmt2));
                return XDP_DROP;
        }

        // 打印转发信息到/sys/kernel/tracing/trace_pipe
        bpf_trace_printk(info_fmt, sizeof(info_fmt), dst_mac, in_index, *out_index);

        // 转发到目标端口
        return  bpf_redirect(*out_index, 0);
}

char _license[] SEC("license") = "GPL";

3. Implementación del código fuente del programa de gestión y control de la capa de aplicación del estado del usuario

La función también es sencilla:

  1. Cargue el código de bytes eBPF especificado en las dos NIC
  2. Escuche la notificación de actualización de dirección mac, elimine información y actualice la relación de asignación de la dirección/puerto MAC de destino a la tabla mac_port_map para la consulta de código de bytes eBPF
  3. Descargar automáticamente el código de bytes eBPF al salir
#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 mac_port_map_fd;
static int *ifindex_list;

// 退出时卸载XDP
static void int_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 sock, i;
        char buf[1024];
        char filename[64];
        static struct sockaddr_nl g_addr;
        struct bpf_object *obj;
        struct bpf_prog_load_attr prog_load_attr = {
    
    
                .prog_type      = BPF_PROG_TYPE_XDP,
        };
        int prog_fd;
        printf("we are starting...\r\n");
        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;
        }

        mac_port_map_fd = bpf_object__find_map_fd_by_name(obj, "mac_port_map");
        ifindex_list = (int *)calloc(2, sizeof(int *));

        //通过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("link set xdp fd failed\n");
                        return 1;
                }
        }
        // 设置CTRL+C退出程序时要执行的卸载函数
        signal(SIGINT, int_exit);

        bzero(&g_addr, sizeof(g_addr));
        g_addr.nl_family = AF_NETLINK;
        g_addr.nl_groups = RTM_NEWNEIGH;
        printf("we are starting socket...\r\n");
        if ((sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) {
    
    
                int_exit(0);
                return -1;
        }

        if (bind(sock, (struct sockaddr *) &g_addr, sizeof(g_addr)) < 0) {
    
    
                int_exit(0);
                return 1;
        }


        // 持续监听socket,捕获更新信息,更新删除MAC/端口对应表
        while (1) {
    
    
                int len;
                struct nlmsghdr *nh;
                struct ndmsg *ifimsg ;
                int ifindex = 0;
                unsigned char *cmac;
                unsigned long long lkey = 0;

                len = recv(sock, buf, sizeof(buf), 0);
                printf("recv...\r\n");
                if (len <= 0) continue;
                printf("get mac notification\r\n");
                for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) {
    
    
                        ifimsg = NLMSG_DATA(nh) ;
                        if (ifimsg->ndm_family != AF_BRIDGE) {
    
    
                                continue;
                                printf("not AF_BRIDGE\r\n");
                        }

                        printf("AF_BRIDGE\r\n");
                        // 获取notify信息中的端口
                        ifindex = ifimsg->ndm_ifindex;
                        for (i = 0; i < 2; i++) {
    
    
                                printf("find ifindex = %d\r\n", ifindex);
                                if (ifindex == ifindex_list[i]) break;
                        }
                        if (i == 2) continue;

                        printf("i=%d,  ifindex=%d\r\n",i,ifindex);
                        // 获取notify信息中的MAC地址
                        cmac = (unsigned char *)ifimsg + sizeof(struct ndmsg) + 4;

                        memcpy(&lkey, cmac, 6);
                        printf("sizeof lkey %d\r\n", sizeof(lkey));
                        printf("2nd i=%d, ifindex=%d\r\n",i,ifindex);
                        if (nh->nlmsg_type == RTM_DELNEIGH) {
    
    
                                bpf_map_delete_elem(mac_port_map_fd, (const void *)&lkey);
                                printf("Delete XDP item from [HW Address:Port] table-------------[0x%llx]:[%d]\r\n", lkey, ifindex);
                        } else if (nh->nlmsg_type == RTM_NEWNEIGH) {
    
    
                                bpf_map_update_elem(mac_port_map_fd, (const void *)&lkey, (const void *)&ifindex, 0);
                                printf("Update XDP item from [HW Address:Port] table-------------[0x%llx]:[%d]\r\n", lkey, ifindex);
                        }
                }
                printf("out of for()\r\n");
        }
}

4. Compilar y ejecutar

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

Genere dos archivos a.out y brideg.o en el directorio actual

  • correr
 sudo ./a.out eth0 eth1

5. Resultados de la prueba

Probamos este código apagando y encendiendo la función del puente.

Proceder de la siguiente:

  1. En el estado inicial, el puente br0 está habilitado.Cuando el programa no se está ejecutando, hacemos ping 10.0.0.10 desde 10.0.0.4 y luego usamos el comando tcpdump para capturar paquetes en el puerto de red 10.0.0.4.
    puede ser visto:
  • trabajos de ping
    inserte la descripción de la imagen aquí

  • tcpdump puede ver el mensaje de solicitud/respuesta de icmp
    inserte la descripción de la imagen aquí

  1. Ejecute el programa, y ​​primero cierre y luego abra el puente.
  • ejecuta el programa
sudo ./a.out eth0 eth1
  • Ejecute cerrando y abriendo el puente en otro terminal para activar la notificación de actualización del puerto de dirección MAC
sudo ifconfig br0 down
sudo ifconfig br0 up

El programa de interfaz de usuario imprimirá de la siguiente manera, primero eliminará dos entradas y luego agregará dos entradas

get mac notification
AF_BRIDGE
find ifindex = 3
i=0,  ifindex=3
sizeof lkey 8
2nd i=0, ifindex=3
Delete XDP item from [HW Address:Port] table-------------[0x26513558577c]:[3]
out of for()
recv...
get mac notification
AF_BRIDGE
find ifindex = 4
find ifindex = 4
i=1,  ifindex=4
sizeof lkey 8
2nd i=1, ifindex=4
Delete XDP item from [HW Address:Port] table-------------[0x40f046730318]:[4]
out of for()
recv...
get mac notification
out of for()
recv...
get mac notification
out of for()
recv...
get mac notification
out of for()
recv...
get mac notification
AF_BRIDGE
find ifindex = 3
i=0,  ifindex=3
sizeof lkey 8
2nd i=0, ifindex=3
Update XDP item from [HW Address:Port] table-------------[0x26513558577c]:[3]
out of for()
recv...
get mac notification
AF_BRIDGE
find ifindex = 4
find ifindex = 4
i=1,  ifindex=4
sizeof lkey 8
2nd i=1, ifindex=4
Update XDP item from [HW Address:Port] table-------------[0x40f046730318]:[4]
out of for()
recv...
get mac notification
out of for()
recv...
get mac notification
out of for()
recv...
get mac notification
out of for()
recv...
get mac notification
out of for()

  • Verifique el estado de ping y tcpdump
    En este punto, ping está conectado, pero el mensaje de solicitud/respuesta de icmp no se puede capturar en tcpdump
    inserte la descripción de la imagen aquí
    inserte la descripción de la imagen aquí
  • Ver registros de registro de impresión del kernel

Podemos ver que los paquetes de ping entre 10.0.0.4 y 10.0.0.10 se han reenviado directamente en XDP.

sudo cat /sys/kernel/tracing/trace_pipe

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/meihualing/article/details/130842827
Recomendado
Clasificación