You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

veth挂载的XDP无法处理非直连流量问题咨询

问题:Veth上XDP实现NAT时SNAT未生效,XDP是否仅处理直连流量?

实验拓扑

实验环境拓扑图

问题现象

在veth0上执行ping 192.168.50.3时,veth-xdp的DNAT功能正常,能将目的IP修改为172.10.1.2,数据包可成功到达ns1。但ICMP回复包从ns1返回至veth0时,SNAT未生效,终端提示如下:
终端截图:ping失败提示DIFFERENT ADDRESS!

所用XDP程序

SEC("xdp")
int xdp_redirect_func(struct xdp_md *ctx)
{
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    int action = XDP_PASS;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
    {
        action = XDP_DROP;
        goto out;
    }

    if (eth->h_proto != htons(ETH_P_IP)) goto out;

    struct iphdr *ip = data + sizeof(struct ethhdr);
    if ((void *)(ip + 1) > data_end)
    {
        action = XDP_DROP;
        goto out;
    }

    __u8 protocol = ip->protocol;
    if (protocol != IPPROTO_TCP && protocol != IPPROTO_ICMP &&
        protocol != IPPROTO_UDP)
    {
        goto out;
    }

    __u32 old_src_ip = ip->saddr;
    __u32 old_dst_ip = ip->daddr;
    __u16 original_checksum = ip->check;

    __u32 *new_dst_ip = bpf_map_lookup_elem(&dnat_map, &old_dst_ip);
    if (new_dst_ip)
    {
        ip->daddr = *new_dst_ip;
        ip->check = update_checksum(original_checksum, (__u16)(old_dst_ip >> 16),
                                    (__u16)(*new_dst_ip >> 16));
        ip->check = update_checksum(ip->check, (__u16)(old_dst_ip & 0xFFFF),
                                    (__u16)(*new_dst_ip & 0xFFFF));
        goto out;
    }

    __u32 *new_src_ip = bpf_map_lookup_elem(&snat_map, &old_src_ip);
    if (new_src_ip)
    {
        ip->saddr = *new_src_ip;
        ip->check = update_checksum(original_checksum, (__u16)(old_src_ip >> 16),
                                    (__u16)(*new_src_ip >> 16));
        ip->check = update_checksum(ip->check, (__u16)(old_src_ip & 0xFFFF),
                                    (__u16)(*new_src_ip & 0xFFFF));
    }

out:
    return xdp_stats_record_action(ctx, action);
}

疑问

  1. 挂载在veth上的XDP程序是否仅能处理直连流量?
  2. 如何让veth-xdp正确处理其他veth接口转发的流量?

解答

关于veth上XDP的流量处理范围

veth上的XDP程序不是仅处理直连流量,它能处理所有经过该veth接口的数据包——不管是来自直连端点的流量,还是内核转发过来的流量。但要注意:XDP运行在网络栈的最底层(虚拟网卡入口处),数据包到达XDP程序的时机早于内核路由、iptables等逻辑,所以需要确保流量确实会经过挂载XDP的veth接口。

SNAT未生效的原因及修复方案

从代码和现象来看,SNAT未生效的核心问题是匹配逻辑错误:
当ICMP回复包从ns1返回时,数据包的源IP是172.10.1.2,目的IP是veth0的IP(比如192.168.50.1)。现有代码用old_src_ip(即172.10.1.2)查询snat_map,但你的映射表中大概率没有这个条目——实际需要的是反向会话匹配:根据回复包的目的IP(原请求的源IP)和源IP(原请求DNAT后的目的IP),找到要替换的源IP(原请求的目的IP192.168.50.3)。

具体修复步骤:

  1. 改用双向会话表替代独立的DNAT/SNAT表
    定义包含原IP和转换后IP的会话结构体,用哈希表存储正向(请求)和反向(回复)的会话映射,确保两种方向的流量都能匹配到对应的NAT规则。

  2. 调整流量处理顺序
    先检查是否是回复包(通过反向IP组合查询会话表),再处理请求包的DNAT逻辑,避免处理完DNAT就直接跳出导致回复包未被处理。

  3. 补充ICMP校验和更新
    ICMP包的校验和依赖IP伪首部,修改IP源/目的IP后必须同步更新ICMP校验和,否则回复包会被内核或端点丢弃。

  4. 确认流量路径正确性
    检查ns1的路由表,确保回复包的默认路由指向挂载XDP的veth端点;用tcpdump在veth两端抓包,确认回复包确实到达了XDP程序所在的接口。

示例代码调整思路

// 定义NAT会话结构体
struct nat_session {
    __u32 orig_src;
    __u32 orig_dst;
    __u32 new_src;
    __u32 new_dst;
};

// 定义会话表哈希map(键为{源IP, 目的IP, 协议},值为struct nat_session)
struct bpf_map_def SEC("maps") nat_session_map = {
    .type = BPF_MAP_TYPE_HASH,
    .key_size = sizeof(struct {__u32 src; __u32 dst; __u8 proto;}),
    .value_size = sizeof(struct nat_session),
    .max_entries = 1024,
};

// 原DNAT规则表
struct bpf_map_def SEC("maps") dnat_map = {
    .type = BPF_MAP_TYPE_HASH,
    .key_size = sizeof(__u32),
    .value_size = sizeof(__u32),
    .max_entries = 64,
};

SEC("xdp")
int xdp_nat_func(struct xdp_md *ctx)
{
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    int action = XDP_PASS;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end) {
        action = XDP_DROP;
        goto out;
    }
    if (eth->h_proto != htons(ETH_P_IP)) goto out;

    struct iphdr *ip = data + sizeof(struct ethhdr);
    if ((void *)(ip + 1) > data_end) {
        action = XDP_DROP;
        goto out;
    }
    __u8 protocol = ip->protocol;
    if (protocol != IPPROTO_TCP && protocol != IPPROTO_ICMP && protocol != IPPROTO_UDP) goto out;

    __u32 src = ip->saddr;
    __u32 dst = ip->daddr;
    __u16 orig_ip_check = ip->check;

    // 1. 处理回复包:反向查询会话表
    struct {__u32 src; __u32 dst; __u8 proto;} rev_key = {dst, src, protocol};
    struct nat_session *rev_sess = bpf_map_lookup_elem(&nat_session_map, &rev_key);
    if (rev_sess) {
        // 执行SNAT:将源IP替换为原请求的目的IP
        ip->saddr = rev_sess->orig_dst;
        // 更新IP校验和
        ip->check = update_checksum(orig_ip_check, (__u16)(src >> 16), (__u16)(rev_sess->orig_dst >> 16));
        ip->check = update_checksum(ip->check, (__u16)(src & 0xFFFF), (__u16)(rev_sess->orig_dst & 0xFFFF));

        // 更新ICMP校验和(如果是ICMP包)
        if (protocol == IPPROTO_ICMP) {
            struct icmphdr *icmp = (void*)ip + sizeof(struct iphdr);
            if ((void*)icmp + 1 > data_end) goto out;
            __u16 orig_icmp_check = icmp->checksum;
            icmp->checksum = update_checksum(orig_icmp_check, (__u16)(src >> 16), (__u16)(rev_sess->orig_dst >> 16));
            icmp->checksum = update_checksum(icmp->checksum, (__u16)(src & 0xFFFF), (__u16)(rev_sess->orig_dst & 0xFFFF));
        }
        goto out;
    }

    // 2. 处理请求包:查询DNAT规则并创建会话
    struct {__u32 src; __u32 dst; __u8 proto;} req_key = {src, dst, protocol};
    struct nat_session *req_sess = bpf_map_lookup_elem(&nat_session_map, &req_key);
    if (!req_sess) {
        __u32 *new_dst = bpf_map_lookup_elem(&dnat_map, &dst);
        if (new_dst) {
            struct nat_session new_sess = {
                .orig_src = src,
                .orig_dst = dst,
                .new_src = src, // 如需固定SNAT IP,替换为对应值
                .new_dst = *new_dst
            };
            // 存入正向和反向会话
            bpf_map_update_elem(&nat_session_map, &req_key, &new_sess, BPF_NOEXIST);
            struct {__u32 src; __u32 dst; __u8 proto;} rev_key2 = {*new_dst, src, protocol};
            bpf_map_update_elem(&nat_session_map, &rev_key2, &new_sess, BPF_NOEXIST);

            // 执行DNAT
            ip->daddr = *new_dst;
            ip->check = update_checksum(orig_ip_check, (__u16)(dst >> 16), (__u16)(*new_dst >> 16));
            ip->check = update_checksum(ip->check, (__u16)(dst & 0xFFFF), (__u16)(*new_dst & 0xFFFF));

            // 更新ICMP校验和
            if (protocol == IPPROTO_ICMP) {
                struct icmphdr *icmp = (void*)ip + sizeof(struct iphdr);
                if ((void*)icmp + 1 > data_end) goto out;
                __u16 orig_icmp_check = icmp->checksum;
                icmp->checksum = update_checksum(orig_icmp_check, (__u16)(dst >> 16), (__u16)(*new_dst >> 16));
                icmp->checksum = update_checksum(icmp->checksum, (__u16)(dst & 0xFFFF), (__u16)(*new_dst & 0xFFFF));
            }
        }
    }

out:
    return xdp_stats_record_action(ctx, action);
}

额外注意事项

  • 确保内核版本支持veth的native XDP(Linux 4.18+),低版本内核可使用generic模式,但性能会下降。
  • 用bpftool map dump命令查看会话表和DNAT表的条目,确认映射是否正确创建。
  • 抓包时同时监控veth的两端,对比数据包经过XDP前后的IP变化,快速定位问题环节。

内容的提问来源于stack exchange,提问作者Heason H

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 17:35:56