1. Linux TC与QoS工程导论

在网络工程中,我们经常遇到这样的场景:一条出口带宽为 100 Mbps 的链路,同时承载着交互式 SSH 会话、批量文件传输和实时语音流量。当文件传输占满带宽时,SSH 输入出现明显卡顿,语音出现断续。问题并不在于带宽总量不足,而在于所有流量被同等对待,缺乏区分与调度。Linux TC(Traffic Control,流量控制)正是为解决这类问题而生的内核子系统。

流量控制要解决的基本问题

流量控制的核心矛盾可以归纳为三个层面。第一是带宽分配:当多个流竞争同一出口时,谁先发送、谁后发送、各占多少比例。第二是时延与抖动控制:交互流量和实时流量对排队时延极其敏感,而大流量会通过缓冲区膨胀(bufferbloat)显著抬高时延。第三是突发与整形:入口流量往往具有突发性,直接注入链路会造成瞬时拥塞,需要通过令牌桶等机制平滑输出。

从排队论角度看,网络设备本质上是一个或多个队列加上一个调度器。当到达速率超过服务速率时,数据包进入队列等待。队列长度、调度策略和丢弃策略共同决定了时延、丢包和公平性。TC 提供的正是对这些队列行为的可编程控制能力。

QoS 目标与 TC 的能力边界

QoS(服务质量)在工程上通常被拆解为可度量的目标:带宽保证、时延上限、抖动范围、丢包率以及公平性。TC 能够实现的主要手段包括:

  • 分类(Classification):依据 IP、端口、DSCP、防火墙标记等将报文归入不同类别。
  • 整形(Shaping):通过令牌桶限制类的发送速率,超出部分缓存而非立即丢弃。
  • 调度(Scheduling):在多个类之间按优先级、权重或时间片分配发送机会。
  • 丢弃(Policing/Dropping):对超出约定速率的流量直接丢弃或标记。
  • 重标记(Marking):修改 DSCP 或防火墙标记,供下游设备继续处理。

需要明确的是,TC 主要作用于出口方向(egress),因为 Linux 只能在报文离开协议栈、交给网卡驱动之前对其进行排队和调度。入口方向的流量控制能力有限,通常依赖 ingress qdisc 配合 policer 或 IFB 设备重定向后再整形。

TC 在 Linux 网络栈中的位置

理解 TC 的位置,关键在于把握数据包从应用层到网卡的完整路径。以发送方向为例:应用调用 send() 写入 socket,数据经过 TCP/IP 协议栈封装,进入网络设备的发送队列。此时,内核的流量控制层介入,决定该报文进入哪个 qdisc(排队规则)、哪个 class(类),以及何时被 dequeue 交给驱动。驱动再将报文放入网卡的发送环,最终由硬件发出。

因此,TC 位于协议栈与网卡驱动之间,是软件层面的最后一道流量管理关口。它不改变路由决策,也不替代防火墙,而是在报文已经确定出口设备之后,对发送时机和顺序进行精细控制。

Linux 发送路径中 TC 的位置 应用层 / Socket 层 send() / write() → 用户数据进入内核 TCP/IP 协议栈 传输层分段 → 网络层路由决策 → 确定出口设备 路由查找、Netfilter 钩子在此阶段完成 流量控制层(TC / QoS) 根 qdisc 分类 / 过滤规则 默认类 / 优先级映射 子类 / 子 qdisc HTB / CBQ / PRIO 令牌桶速率限制 调度与丢弃 FIFO / SFQ / RED dequeue 时机控制 网卡驱动 / 发送环 DMA 映射 → 硬件发送 → 物理链路 核心控制点 入口方向(ingress) 能力有限,需 IFB 重定向 才能进行整形与调度

TC 的整体架构:qdisc、class 与 filter

TC 的架构由三个核心概念构成,理解它们之间的关系是后续所有配置的基础。

qdisc(排队规则)是 TC 的基本单元,每个网络设备至少有一个根 qdisc。qdisc 分为两类:无类 qdisc(如 pfifo_fast、fq_codel)直接管理一个队列,不区分流量类型;有类 qdisc(如 HTB、CBQ、PRIO)内部包含多个类,每个类可以挂载自己的子 qdisc,形成树状结构。

class(类)只存在于有类 qdisc 内部,用于承载特定类型的流量。每个类可以分配独立的速率、优先级和子 qdisc。例如在 HTB 中,根类下可以划分“交互流量”“批量流量”“默认流量”三个子类,分别设置不同的保证速率和上限速率。

filter(过滤器)决定报文进入哪个类。过滤器挂在 qdisc 或 class 上,依据报文特征进行匹配。常用的过滤器包括 u32(基于报文头部字段)、fw(基于防火墙标记)、flower(基于流分类)等。当报文到达时,TC 依次调用过滤器,第一个匹配成功的过滤器决定报文的类别;若无匹配,则进入默认类。

这三者的关系可以概括为:qdisc 提供队列和调度框架,class 划分流量类别,filter 将报文映射到类别。配置 TC 的过程,本质上就是构建这棵“qdisc-class-filter”树,并为每个节点设定参数。

一个直观的示例场景

假设一台 Linux 网关的出口带宽为 100 Mbps,需要同时服务三类流量:VoIP 语音(约 2 Mbps,要求低时延)、交互式 SSH(约 1 Mbps,要求低时延)、批量下载(剩余带宽,可容忍时延)。一个典型的 HTB 配置思路如下:

# 在 eth0 上建立根 HTB qdisc,默认类为 30
tc qdisc add dev eth0 root handle 1: htb default 30

# 根类:总速率 100 Mbps
tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit

# 类 1:10 —— VoIP,保证 2 Mbps,上限 5 Mbps,优先级最高
tc class add dev eth0 parent 1:1 classid 1:10 htb \
    rate 2mbit ceil 5mbit prio 0

# 类 1:20 —— SSH,保证 1 Mbps,上限 3 Mbps
tc class add dev eth0 parent 1:1 classid 1:20 htb \
    rate 1mbit ceil 3mbit prio 1

# 类 1:30 —— 批量下载,使用剩余带宽
tc class add dev eth0 parent 1:1 classid 1:30 htb \
    rate 10mbit ceil 100mbit prio 2

# 为每个类挂载 SFQ,避免单一流占满类内带宽
tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10

# 过滤器:将 VoIP 流量(UDP 端口 5060)归入 1:10
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
    match ip dport 5060 0xffff flowid 1:10

# 将 SSH 流量(TCP 端口 22)归入 1:20
tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 \
    match ip dport 22 0xffff flowid 1:20

这段配置的语义是:VoIP 和 SSH 获得明确的带宽保证和最高优先级,批量下载只能使用剩余带宽。当链路空闲时,批量下载可以借用高达 100 Mbps;当 VoIP 和 SSH 有流量时,HTB 会优先满足它们的保证速率。SFQ 子 qdisc 则确保同一类内部的多个流公平竞争,避免单个 TCP 连接独占。

需要强调的是,上述参数(2 Mbps、5 Mbps、端口号等)属于教学示例,实际部署时应根据链路容量、流量特征和业务 SLA 进行测量与调整。HTB 的 rate 是保证速率,ceil 是上限速率,prio 数值越小优先级越高。

学习本课程需要建立的认知框架

在后续章节中,我们将逐步展开 TC 的各个组件。建议读者在学习过程中始终围绕以下问题思考:当前配置作用于哪个网络设备、哪个方向?报文经过哪些 qdisc 和 class?过滤器依据什么条件匹配?每个类的 rate、ceil、prio 如何影响调度结果?只有把配置命令与数据包的实际路径对应起来,才能真正掌握 TC 的工程方法。

TC 不是孤立的工具,它与 Netfilter、iptables/nftables、路由策略、网卡多队列等机制协同工作。例如,常用 iptables 的 MARK 目标为报文打标记,再用 TC 的 fw 过滤器根据标记分类,从而把复杂的匹配逻辑交给防火墙层处理。理解这些协作关系,有助于设计出清晰、可维护的 QoS 方案。