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

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 的文件。

当所有准备工作完成后,在网络接口上挂载xdp.o。
ip link set dev ens33 xdp obj xdp.o
如果您不需要此xdp程序,可以将其卸载。
ip link set dev ens33 xdp off
当这个XDP程序处于运行状态时,如果去ping该主机,每两组数据包中,就会有一组无响应,就像如下这样。
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

最低0.47元/天 解锁文章
5678

被折叠的 条评论
为什么被折叠?



