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

GSO/TSO场景下eBPF tc添加的PPPoE头能否在分片中正确复制?

问题

我正在使用eBPF和tc在出口侧为转发及本地生成的数据包添加PPPoE头。由于GSO/TSO机制,遇到了超过MTU大小的数据包,观察到skb->gso_size=1452、skb->gso_segs=3、skb->len=4410等参数。我担心在tc程序中插入的MAC和PPPoE头,能否在GSO/TSO生成的每个分片中被正确包含。

我理解分片发生在IP层,而PPPoE工作在链路层,每个分片应是完整的链路层帧,但不确定GSO是否会在每个分片中复制MAC和PPPoE头。若确实如此,我该如何处理每个分片的PPPoE头len字段?或者有什么特定方法能确保每个分片被正确处理?

以下是我使用的eBPF代码:

SEC("tc")
int pppoe_egress(struct __sk_buff *skb) {
#define BPF_LOG_TOPIC "pppoe_egress"
    void *data_end = (void *)(long)skb->data_end;
    void *data = (void *)(long)skb->data;

    u32 pkt_sz = skb->len - 14;
    if (pkt_sz > pppoe_mtu) {
        bpf_log_info("egress package too large size: %u", pkt_sz);
        return TC_ACT_SHOT;
    }

    struct ethhdr *eth = (struct ethhdr *)(data);
    if ((void *)(eth + 1) > data_end) {
        bpf_log_info("package size smaller than ethhdr");
        return TC_ACT_SHOT;
    }

    if (eth->h_proto != ETH_IPV4 && eth->h_proto != ETH_IPV6) {
        bpf_log_info("egress eth proto is error: %x", eth->h_proto);
        return TC_ACT_PIPE;
    }

    u32 offset = 14;
    u8 protocol = 0;
    u16 mss_value = 0;
    u16 ppp_proto = ETH_PPP_IPV4;
    // DECAP support since Linux kernel 6.3
    u64 adj_room_flag = BPF_F_ADJ_ROOM_ENCAP_L3_IPV4;
    if (eth->h_proto == ETH_IPV6) {
        ppp_proto = ETH_PPP_IPV6;
        adj_room_flag = BPF_F_ADJ_ROOM_ENCAP_L3_IPV6;

        struct ipv6hdr *iph6;
        if (VALIDATE_READ_DATA(skb, &iph6, offset, sizeof(*iph6))) {
            return TC_ACT_SHOT;
        }
        protocol = iph6->nexthdr;
        offset = offset + 40;
        mss_value = pppoe_mtu - 40 - 20;
    } else {
        struct iphdr *iph;
        if (VALIDATE_READ_DATA(skb, &iph, offset, sizeof(*iph))) {
            return TC_ACT_SHOT;
        }
        protocol = iph->protocol;
        offset = offset + (iph->ihl * 4);
        mss_value = pppoe_mtu - (iph->ihl * 4) - 20;
    }

    if (protocol == IPPROTO_TCP) {
        mss_clamp(skb, offset, mss_value);
    }

    u16 l2_proto = bpf_htons(0x8864);
    bpf_skb_store_bytes(skb, 12, &l2_proto, sizeof(u16), 0);

    int result = bpf_skb_adjust_room(skb, 8, BPF_ADJ_ROOM_MAC, adj_room_flag);
    if (result) {
        bpf_log_info("egress adjust room error %d", result);
        return TC_ACT_SHOT;
    }

    struct pppoe_header pppoe = {
        .version_and_type = 0x11,
        .code = 0x00,
        .session_id = bpf_htons(session_id),
        .length = bpf_htons(pkt_sz + 2),
        .protocol = ppp_proto,
    };

    bpf_skb_store_bytes(skb, sizeof(struct ethhdr), &pppoe, sizeof(struct pppoe_header), 0);
    return TC_ACT_PIPE;
#undef BPF_LOG_TOPIC
}

解决方案

1. GSO/TSO分片的链路层头复制逻辑

Linux内核的GSO/TSO机制在分片时,会自动复制整个链路层头部(包括你添加的MAC和PPPoE头)到每个分片。GSO处理上层IP/TCP分片,而链路层头属于skb的head区域,分片过程中会完整复制该区域到每个新生成的skb,无需手动干预。

2. PPPoE头length字段的处理

当前代码使用原大包的长度计算length,会导致分片后每个PPPoE头的长度值错误。解决思路是:

  • 不要在tc阶段直接固定length值,或改为基于当前skb的实际IP payload长度计算;
  • 优先让GSO分片在PPPoE封装前完成,确保每个分片的IP包大小符合PPPoE MTU限制,此时length字段可以直接设置为对应IP包长度+2(PPP协议字段长度)。

3. 代码调整建议

(1)移除大包丢弃逻辑,允许GSO包通过

原代码中if (pkt_sz > pppoe_mtu)直接丢弃大包,会阻止GSO分片。替换为调整GSO参数:

// 替换原有的pkt_sz检查逻辑
if (skb->gso_size) {
    // 根据IP版本调整GSO分片大小,适配PPPoE MTU
    if (eth->h_proto == ETH_IPV4) {
        skb->gso_size = pppoe_mtu - (iph->ihl * 4);
    } else {
        skb->gso_size = pppoe_mtu - 40;
    }
}

(2)修正PPPoE length字段计算

基于当前skb的实际长度计算,而非原大包长度:

// 替换原有的length赋值
u32 ip_payload_len = skb->len - sizeof(struct ethhdr);
struct pppoe_header pppoe = {
    .version_and_type = 0x11,
    .code = 0x00,
    .session_id = bpf_htons(session_id),
    .length = bpf_htons(ip_payload_len + 2),
    .protocol = ppp_proto,
};

(3)添加GSO修正标志

调用bpf_skb_adjust_room时加入BPF_F_ADJ_ROOM_FIX_GSO,让内核自动修正GSO参数(需内核5.10+支持):

int result = bpf_skb_adjust_room(skb, 8, BPF_ADJ_ROOM_MAC, adj_room_flag | BPF_F_ADJ_ROOM_FIX_GSO);

(4)调整TCP MSS clamp值

确保TCP MSS适配PPPoE链路:

// IPv4 MSS = PPPoE MTU - IP头 - TCP头
mss_value = pppoe_mtu - (iph->ihl * 4) - 20;
// IPv6 MSS = PPPoE MTU - IPv6头 - TCP头
mss_value = pppoe_mtu - 40 - 20;

4. 额外注意事项

  • 若内核版本低于5.10,无法使用BPF_F_ADJ_ROOM_FIX_GSO,需手动调整skb->gso_segs参数;
  • 确认pppoe_mtu设置为PPPoE链路的实际MTU(通常为1492,即以太网MTU 1500减去8字节PPPoE头)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 10:30:00