3. 网络包处理路径与TC挂载点

要正确使用 TC,必须先弄清楚一个数据包在 Linux 内核里究竟走了哪条路、在哪些位置可以被“拦截”和“整形”。TC 不是挂在网卡驱动上的万能开关,它依赖内核协议栈在收发路径上预留的若干钩子点。理解这些钩子点的位置、方向和限制,才能判断一条规则为什么生效、为什么无效,以及应该把 qdisc 挂到哪个设备、哪个方向。

一、发送路径(egress):从协议栈到网卡队列

当本机应用调用 send() 发送数据时,数据先经过传输层(TCP/UDP)封装,再进入网络层完成路由查找,确定出口设备和下一跳。路由确定后,包进入邻居子系统解析目的 MAC,随后到达本节最关键的节点——出口设备的 qdisc 层。TC 的 egress 处理就发生在这里:内核在把包交给驱动之前,先让该设备的根 qdisc 处理。

egress 方向是 TC 能力最完整的方向。根 qdisc 之下可以挂 class(分类),class 之下再挂子 qdisc,形成树状结构;filter(分类器)根据 IP、端口、fwmark、优先级等条件把包分到不同 class,从而实现限速、优先级调度、延迟控制。处理完成后,包被放入驱动的发送环(TX ring),由网卡 DMA 发出。如果队列已满,包会在 qdisc 层被丢弃或按策略处理,这正是 tc -s qdisc 中 drops、backlog 等计数的来源。

二、接收路径(ingress):从网卡到协议栈

接收方向的数据流方向相反:网卡收到帧后通过 DMA 写入内存,触发中断或 NAPI 轮询,驱动把 skb 交给内核。此时包尚未进入协议栈的 IP 层,而是先经过一个特殊的钩子——ingress qdisc。TC 的 ingress 处理就在这里发生。

ingress 方向有一个重要限制:它不能做整形(shaping),只能做策略(policing)。原因是包已经到达,无法像 egress 那样通过排队延迟来平滑速率,只能选择放行或丢弃。ingress qdisc 本身是一个特殊的无类 qdisc,通常配合 ingress 句柄和 filter 使用,把匹配的流量重定向到 IFB(Intermediate Functional Block)虚拟设备,再在 IFB 的 egress 方向做真正的整形。这是实现“入向限速”的标准做法。

三、协议栈钩子与TC介入时机

Linux 内核在收发路径上提供了多个钩子点,TC 只是其中之一。理解它们的分工有助于判断该用哪个工具:

  • ingress qdisc:位于驱动收包之后、IP 层之前,可丢弃、可重定向到 IFB,不能排队整形。
  • egress qdisc:位于路由之后、驱动发送之前,可分类、排队、整形、调度,能力最完整。
  • Netfilter(iptables/nftables):位于 IP 层,可做过滤、NAT、mark,常与 TC 配合,用 fwmark 给包打标记供 TC 分类。
  • XDP/eBPF:更早,在驱动层即可处理,性能高但编程复杂,适合高速丢弃和转发。

一个常见的组合是:用 iptables 在 PREROUTING 或 OUTPUT 链给特定流量打 fwmark,再用 TC filter 匹配 fwmark 分类。这样既利用了 Netfilter 灵活的匹配能力,又利用了 TC 的队列调度能力。

四、设备队列与数据流向

每个网络设备都有独立的 qdisc 实例。默认情况下,egress 使用 pfifo_fast(多队列网卡上可能是 mq),ingress 默认没有 qdisc,需要手动添加。qdisc 的句柄用 major:minor 表示,例如 1: 表示根 qdisc,1:10 表示其下的 class 10。

数据在 qdisc 树中的流向是:根 qdisc 收到包 → 调用分类器确定 class → 包进入对应 class 的子 qdisc → 子 qdisc 决定排队、延迟或丢弃 → 出队交给驱动。理解这个流向,就能明白为什么 filter 要挂在正确的位置、为什么子 qdisc 的速率不能超过父 class 的速率。

Linux 网络包处理路径与 TC 挂载点 发送路径 Egress 应用 send() 传输层 TCP/UDP 网络层 路由查找 egress qdisc 分类/排队/整形 驱动 TX ring DMA 发送 网卡发出 egress qdisc 内部结构 根 qdisc class 子 qdisc filter 可整形 shaping · 可排队 · 可调度 · 能力完整 接收路径 Ingress 网卡收包 驱动/NAPI DMA 写入 ingress qdisc 策略/丢弃/重定向 IP 层 Netfilter 传输层 TCP/UDP 应用 recv() ingress 限制:只能 policing,不能 shaping 需整形时 → 重定向到 IFB 设备,在 IFB 的 egress 方向处理 IFB 虚拟设备 redirect IFB egress qdisc 整形 shaping TC 挂载点 协议栈处理

五、关键判断:该挂在哪里

面对一个具体的 QoS 需求,可以按以下逻辑判断挂载点:

  • 限制本机发出的流量:在出口设备的 egress 挂根 qdisc,用 HTB 或 TBF 做整形。
  • 限制本机收到的流量:在入口设备挂 ingress qdisc,用 filter 重定向到 IFB,再在 IFB 的 egress 做整形。
  • 只做丢弃不做延迟:ingress qdisc 直接配合 filter 的 police 动作即可,无需 IFB。
  • 需要按应用或用户分类:先用 iptables 打 fwmark,再用 TC filter 匹配 fwmark。
  • 多队列网卡:注意 mq qdisc 会把流量分散到多个硬件队列,整形规则需要挂到每个子队列或改用其他结构。
判断一个 TC 规则是否生效,最直接的方法是观察 tc -s qdisc show dev eth0 的计数变化:sent 字节数增长说明包经过了该 qdisc,dropped 增长说明发生了丢弃,backlog 非零说明队列在排队。如果计数始终为零,通常意味着包没有走到这个 qdisc,需要检查设备名、方向、句柄和 filter 是否匹配。