tc命令是Linux中用于网络流量控制的强大工具,可对出站流量进行限速、优先级划分和整形。其核心机制基于qdisc(排队规则)、class(类)和filter(过滤器)三大组件。tbf适用于简单限速,htb则支持复杂的分级带宽管理,如为SSH、HTTP分配不同优先级和带宽。常见问题包括误用于入站流量、旧规则残留、过滤器匹配错误及测试方法不当。排查需检查规则状态与统计信息。tc常与iptables协同:iptables在mangle表中通过MARK为目标流量打标记,tc使用fw过滤器根据标记将流量导向特定class进行调度,从而实现精细化QoS策略。
tc
命令是 Linux 系统中一个强大但配置起来也颇为复杂的流量控制工具。简单来说,它就是你操作系统层面用来精细管理网络数据包“排队”和“发送”的“交通警察”。它允许你对出站(甚至部分入站)流量进行限速、优先级设置、流量整形,从而优化网络性能,确保关键应用的服务质量(QoS)。在我看来,掌握
tc
的基本原理和常用配置,对于任何需要进行网络性能调优的系统管理员或开发者来说,都是一项非常有价值的技能,尽管它确实有点学习曲线。
解决方案
使用
tc
命令进行流量控制的核心在于理解其三个主要概念:
qdisc
(queuing discipline,排队规则)、
class
(类) 和
filter
(过滤器)。
-
qdisc
(排队规则):
这是流量控制的基础。每个网络接口(如eth0
)都有一个根
qdisc
。所有出站数据包都必须经过这个
qdisc
。你可以选择不同的
qdisc
类型来定义数据包如何被排队和调度。
-
class
(类):
某些qdisc
(如
HTB
)是分层的,允许你在一个
qdisc
下创建多个“类”。每个类可以有自己的带宽限制和优先级,并且可以进一步包含子
qdisc
和子类。
-
filter
(过滤器):
过滤器用于将特定的数据包导向特定的class
或
qdisc
。你可以根据源/目的 IP、端口、协议等多种条件来匹配数据包。
基本限速示例 (使用
tbf
– Token Bucket Filter):
tbf
是最简单的
qdisc
之一,适用于对整个接口的出站流量进行硬性限速。
# 1. 清除eth0接口上可能存在的旧规则,避免冲突 sudo tc qdisc del dev eth0 root # 2. 在eth0接口上添加一个tbf qdisc,限制出站速度为100kbit/s, # 允许突发流量达到10kbit,并设置延迟为70ms(数据包在队列中等待的最长时间) sudo tc qdisc add dev eth0 root tbf rate 100kbit burst 10kbit latency 70ms # 3. 验证规则是否生效 sudo tc qdisc show dev eth0
分级限速与优先级示例 (使用
HTB
– Hierarchical Token Bucket):
HTB
是
tc
中最常用也最强大的
qdisc
之一,它允许你创建复杂的流量分级和优先级策略。
假设我们想将
eth0
的总出站带宽限制在 100Mbps,然后为 SSH 流量(端口 22)分配 50Mbps 的高优先级带宽,为 HTTP 流量(端口 80)分配 20Mbps 的中优先级带宽,其余流量则走默认的低优先级通道。
# 1. 清除eth0接口上的所有现有规则 sudo tc qdisc del dev eth0 root # 2. 创建一个HTB根qdisc。handle 1: 是这个qdisc的标识符。 # default 20 表示所有未被特定过滤器匹配的流量都将进入classid 1:20。 sudo tc qdisc add dev eth0 root handle 1: htb default 20 # 3. 创建一个主类 (parent 1:),定义总的可用带宽。 # classid 1:1 是这个主类的标识符。 # rate 100mbit 是承诺带宽,ceil 100mbit 是最大可用带宽。 sudo tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit ceil 100mbit # 4. 创建子类,分配给不同类型的流量。 # a. SSH流量类 (高优先级) # parent 1:1 表示它是主类1:1的子类。classid 1:10 是其标识符。 # rate 50mbit 是承诺带宽,ceil 80mbit 是在有空闲时可以突发到的最大带宽。 # prio 1 表示优先级最高(数字越小优先级越高)。 sudo tc class add dev eth0 parent 1:1 classid 1:10 htb rate 50mbit ceil 80mbit prio 1 # b. HTTP流量类 (中优先级) sudo tc class add dev eth0 parent 1:1 classid 1:20 htb rate 20mbit ceil 50mbit prio 2 # c. 默认流量类 (低优先级,用于所有未匹配的流量) # 这个类的rate和ceil可以根据实际情况设置,或者让它共享剩余带宽。 sudo tc class add dev eth0 parent 1:1 classid 1:30 htb rate 10mbit ceil 30mbit prio 3 # 5. 使用过滤器将特定流量分类到对应的类。 # protocol ip 表示匹配IP协议流量。prio 定义过滤器本身的优先级。 # u32 过滤器允许我们基于IP头部字段进行匹配。 # match ip dport 22 0xffff 表示匹配目的端口为22的TCP/UDP流量。 # flowid 1:10 表示将匹配到的流量导向classid 1:10。 # a. SSH流量 (目的端口22) sudo tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip dport 22 0xffff flowid 1:10 # b. HTTP流量 (目的端口80) sudo tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip dport 80 0xffff flowid 1:20 # 6. 验证所有规则 sudo tc -s qdisc show dev eth0 sudo tc -s class show dev eth0 sudo tc -s filter show dev eth0 # 7. 删除所有规则 # 当你不再需要这些规则时,记得删除它们。 # sudo tc qdisc del dev eth0 root
这个
HTB
示例虽然看起来复杂,但它正是
tc
强大之处的体现。通过这种方式,你可以为服务器上的不同服务、不同用户或不同应用分配和保障带宽,确保关键业务的流畅运行。
为什么我的
tc
tc
规则似乎没有生效?
这绝对是
tc
新手最常遇到的“坑”之一,甚至我自己也时不时会在这里跌倒。
tc
规则不生效,往往不是命令本身错了(虽然语法错误也常见),而是对
tc
的工作原理或测试环境理解有偏差。
一个最核心的误区是:
tc
默认只控制
egress
(出站)流量。 也就是说,你设置的规则,只会影响从你的网卡
eth0
发出去的数据包。如果你想控制 进入
eth0
的数据包(
ingress
流量),情况就复杂多了。虽然
tc
有一个
ingress qdisc
,但它功能有限,主要用于丢弃超速流量,不能像
egress
那样进行复杂的整形和优先级划分。通常,对于入站流量的控制,你需要结合
iptables
进行数据包标记,然后在一个 其他 接口的
egress
端进行整形,或者利用一些更高级、更复杂的内核模块(比如
IMQ
,但它现在已经不推荐使用了)。所以,当你发现规则没生效时,先问问自己:我是在控制出站还是入站流量?
另外,还有几个常见原因:
- 旧规则残留: 你在添加新规则之前,有没有彻底清除掉
eth0
上可能存在的旧规则?
sudo tc qdisc del dev eth0 root
这一步至关重要。如果存在冲突的规则,新的可能就不会被应用,或者行为不符合预期。
- 过滤器匹配问题: 你的
filter
规则是否真的匹配到了你想要控制的流量?IP 地址、端口号、协议、甚至
u32
匹配的偏移量和掩码,任何一个细节不对,数据包就可能溜走,进入
default
类或者根本不受控制。我发现很多人在
u32
匹配上容易出错,一个
0xffff
的掩码可能就导致匹配不精确。
- 测试方法不当: 你是如何测试限速效果的?仅仅是
ping
吗?
ping
的流量很小,可能根本触及不到限速阈值。通常,你需要使用
iperf3
、
netcat
或者实际的大文件下载/上传来产生足够的流量,才能观察到
tc
规则的效果。同时,确保你测试的流量确实是通过你设置
tc
规则的那个接口。
- 优先级冲突: 如果你设置了多个
filter
,它们的
prio
值很重要。
tc
会按照
prio
值从小到大(优先级从高到低)的顺序处理过滤器。一个优先级较高的、匹配范围较广的过滤器,可能会在更具体的过滤器之前捕获到流量。
- 内核模块缺失: 虽然现代 Linux 内核通常都预装了
tc
所需的模块,但如果你的系统是高度定制的,或者非常老旧,可能需要检查
sch_htb
、
sch_tbf
等模块是否已加载 (
lsmod | grep sch_
)。
遇到问题,最有效的排查方式是使用
sudo tc -s qdisc show dev eth0
、
sudo tc -s class show dev eth0
和
sudo tc -s filter show dev eth0
命令,它们会显示当前规则的详细状态和统计信息,包括通过每个
qdisc
或
class
的数据包数量和字节数,这能帮助你判断流量是否真的进入了你预期的队列。
tc
tc
与
iptables
在流量控制中如何协同工作?
tc
和
iptables
在 Linux 网络栈中扮演着不同的角色,但它们可以非常优雅地协同工作,实现更精细、更灵活的流量控制策略。简单来说,
iptables
负责识别和标记数据包,而
tc
则负责根据这些标记调度和整形数据包。
iptables
的强大之处在于它能够基于非常丰富的条件来检查和修改数据包。它可以查看源/目的 IP、端口、协议、TCP 标志位、连接状态,甚至数据包的特定内容。在流量控制的场景下,
iptables
最常用的功能是使用
MARK
或
CONNMARK
目标,在
mangle
表中为数据包打上一个数字标记。这个标记本身不会改变数据包的内容,但它会随着数据包在内核中传递,可以被其他模块(比如
tc
)读取。
协同工作流程:
-
iptables
标记数据包: 你可以在
iptables
的
mangle
表中定义规则,根据你需要的条件(例如,来自特定 IP 的流量、去往特定端口的流量、特定协议的流量等)为数据包打上一个唯一的数字标记。
# 示例:标记所有源IP为192.168.1.100的出站流量,标记值为10 sudo iptables -t mangle -A POSTROUTING -s 192.168.1.100 -j MARK --set-mark 10 # 示例:标记所有目的端口为80(HTTP)的出站TCP流量,标记值为20 sudo iptables -t mangle -A POSTROUTING -p tcp --dport 80 -j MARK --set-mark 20 # 确保路由后,标记仍然存在。PREROUTING 也可以,但POSTROUTING更靠近tc的egress。 # 如果要对入站流量进行标记,通常在PREROUTING链进行,但要记住tc只能在出站接口整形。
注意:
MARK
目标会给单个数据包打标记。如果你想标记整个连接的所有数据包,可以使用
CONNMARK
,它会将
MARK
值保存到连接跟踪表中,并应用到该连接的所有后续数据包。
-
tc
根据标记过滤和整形: 在
tc
的过滤器中,你可以使用
fw
(firewall mark) 过滤器来匹配
iptables
设置的标记,然后将匹配到的数据包导向特定的
class
或
qdisc
进行流量整形。
# 假设你已经设置了一个HTB根qdisc和主类,例如: # sudo tc qdisc add dev eth0 root handle 1: htb default 30 # sudo tc class add dev eth0 parent 1: classid 1:1 htb rate 100mbit ceil 100mbit # 创建一个子类1:10,用于处理标记为10的流量 sudo tc class add dev eth0 parent 1:1 classid 1:10 htb rate 10mbit ceil 20mbit prio 1 # 创建一个子类1:20,用于处理标记为20的流量 sudo tc class add dev eth0 parent 1:1 classid 1:20 htb rate 5mbit ceil 15mbit prio 2 # 使用fw过滤器匹配iptables的标记 # prio 1 表示这个过滤器本身的优先级。handle 10 fw 匹配标记值为10的数据包。 sudo tc filter add dev eth0 parent 1: protocol ip prio 1 handle 10 fw flowid 1:10 # 匹配标记值为20的数据包 sudo tc filter add dev eth0 parent 1: protocol ip prio 2 handle 20
linux 操作系统 字节 端口 工具 栈 路由 常见问题 为什么 子类 Filter Token 接口 栈 class default http linux ssh