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

使用XDP转发lo网卡127.0.0.1:9999到8080失败,请求排查问题

问题描述

我尝试用XDP把lo网卡上发往127.0.0.1:9999的流量转发到127.0.0.1:8080,以此访问启动的Golang Web服务,但始终无法建立连接。

执行命令cat /sys/kernel/debug/tracing/trace_pipe得到输出:

bpf_trace_printk: source3879 target 36895

以下是我的XDP代码:

//+build ignore
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_endian.h>
#define MAX_CPU 128
#define ETH_P_IP        0x0800

struct pkt_meta {
    union {
        __be32 src;
        __be32 srcv6[4];
    };
    union {
        __be32 dst;
        __be32 dstv6[4];
    };
    __u16 port16[2];
    __u16 l3_proto;
    __u16 l4_proto;
    __u16 data_len;
    __u16 pkt_len;
    __u32 seq;
};

static __always_inline bool parse_tcp(struct tcphdr *tcp,
                      struct pkt_meta *pkt)
{
    pkt->port16[0] = tcp->source;
    pkt->port16[1] = tcp->dest;
    pkt->seq = tcp->seq;
    return true;
}

static __always_inline bool parse_ip4(struct iphdr *iph,
                      struct pkt_meta *pkt)
{
    if (iph->ihl != 5)
        return false;
    pkt->src = iph->saddr;
    pkt->dst = iph->daddr;
    pkt->l4_proto = iph->protocol;
    return true;
}

SEC("xdp")
int process_packet(struct xdp_md *ctx)
{
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;
    struct tcphdr *tcp;
    struct iphdr *iph;
    struct pkt_meta pkt = {};
    __u32 off;

    /* parse packet for IP Addresses and Ports */
    off = sizeof(struct ethhdr);
    if (data + off > data_end)
        return XDP_PASS;

    pkt.l3_proto = bpf_htons(eth->h_proto);

    if (pkt.l3_proto == ETH_P_IP) {
        iph = data + off;
        if ((void *)(iph + 1) > data_end)
            return false;
        if (!parse_ip4(iph, &pkt))
            return XDP_PASS;
        off += sizeof(struct iphdr);
    }
    if (data + off > data_end)
        return XDP_PASS;

    if (pkt.l4_proto == IPPROTO_TCP) {
        tcp = data + off;
        if ((void *)(tcp + 1) > data_end)
            return false;
        off += sizeof(struct tcphdr);

        if (pkt.port16[1] == bpf_htons(9999) && pkt.dst == bpf_htonl(0x7f000001)) {
            tcp->dest = bpf_htons(8080);
            iph->daddr = bpf_htonl(0x7f000001);
            bpf_printk("source%d target %d\n", pkt.port16[1], tcp->dest);
            return XDP_TX;
        }
    }
    return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";

问题排查与修复

核心问题分析

  1. 字节序打印误导
    打印输出里的3879是9999的网络字节序转主机字节序前的数值(9999十六进制为0x270F,网络大端存储为0x0F27,对应十进制3879),36895是8080的网络字节序数值,说明端口修改逻辑确实触发了,但连接失败另有原因。

  2. TCP校验和未更新
    修改TCP目标端口后,TCP头部的校验和会失效,内核后续处理时会直接丢弃该包,这是连接失败的核心原因。

  3. 错误的XDP返回值
    代码中存在返回false的逻辑,而XDP程序的返回值必须是XDP_*系列常量(如XDP_PASS、XDP_DROP),返回false会被内核解析为XDP_ABORTED,可能导致包被意外丢弃。

  4. TCP端口解析逻辑缺失
    在TCP处理分支中未调用parse_tcp函数,pkt.port16[1]为初始化的0值,理论上不会触发后续的端口匹配逻辑——若实际触发了,说明运行的代码与贴出的代码存在差异,但仍需补全该逻辑保证正确性。

具体修复步骤

1. 补全TCP端口解析逻辑

在TCP处理分支中调用parse_tcp,确保pkt.port16被正确赋值:

if (pkt.l4_proto == IPPROTO_TCP) {
    tcp = data + off;
    if ((void *)(tcp + 1) > data_end)
        return XDP_PASS; // 替换原返回false的逻辑
    parse_tcp(tcp, &pkt); // 新增调用,解析TCP端口到pkt结构体
    off += sizeof(struct tcphdr);
    // ... 后续匹配逻辑
}

2. 重新计算TCP校验和

修改端口后,使用bpf_l4_csum_replace辅助函数更新TCP校验和:

if (pkt.port16[1] == bpf_htons(9999) && pkt.dst == bpf_htonl(0x7f000001)) {
    __u16 old_port = bpf_htons(9999);
    __u16 new_port = bpf_htons(8080);
    
    tcp->dest = new_port;
    // 重新计算TCP校验和
    bpf_l4_csum_replace(ctx, &tcp->check, old_port, new_port, sizeof(old_port));
    
    // ... 打印与返回逻辑
}

3. 修正错误返回值

将所有返回false的位置替换为XDP_PASS:

if ((void *)(iph + 1) > data_end)
    return XDP_PASS;

4. 优化打印逻辑(可选)

将网络字节序端口转为主机字节序后打印,避免数值混淆:

bpf_printk("source%d target %d\n", bpf_ntohs(pkt.port16[1]), bpf_ntohs(tcp->dest));

5. 确认XDP挂载方式

确保程序挂载到lo网卡的通用XDP模式(lo网卡不支持驱动XDP),挂载命令示例:

ip link set dev lo xdp obj your_program.o sec xdp

修复后的完整代码
//+build ignore
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_endian.h>
#define MAX_CPU 128
#define ETH_P_IP        0x0800

struct pkt_meta {
    union {
        __be32 src;
        __be32 srcv6[4];
    };
    union {
        __be32 dst;
        __be32 dstv6[4];
    };
    __u16 port16[2];
    __u16 l3_proto;
    __u16 l4_proto;
    __u16 data_len;
    __u16 pkt_len;
    __u32 seq;
};

static __always_inline bool parse_tcp(struct tcphdr *tcp,
                      struct pkt_meta *pkt)
{
    pkt->port16[0] = tcp->source;
    pkt->port16[1] = tcp->dest;
    pkt->seq = tcp->seq;
    return true;
}

static __always_inline bool parse_ip4(struct iphdr *iph,
                      struct pkt_meta *pkt)
{
    if (iph->ihl != 5)
        return false;
    pkt->src = iph->saddr;
    pkt->dst = iph->daddr;
    pkt->l4_proto = iph->protocol;
    return true;
}

SEC("xdp")
int process_packet(struct xdp_md *ctx)
{
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;
    struct tcphdr *tcp;
    struct iphdr *iph;
    struct pkt_meta pkt = {};
    __u32 off;

    /* parse packet for IP Addresses and Ports */
    off = sizeof(struct ethhdr);
    if (data + off > data_end)
        return XDP_PASS;

    pkt.l3_proto = bpf_htons(eth->h_proto);

    if (pkt.l3_proto == ETH_P_IP) {
        iph = data + off;
        if ((void *)(iph + 1) > data_end)
            return XDP_PASS;
        if (!parse_ip4(iph, &pkt))
            return XDP_PASS;
        off += sizeof(struct iphdr);
    }
    if (data + off > data_end)
        return XDP_PASS;

    if (pkt.l4_proto == IPPROTO_TCP) {
        tcp = data + off;
        if ((void *)(tcp + 1) > data_end)
            return XDP_PASS;
        parse_tcp(tcp, &pkt);
        off += sizeof(struct tcphdr);

        if (pkt.port16[1] == bpf_htons(9999) && pkt.dst == bpf_htonl(0x7f000001)) {
            __u16 old_port = bpf_htons(9999);
            __u16 new_port = bpf_htons(8080);
            
            tcp->dest = new_port;
            bpf_l4_csum_replace(ctx, &tcp->check, old_port, new_port, sizeof(old_port));
            
            bpf_printk("source%d target %d\n", bpf_ntohs(pkt.port16[1]), bpf_ntohs(tcp->dest));
            return XDP_TX;
        }
    }
    return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 18:47:26