XDP 与 TC:Linux 内核网络可观测性的双引擎

在上一篇文章中,我们从 strace 出发,系统梳理了 Linux 系统调用追踪的技术栈,并最终引入了 eBPF 作为现代性能分析的利器。本文将继续深入 eBPF 生态,将焦点从系统调用追踪转向网络数据包处理——具体而言,是 XDP(eXpress Data Path)和 TC(Traffic Control)这两大 eBPF 挂载点在实际网络可观测性工程中的应用。

XDP:数据包到达后的第一个战场

XDP 允许 eBPF 程序在数据包进入 Linux 内核网络栈的最早阶段介入——甚至在 socket buffer 分配之前。这意味着 XDP 程序运行在网卡驱动层的接收路径上,具有极高的处理效率。一个典型的 XDP 程序可以直接读取数据包的原始字节,并根据预定义的逻辑决定是丢弃、重定向还是放行该数据包。

XDP 的三种工作模式

  • Native XDP:eBPF 程序直接在网卡驱动内部执行,需要将 XDP 程序加载到支持该模式的驱动中。性能最优,但依赖硬件驱动支持。
  • Offloaded XDP:eBPF 程序被卸载到网卡硬件上执行(SmartNIC 场景),完全绕过 CPU,适合超大规模流量处理。
  • Generic XDP:作为 fallback 模式,在 Linux 通用网络层执行 eBPF 程序。性能略低于 Native 模式但不要求驱动支持。

对于大多数可观测性场景,Generic XDP 已足够使用。当需要监控大量流量但不希望分配完整 socket buffer 时,XDP 提供了一个理想的观测点:你可以在数据包进入内核协议栈之前就完成统计、采样和异常检测。

TC:流量控制层的精细观测

与 XDP 不同,TC eBPF 程序运行在内核流量控制子系统层,此时数据包已经封装为 sk_buff。TC 支持ingress 和 egress 两个方向,能够在协议栈的多个关键点介入。

TC 的主要优势在于:

  • 可以访问完整的 sk_buff 结构,获取更多协议元数据(如 socket、cgroup 信息)
  • 支持更复杂的分类和排队规则(与 qdisc 集成)
  • 能够修改数据包内容或添加/移除包头
  • 对容器场景友好——可以直接关联到 cgroup 级别的网络观测

实战:构建实时网络异常检测系统

下面展示一个基于 XDP 的 SYN Flood 检测与缓解系统。该系统在驱动层统计每个源 IP 的 SYN 包速率,当超过阈值时自动丢弃后续 SYN 包,从而实现内核态的 DDoS 防护。

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>

// 哈希表:记录每个源IP的SYN计数
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, __u32);    // 源IP
    __type(value, __u64);  // SYN计数
    __uint(max_entries, 10000);
} syn_count SEC(".maps");

SEC("xdp")
int syn_flood_detect(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;
    
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;
    
    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;
    
    if (ip->protocol != IPPROTO_TCP)
        return XDP_PASS;
    
    struct tcphdr *tcp = (void *)ip + (ip->ihl * 4);
    if ((void *)(tcp + 1) > data_end)
        return XDP_PASS;
    
    // 只关注SYN包(非ACK)
    if (!tcp->syn || tcp->ack)
        return XDP_PASS;
    
    __u32 src_ip = ip->saddr;
    __u64 *count = bpf_map_lookup_elem(&syn_count, &src_ip);
    __u64 now = bpf_ktime_get_ns();
    
    if (count) {
        // 速率检测:1秒内超过1000个SYN则丢弃
        if (*count > 1000 && (now - *count) < 1000000000ULL) {
            return XDP_DROP;
        }
        *count = now;  // 更新时间戳和计数
    } else {
        bpf_map_update_elem(&syn_count, &src_ip, &now, BPF_ANY);
    }
    
    return XDP_PASS;
}

TC 实战:容器网络流量分类

在容器化部署场景中,我们经常需要按 Pod/容器维度统计网络流量。TC eBPF 可以直接获取发送方的 cgroup 信息,配合 bpf_skb_cgroup_id() 辅助函数实现容器级别的网络可观测性。

// TC eBPF 程序:按cgroup统计流量
SEC("tc")
int cgroup_traffic_count(struct __sk_buff *skb) {
    __u64 cgroup_id = bpf_skb_cgroup_id(skb);
    __u64 pkt_len = skb->len;
    
    // 按cgroup聚合流量统计
    __u64 *bytes = bpf_map_lookup_elem(&cgroup_stats, &cgroup_id);
    if (bytes) {
        __sync_fetch_and_add(bytes, pkt_len);
    } else {
        bpf_map_update_elem(&stats, &cgroup_id, &pkt_len, BPF_ANY);
    }
    
    return TC_ACT_OK;
}

性能对比:选择 XDP 还是 TC

维度XDPTC
运行层级网卡驱动层(最早)流量控制子系统(较晚)
数据访问原始以太网帧完整 sk_buff
可观测信息包头(L2-L4)全部协议元数据 + cgroup + socket
处理吞吐最高(~24M pps/核)高(~5M pps/核)
修改能力可重定向/丢弃/放行可修改包头/排队/重写
驱动依赖需要驱动支持(Offloaded)无特殊要求

生产部署建议

在实际生产环境中,我们通常会组合使用 XDP 和 TC:

  • 第一层(XDP):用于高速流量采样、DDoS 防护、负载均衡重定向
  • 第二层(TC):用于精细流量分类、QoS 策略、容器网络监控
  • 第三层(用户态):将 BPF Map 中的统计数据导出到 Prometheus/Grafana 进行可视化

通过 libbpf 提供的 CO-RE(Compile Once, Run Everywhere)机制,可以编写与内核版本无关的 eBPF 程序,避免在生产环境中重新编译的痛苦。

结语

XDP 和 TC 代表了 Linux 网络可观测性的两个不同层级:XDP 追求极致性能,TC 提供丰富语义。理解两者的差异和协同方式,是构建现代云原生网络观测体系的关键一步。结合前一篇文章讨论的系统调用追踪技术,我们现在已经有了从系统调用到网络数据包的完整观测能力——这正是 eBPF 最初的设计愿景:一个统一的内核可编程观测平台。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部