默安逐日实验室:XDP的应用实践

1. 网络数据包是如何进入进计算机的

众所周知,网络数据包通常需要在TCP/IP协议栈中进行处理,但网络数据包并不直接进入TCP/IP协议栈;相反,他们直接进入网络接口。因此,在数据包进入 TCP/IP 堆栈之前,它们已经到达计算机内部。到目前为止,大多数应用程序都是在 TCP/IP 堆栈之后处理的。

image-20240118150012600

2. 什么是XDP

XDP,全称eXpress Data Path,是Linux内核提供的一个高可用性和可编程性的网络数据包处理框架,XDP允许在网络数据包到达Linux网络栈之前,在网络驱动程序级别对数据包进行处理。 XDP 使内核有能力在数据包到达网络层时快速处理数据包,它有高性能、高灵活度、低开销等优点。

3. 开始一个简单的XDP项目

挂载XDP程序请谨慎,一条错误的xdp规则是极有可能导致服务器失联的!

以下是一个很简单的XDP示例程序

#include <linux/bpf.h>

SEC("xdp")
//XDP 处理逻辑
int xdp_main(struct xdp_md *ctx)
{
    static int count = 1;
    count++;
    if (count%2)
    {
        return XDP_DROP;
    }
    else
    {
        return XDP_PASS;
    }
}

char __license[] __section("license") = "GPL";

工作时,当数据包进入接口时,计数将加1。当计数为偶数时,数据包被丢弃。

我这里采用了clang来编译XDP程序,不过clang有一定的版本限制,需要10.0以上,同样对于内核版本来说xdp需要4.15以上。

编译命令

clang -O2 -g -Wall -target bpf -c main.c -o xdp.o

当命令执行时,它会生成一个名为 xdp.o 的文件。

image-20240119104600652

当所有准备工作完成后,在网络接口上挂载xdp.o。

ip link set dev ens33 xdp obj xdp.o

如果您不需要此xdp程序,可以将其卸载。

ip link set dev ens33 xdp off

当这个XDP程序处于运行状态时,如果去ping该主机,每两组数据包中,就会有一组无响应,就像如下这样。image-20240119111654161

4. 逐步升级XDP应用功能

4.1 Level 1 使用XDP记录源地址IP

由于XDP是内核应用,而将源地址IP记录到本地又是一个用户态行为,那么我们就需要设法让内核态跟用户态进行交互,在这里我们使用了自带的bpf系统当中的MAP来进行数据交换。Map的本质是结构,它允许用户在内核空间和用户空间之间存储和共享数据。

4.1.1 XDP
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>

#define MAX_ENTRIES 1024

// 定义一个xdp_map结构,其中要包含map的类型、kv的数据类型以及map的最大条数
struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __type(key, __u32);
    __type(value, __u32);
    __uint(max_entries, MAX_ENTRIES);
} xdp_map SEC(".maps");

struct ip_event {
    __u32 src_ip;
};

SEC("xdp")
int xdp_main(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;
    struct iphdr *ip;
    // 对一些畸形包以及非ipv4的包放行
    if (eth + 1 > data_end) {
        return XDP_PASS;
    }
    if (eth->h_proto != __constant_htons(ETH_P_IP)) {
        return XDP_PASS;
    }

    ip = data + sizeof(*eth);
    if (ip + 1 > data_end) {
        return XDP_PASS;
    }
    struct ip_event evt = {
        .src_ip = ip->saddr,
    };
    __u32 key = 0; 
    __u32 value = evt.src_ip;
    bpf_map_update_elem(&xdp_map, &key, &value, BPF_ANY);
    return XDP_PASS;
}

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

在上述代码当中使用了bpf_map_update_elem接口,这个接口是bpf系统提供用来更新映射Map的。

4.1.2 用户态
package main

import (
	"fmt"
	"log"
	"net"
	"time"

	"github.com/cilium/ebpf"
	"github.com/cilium/ebpf/link"
	"github.com/cilium/ebpf/rlimit"
)

var (
	XDP_PATH = "xdp.o"
	IF       = "ens33"
	XDP_Func = "xdp_main"
	XDP_Map  = "xdp_map"
)

func main() {
   
   
	if err := rlimit.RemoveMemlock(); err != nil {
   
   
		log.Fatalf("关闭内存锁失败: %v", err)
	}
	// 加载xdp程序规则
	spec, err := ebpf.LoadCollectionSpec(XDP_PATH)
	if err != nil {
   
   
		log.Fatalf("XDP 程序加载失败: %v", err)
	}
	coll, err := ebpf.NewCollection(spec)
	if err != nil {
   
   
		log.Fatalf("创建新的程序集失败: %v", err)
	}
	defer coll.Close()
	// 在当前的程序集当中寻找名为 xdp_map 的bpfmap对象
	cmdMap := coll.DetachMap(XDP_Map)
	if cmdMap == nil {
   
   
		log.Fatalf("在%s当中未找到%s对象", XDP_PATH, XDP_Map)
	}
	// 将网卡名称转换为网卡index
	ifIndex, err := getInterfaceIndex(IF)
	if err != nil {
   
   
		log.Fatalf("获取%s索引失败: %v", IF, err)
	}
	// 寻找
	prog := coll.Programs[XDP_Func]
	if prog == nil {
   
   
		log.Fatalf("未找到%s方法",XDP_Func)
	}
	link, err := link.AttachXDP(link.XDPOptions{
   
   
		Program:   prog,
		Interface: ifIndex,
	})
	defer link.Close()
	// 读取ma
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值