Введение в XDP — программа eBPF реализует функцию переадресации моста/коммутатора уровня 2

В расширенной части этой статьи или этой статьи мы описали , как настроить мост Linux и добавить несколько интерфейсов Ethernet к мосту, чтобы реализовать функцию коммутации и пересылки уровня 2 самого простого коммутатора уровня 2. Мост Linux может узнать MAC-адрес каждого устройства, подключенного к каждому порту моста, и создать таблицу сопоставления MAC-адресов/портов, а затем выполнить пересылку полученных пакетов данных уровня 2 в соответствии с таблицей сопоставления MAC-адресов/портов.

В основном делится на следующие два процесса:

  • Изучение и устаревание MAC-адресов, создание и обновление таблицы сопоставления MAC/портов (процесс уровня управления)
  • В соответствии с таблицей сопоставления MAC-адресов и портов перенаправьте полученное сообщение с правильного порта (процесс уровня данных).

Мы знаем, что после настройки моста Linux он автоматически получит эту функцию, но мы знаем, что если для реализации этой функции используется мост Linux, каждый пакет данных должен проходить через канальный уровень XDP, Qdisc, Bridge_check, netfilter. После обработки каждой таблицы/цепочки ( подробности см. в этой статье ), ее можно передать на правильный выход.Ссылка переадресации очень длинная, а эффективность и производительность относительно низкие.

Здесь мы собираемся использовать основные функции моста Linux, реализованные инфраструктурой BPF, чтобы обойти обработчики стека протоколов ядра, такие как Qdisc, bridge_check, netfilter и т. д., и реализовать мост Linux с большей производительностью.
В этом примере будет реализовано разделение плоскости данных и плоскости управления (предполагается, что читатель понял, как автоматически загружать и выгружать байт-коды eBPF с помощью программ пользовательского режима ):

  • Программа BPF запрашивает таблицу соответствия MAC-адресов и портов в структуре XDP для реализации пересылки сообщений.
  • Обновление и устаревание таблицы соответствия MAC-адресов и портов управляются прикладной программой, работающей в пользовательском режиме.

Мы можем использовать это, чтобы лучше понять BPF, структуру карты BPF и принцип работы программы BPF.

1. Тестовая среда

Тестовая среда такая же, как и в предыдущей статье Начало работы с XDP — Автоматическая загрузка и выгрузка байт-кодов программ eBPF на сетевые карты с помощью программ пользовательского режима .

Аппаратное обеспечение: на основе Raspberry Pi Zero w + основная плата расширения с двумя картами Ethernet ---- сеть 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    |                                                       
                                                     +-------------+          |             |
                                                                              +-------------+

вставьте сюда описание изображения

2. Реализация исходного кода байт-кода eBPF

Принцип реализации очень прост, проверяем целевой mac адрес каждого полученного сообщения, и запрашиваем в таблице соответствия MAC адрес-порт:

  1. Если он не может быть найден, он будет отправлен в стек протоколов tcp/ip ядра для обработки.
  2. Если найдено, перешлите сообщение напрямую по порту, соответствующему целевому MAC-адресу.
#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. Реализация исходного кода программы управления и контроля прикладного уровня пользовательского состояния.

Функция тоже несложная:

  1. Загрузите указанный байт-код eBPF во все две сетевые карты.
  2. Прослушивание уведомления об обновлении MAC-адреса, удаление информации и обновление отношения сопоставления целевого MAC-адреса/порта с таблицей mac_port_map для запроса байт-кода eBPF
  3. Автоматически выгружать байт-код eBPF при выходе
#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. Скомпилируйте и запустите

  • компилировать
sudo clang -O2 -Wall -target bpf -c bridge.c -o bridge.o
gcc main.c -lbpf

Создайте два файла a.out и brideg.o в текущем каталоге.

  • бегать
 sudo ./a.out eth0 eth1

5. Результаты испытаний

Мы проверили этот код, выключив и включив функцию моста.

Действуйте следующим образом:

  1. В исходном состоянии мост br0 включен, когда программа не запущена, пингуем 10.0.0.10 с 10.0.0.4, а затем используем команду tcpdump для перехвата пакетов на сетевом порту 10.0.0.4.
    можно увидеть:
  • пинг работает
    вставьте сюда описание изображения

  • tcpdump может видеть сообщение запроса/ответа icmp
    вставьте сюда описание изображения

  1. Запустите программу, и сначала закройте, а затем откройте мост
  • запустить программу
sudo ./a.out eth0 eth1
  • Запустите закрытие и открытие моста в другом терминале, чтобы вызвать уведомление об обновлении порта MAC-адреса.
sudo ifconfig br0 down
sudo ifconfig br0 up

Программа пользовательского интерфейса будет печатать следующим образом: сначала удалите две записи, а затем добавьте две записи.

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()

  • Проверьте состояние ping и tcpdump
    . В этот момент ping подключен, но сообщение запроса/ответа icmp, которое не может быть перехвачено в tcpdump
    вставьте сюда описание изображения
    вставьте сюда описание изображения
  • Просмотр записей журнала печати ядра

Мы видим, что пакеты ping между 10.0.0.4 и 10.0.0.10 были напрямую перенаправлены в XDP.

sudo cat /sys/kernel/tracing/trace_pipe

вставьте сюда описание изображения

рекомендация

отblog.csdn.net/meihualing/article/details/130842827