XDP入门--eBPF程序实现网桥/二层交换机转发功能

news2024/12/25 0:59:56

本文目录

  • 1、试验环境
  • 2、eBPF字节码源代码实现
  • 3、用户态应用层管理与控制程序的源代码实现
  • 4、编译与运行
  • 5、测试结果

我们在此文的进阶部分 或者 此文中已经描述了如何设置Linux网桥,并将多个以太接口加入网桥后实现一个最基本的二层交换机的二层交换转发功能。Linux网桥可以学习到连接到网桥的每个端口上的各个设备MAC地址,并生成MAC/端口映射表,进而依据这个MAC/端口映射表对收到的数据包进行二层转发。

主要分以下二个过程:

  • MAC地址的学习与老化,生成并更新MAC/端口映射表(控制面过程)
  • 根据MAC/端口映射表,将收到的报文从正确的端口进行转发(数据面过程)

我们知道,配置完Linux网桥后,就自动具备了这个功用,但我们知道,如果使用Linux网桥来实现这个功能的话,每个数据包,需要经过XDP, Qdisc, Bridge_check,netfilter的link-layer的各个tables/chains的处理后(详见此文),才能转到正确的出口,转发的链路很长,效率和性能比较低。

这里我们准备使用BPF框架实现的Linux网桥的基本功能,以绕过Qdisc, bridge_check, netfilter等内核协议栈处理程序,实现性能更好的Linux网桥。
本例子中,会实现数据面和控制面分离(前提,读者已经理解了如何用用户态程序自动去加载与卸载eBPF字节码):

  • 由BPF程序在XDP框架里查询MAC地址与端口对应表实现报文转发
  • 由运行在用户态的应用程序管理MAC地址与端口对应表的更新与老化

我们可以借此进一步理解BPF, BPF map的框架与BPF程序的工作原理。

1、试验环境

试验环境与上一篇文章XDP入门–通过用户态程序自动加载与卸载eBPF程序字节码到网卡相同。

硬件:基于树莓派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    |                                                       
                                                     +-------------+          |             |
                                                                              +-------------+

在这里插入图片描述

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地址notify更新、删除信息,并将目标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.4 ping 10.0.0.10,然后在10.0.0.4这个网口上用tcpdump命令抓包。
    可以看到:
  • ping是通的
    在这里插入图片描述

  • tcpdump能看到icmp req/reply报文
    在这里插入图片描述

  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是通的,但tcpdump里抓不到的icmp 的req/reply报文了
    在这里插入图片描述
    在这里插入图片描述
  • 查看内核打印log记录

我们可以看到10.0.0.4和10.0.0.10之间的ping报文已经直接在XDP里转发走了。

sudo cat /sys/kernel/tracing/trace_pipe

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/579664.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在华为OD机试B卷中获得满分?Java实现【食堂供餐】一文详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

【剑指offer】数据结构——链表

目录 数据结构——字符串直接解【剑指offer】06. 从尾到头打印链表牛客力扣 【剑指offer】24. 反转链表【剑指offer】25. 合并两个排序的链表【剑指offer】35. 复杂链表的复制【剑指offer】52. 两个链表的第一个公共结点 特殊解——双指针【剑指offer】18. 删除链表的节点【剑指…

六级备考23天|CET-6|翻译技巧4|2013年官方样题|新年|9:45~11:00

目录 1 PRACTICE ANSWER 2 PRACTICE ANSWER 3 ​ PRACTICE ANSWER 4 PRACTICE ANSWER 5 PRACTICE ANSWER 6 ​ PRACTICE ANSWER ​​​​​​​ 答案整合​​​​​​​ 1 PRACTICE Chinese new year is the Chinese most important traditional festival, wh…

2023上半年软考系统分析师科目一整理-02

2023上半年软考系统分析师科目一整理-02 1. 安全2. 知识产权 1. 安全 对称加密算法中&#xff0c;由于加密解密都使用同样的密钥&#xff0c;所以密钥需要进行共享&#xff0c;故也被称共享密钥算法。 三重DES加密是使用2个DES密钥&#xff0c;进行多次操作来完成的&#xff…

Redis相关

Redis基本概念 一、Redis的持久化方式二、Redis的单机、主从、哨兵、集群Redis主从复制的原理 三、Redis分布式锁的实现四、缓存穿透 击穿 雪崩 一、Redis的持久化方式 1&#xff09;RDB方式 2&#xff09;AOF方式 二、Redis的单机、主从、哨兵、集群 单机的问题&#xf…

机器学习 | SVD奇异值分解

本文整理自哔哩哔哩视频&#xff1a;什么是奇异值分解SVD–SVD如何分解时空矩阵 &#x1f4da;奇异值分解是什么&#xff1f; M是原始矩阵&#xff0c;它可以是任意的矩阵&#xff0c;奇异值分解就是将它分解为三个矩阵相乘。U和V是方阵&#xff0c;∑是不规则矩阵&#xff0c;…

django组件552

前言&#xff1a;相信看到这篇文章的小伙伴都或多或少有一些编程基础&#xff0c;懂得一些linux的基本命令了吧&#xff0c;本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python&#xff1a;一种编程语言&…

618京东预售一般便宜多少?跟直接买有啥区别?

618京东预售一般便宜多少?跟直接买有啥区别? 京东作为消费者比较喜欢的电商购物平台之一&#xff0c;经常会推出促销打折的活动&#xff0c;以吸引用户到平台上购物。在这些大促活动中&#xff0c;平台会在预售环节设置专属的优惠&#xff0c;让消费者下单提前锁定这些折扣&a…

一、stable diffusion的发展史

一、stable diffusion的发展史 本文目标&#xff1a;学习交流 对于熟悉SD的同学&#xff0c;一起学习和交流使用过程中的技巧和心得。 帮助新手 帮助没有尝试过SD但又对它感兴趣的同学快速入门&#xff0c;并且能够独立生成以上效果图。 1.发展史介绍&#xff1a; 2015年的时候…

RepGhost 解析

paper&#xff1a;RepGhost: A Hardware-Efficient Ghost Module via Re-parameterization official implementation&#xff1a;https://github.com/chengpengchen/repghost 存在的问题 特征重用feature reuse是轻量网络设计中常用的一种技术&#xff0c;现有的方法通常使…

[元带你学: eMMC协议详解 10] Device 识别流程 与 中断模式

依JEDEC eMMC 5.1及经验辛苦整理&#xff0c;付费内容&#xff0c;禁止转载。 所在专栏 《元带你学: eMMC协议详解》 全文2700字&#xff0c;重点需掌握设备识别过程&#xff08;CMD1 -> CMD2 -> CMD3&#xff09;, 这很常用&#xff0c; 也是最容易出现异常的地方。其他…

Git进阶之代码回滚、合并代码、从A分支选择N次提交,合并到B分支【revert、merge、rebase、cherry-pick】

B站视频地址&#xff1a; https://www.bilibili.com/video/BV1KX4y1a7N9 Git学习文档&#xff1a;https://d9bp4nr5ye.feishu.cn/wiki/PeDPw3mm3iFA36k9td9cVeignsZ 在很长一段时间里&#xff0c;我对Git的操作只限于&#xff1a;提交代码&#xff0c;拉取代码&#xff0c;合…

研报精选230528

目录 【行业230528华金证券】传媒行业深度研究&#xff1a;AIGC最新应用与场景研究 【行业230528国海证券】电动船舶行业深度报告&#xff1a;绿色智能大势已至&#xff0c;驶向电化百亿蓝海 【行业230528华西证券】纺织服装行业周报&#xff1a;5月增长放缓无碍中长期出清逻辑…

Linux下的yum和vim

目录 一、Linux软件包管理器yum1.1 何为软件包&#xff1f;1.2 rzsz工具1.3 如何安装和卸载软件&#xff1f;1.4 Linux的软件生态 二、vim文本编辑器 一、Linux软件包管理器yum 1.1 何为软件包&#xff1f; 软件包可以理解成是windows下别人提前编译好的安装包程序&#xff0…

任务7 课程信息管理系统

系列文章 任务7 课程信息管理系统 已知课程的信息包括&#xff1a;课程编号&#xff0c;课程名称&#xff0c;课程性质&#xff08;必修、选修&#xff09;&#xff0c;课时&#xff0c;学分&#xff0c;考核方式&#xff08;考试、考查课&#xff09;&#xff0c;开课学期&a…

day41_servlet

今日内容 零、 复习昨日 一、Cookie 二、Session 三、拦截器 四、登录认证、全局编码格式 零、 复习昨日 注解 热部署 请求转发 重定向 路径问题 总结使用经验: 无论请求路径是多层是单层,在写路径时都从/开始,即从根开始如果是服务器动作,从/开始直接写如果是浏览器动作,从/开…

CVPR 2018 | Spotlight论文:单摄像头数秒构建3D人体模型

想把自己的身体形象投射进电子游戏里?现在已经是很容易的事了。人工智能算法此前已被广泛应用于虚拟现实头像、监视、服装试穿或电影等多种任务的人体建模上,但大多数方法需要特殊的照相设备来检测景深,或从多个角度探查人体。近日,来自德国布伦瑞克工业大学和 Max Planck …

js获取Element元素的常用方法

js中获取Element元素的常用方法有以下四种&#xff1a; 【方法一】根据元素ID&#xff1a;document.getElementById() 【方法二】根据元素标签&#xff1a;document.getElementsByTagName() 【方法三】根据元素class名&#xff1a;document.getElementsByClassName() 【方法…

yolov5刚开始train时的环境问题

torch会自动被requirement.txt替换 在对yolov5_5.0进行pip install requirement.txt后&#xff0c;yolo5_5.0会将虚拟环境中中的torch替换为2.0.1版本的&#xff0c;但要注意查看该torch是否为gpu版本&#xff0c;查看方式如下&#xff1a;打开Anaconda Prompt&#xff0c;激活…

渗透测试 | 端口扫描

0x00 免责声明 本文仅限于学习讨论与技术知识的分享&#xff0c;不得违反当地国家的法律法规。对于传播、利用文章中提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;本文作者不为此承担任何责任&#xff0c;一旦造成后果请自行承担…