eBPF入门系列:实现快速响应Ping包

通过使用 XDPTC 两种 eBPF 类型程序实现快速响应Ping包

XDP

学习项目

xdpping

编译及运行

# 使用bpf2go生成对应的golang类型的eBPF数据结构及函数
$ go generate
# 编译
$ go build

# 运行,加载eBPF程序到enp0s3网卡
$ sudo ./xdpping --dev enp0s3

源码

加载eBPF程序的用户态代码程序主要是调用Cilium社区的ebpf库实现,这里不做介绍,下面主要讲一下C语言实现的eBPF代码片段:

//go:build ignore

#include "../headers/bpf_all.h"

// 挂载点为xdp,函数名xdp_ping(可自定义),参数类型xdp_md(由内核xdp函数参数决定)
SEC("xdp")
int xdp_ping(struct xdp_md *ctx)
{
    bpf_printk("xdpping starting\n");
    void *data = ctx_ptr(ctx, data);
    void *data_end = ctx_ptr(ctx, data_end);

    // 根据原始数据包解析出以太网包头信息
    struct ethhdr *eth;
    eth = (typeof(eth))data;
    // struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;

    // 判断是否为IP协议
    if (eth->h_proto != bpf_htons(ETH_P_IP))
        return XDP_PASS;

    // 解析IP包头信息
    struct iphdr *iph;
    iph = (typeof(iph))(eth + 1);
    // struct iphdr *iph = (void *)(eth + 1);
    if ((void *)(iph + 1) > data_end)
        return XDP_PASS;

    // 判断是否为ICMP协议
    if (iph->protocol != IPPROTO_ICMP)
        return XDP_PASS;

// CSUM_SIZE用于判断ICMP包长度合法性和计算ICMP包头checksum信息
#define CSUM_SIZE 40
    int csum_size = CSUM_SIZE;

    // 解析ICMP数据包头信息
    struct icmphdr *icmph;
    // icmph = (typeof(icmph))((void *)iph + (iph->ihl * 4)); // FAILED: R3 offset is outside of the packet
    icmph = (typeof(icmph))(iph + 1);
    // struct icmphdr *icmph = (void *)(iph + 1);
    if ((void *)(icmph) + csum_size > data_end)
        return XDP_PASS;

    // 判断是否为ICMP request类型包
    if (icmph->type != ICMP_ECHO)
        return XDP_PASS;

    // FAILED: int csum_size = iph->tot_len - sizeof(*iph); // R3 offset is outside of the packet
    // FAILED: int csum_size = data_end - (void *)icmph;    // R4 unbounded memory access, use 'var &= const' or 'if (var < const)'

    // 构造ICMP reply包头
    icmph->type = ICMP_ECHOREPLY;
    icmph->checksum = 0; // Note: reset and then checksum
    icmph->checksum = ipv4_csum(icmph, csum_size);

    // 构造IP包头
    __be32 daddr = iph->daddr;
    iph->daddr = iph->saddr;
    iph->saddr = daddr;
    iph->ttl = 64;
    iph->check = 0; // Note: reset and then checksum
    iph->check = ipv4_csum(iph, sizeof(*iph));

    // 构造以太网包头
    char dmac[ETH_ALEN];
    __builtin_memcpy(dmac, eth->h_dest, ETH_ALEN);
    __builtin_memcpy(eth->h_dest, eth->h_source, ETH_ALEN);
    __builtin_memcpy(eth->h_source, dmac, ETH_ALEN);

    bpf_printk("xdpping replay icmp echo reply\n");

    // 从当前网卡发送相应数据包,数据包不再进入内核协议栈处理
    return XDP_TX;
}

TC

学习项目

在群里听到听到大佬说使用 TC层模拟 ICMP 包响应不需要硬编码 CSUM_SIZE,于是在搜了一圈终于找到了一个现成的工具 ebpf-icmp-ping,测试过以后发现可以正常运行。想了一下刚好可以把这个纯 C 的工具改造为 Go+eBPF CO-RE 的项目,也可以作为自己第一个写的 Go+eBPF 的玩具。

项目地址:tc-icmp-ping

编译及运行

源码

部分 eBPF C 代码片段:

前面的逻辑还是做数据包头解析以及协议判断,MAC 地址和 IP 地址交换以及 ICMP TYPE 字段更新这里使用了 bpf_skb_store_bytes bpf helper 函数,计算 ICMP checksum 使用了 bpf_l4_csum_replace helper 函数。

小结

XDP 挂载点计算ICMP checksum逻辑需要的ICMP payload大小没有通用的计算方式,暂时只能写死,后续想到更好的方式再来更新;后面研究一下 bpf_l4_csum_replace 函数逻辑是否可以复用在 XDP 场景。

Last updated