在复杂的网络环境中,我们常常需要对不同类型的网络流量进行区分对待,特别是要保证关键应用的服务质量(QoS)。比如,你可能希望优先处理集群内部节点间的通信流量,或者为特定用户的 SSH 会话提供更低的延迟。传统的基于 IP 地址和端口的 iptables
规则在处理有状态连接(如 TCP)时,只能匹配连接的第一个数据包,后续数据包无法直接应用相同的策略,这限制了我们进行精细化控制的能力。这时候,iptables
的 CONNMARK
扩展就派上用场了。
CONNMARK
允许我们为整个网络连接(Connection)打上一个标记(Mark),这个标记存储在内核的连接跟踪(conntrack)条目中。一旦连接被标记,后续属于该连接的所有数据包都可以方便地恢复(Restore)这个标记,从而让我们能够基于连接状态来应用策略,例如结合 tc
(Traffic Control) 进行流量整形或优先级调度。
本文将深入探讨如何使用 CONNMARK
精准标记特定应用的连接,并结合 tc filter ... handle <fwmark> fw
实现基于连接标记的流量优先级分类。我们还会详细对比这种方法与直接使用 DSCP 标记在 CPU 开销、配置复杂度及跨设备兼容性方面的优劣,并提供完整的 CONNMARK
设置、保存、恢复规则链的示例。
理解 CONNMARK 的核心机制
首先,得搞清楚 CONNMARK
和普通的 MARK
有啥不一样。
MARK
Target: 这是iptables
的一个目标(Target),用于给单个数据包(Packet)打上一个称为fwmark
(Firewall Mark) 的标记。这个标记只存在于数据包在内核协议栈中传递的过程中,一旦数据包离开本机,标记通常就丢失了(除非有特殊配置,比如 IPsec 策略)。MARK
是无状态的,每个数据包都需要独立匹配规则才能被打上标记。CONNMARK
Extension/Target: 这既是一个匹配(Match)模块,也是一个目标(Target)。它操作的是连接跟踪条目中的标记,我们称之为connmark
。- 作为 Target:
--set-mark mark[/mask]
: 直接设置connmark
的值。通常用于新连接的第一个包。--save-mark [--mask mask]
: 将当前数据包的fwmark
保存到该连接的connmark
中。这是实现状态化标记的关键!我们先用其他规则给第一个包打上fwmark
,然后用--save-mark
把它存到连接里。--restore-mark [--mask mask]
: 将连接的connmark
恢复(复制)到当前数据包的fwmark
上。这样,后续属于该连接的数据包就能自动获得之前保存的标记。
- 作为 Match:
-m connmark --mark mark[/mask]
: 匹配connmark
具有特定值的连接。注意,这匹配的是连接本身的标记,而不是数据包当前的fwmark
。
- 作为 Target:
核心流程通常是这样的:
- 识别新连接的第一个包: 使用
-m state --state NEW
或其他特定条件(如 TCP SYN 包)匹配新连接的第一个数据包。 - 分类并设置数据包标记 (
fwmark
): 根据应用、端口、用户 ID 等条件,使用-j MARK --set-mark <value>
给这个初始数据包打上一个临时的fwmark
。 - 保存标记到连接 (
connmark
): 紧接着,使用-j CONNMARK --save-mark
将上一步设置的fwmark
保存到该连接的connmark
中。 - 恢复后续包的标记: 对于该连接的后续数据包(ESTABLISHED, RELATED 状态),使用
-j CONNMARK --restore-mark
将之前保存的connmark
恢复到数据包的fwmark
上。
这样一来,所有属于同一个逻辑连接的数据包(无论方向)都会带上相同的 fwmark
,我们就可以基于这个 fwmark
做进一步处理了,比如用 tc
进行 QoS。
关键点: 这些操作通常在 mangle
表中进行,因为它允许修改数据包(包括 fwmark
)。PREROUTING
链处理进入本机的数据包,OUTPUT
链处理本机产生的数据包。
规则顺序非常重要! 通常,恢复标记 (--restore-mark
) 的规则应该放在前面。如果一个数据包已经成功恢复了标记(即它的 fwmark
非零),我们就不需要再对它进行分类和保存标记了。这可以通过 -m mark --mark 0
来判断。
一个典型的 mangle
表结构可能像这样:
# iptables -t mangle -N CONNMARK_CHAIN
# iptables -t mangle -A PREROUTING -j CONNMARK_CHAIN
# iptables -t mangle -A OUTPUT -j CONNMARK_CHAIN
# 1. 恢复标记
# 对于已经有 connmark 的连接,将 connmark 恢复到 fwmark
# --nfmask 和 --ctmask 用于控制哪些位被恢复,通常用 0xffffffff 表示全部位
iptables -t mangle -A CONNMARK_CHAIN -j CONNMARK --restore-mark --nfmask 0xffffffff --ctmask 0xffffffff
# 2. 如果 fwmark 仍然是 0 (表示 restore 没成功,或者这是个新连接),则进行分类标记
# 这里是防止我们覆盖已经恢复的标记
iptables -t mangle -A CONNMARK_CHAIN -m mark --mark 0 -j CLASSIFY_CHAIN # 跳转到分类链
# 3. 如果数据包被打上了新的 fwmark (在 CLASSIFY_CHAIN 中完成),保存它
# 这一步应该在 CLASSIFY_CHAIN 的末尾或者返回后执行
iptables -t mangle -A CONNMARK_CHAIN -j CONNMARK --save-mark --nfmask 0xffffffff --ctmask 0xffffffff
# --- 分类链 (CLASSIFY_CHAIN) 示例 ---
# iptables -t mangle -N CLASSIFY_CHAIN
# 规则示例:给特定应用的 NEW 连接打标记 (fwmark)
# iptables -t mangle -A CLASSIFY_CHAIN -m state --state NEW -p tcp --dport 9300 -j MARK --set-mark 0x1
# iptables -t mangle -A CLASSIFY_CHAIN -m state --state NEW -p tcp --dport 9200 -j MARK --set-mark 0x2
# iptables -t mangle -A CLASSIFY_CHAIN -m state --state NEW -p tcp --dport 22 -m owner --uid-owner 1000 -j MARK --set-mark 0x3
# ... 其他分类规则 ...
# 分类链最后返回
# iptables -t mangle -A CLASSIFY_CHAIN -j RETURN
注意: 上面的 --nfmask
和 --ctmask
在较新版本的 iptables
中可能不是必需的,--save-mark
和 --restore-mark
默认会操作整个标记。但显式指定 --mask 0xffffffff
通常更清晰。
场景实战:精准标记应用流量
让我们来看几个具体的例子。
场景 1:区分不同端口上的 Elasticsearch (ES) 通信
假设我们希望优先保障 ES 集群内部通信(通常使用 9300 端口)的带宽和低延迟,而对外提供服务的客户端接口(通常使用 9200 端口)优先级稍低。
目标:
- 发往或来自端口 9300 的 TCP 连接,标记为
0x1
。 - 发往或来自端口 9200 的 TCP 连接,标记为
0x2
。
iptables 规则 (mangle
表):
# 清理旧规则 (如果需要)
iptables -t mangle -F PREROUTING
iptables -t mangle -F OUTPUT
# 可以考虑删除自定义链 (如果存在)
# iptables -t mangle -X CONNMARK_CHAIN
# iptables -t mangle -X CLASSIFY_CHAIN
# 创建自定义链,更清晰
iptables -t mangle -N CONNMARK_SETUP
iptables -t mangle -N CLASSIFY_ES
# 将 PREROUTING 和 OUTPUT 流量导入处理链
iptables -t mangle -A PREROUTING -j CONNMARK_SETUP
iptables -t mangle -A OUTPUT -j CONNMARK_SETUP
# --- CONNMARK_SETUP 链 ---
# 1. 恢复已存在的连接标记到数据包标记 (fwmark)
# 对于 ESTABLISHED/RELATED 状态的包,尝试恢复标记
# 注意:这里我们只对已有连接尝试恢复,新连接的第一个包 fwmark 初始为 0
iptables -t mangle -A CONNMARK_SETUP -m state --state ESTABLISHED,RELATED -j CONNMARK --restore-mark --mask 0xffffffff
# 2. 如果数据包标记不为 0 (说明成功恢复),则直接接受,不再处理
# 这避免了对已恢复标记的包再次进行分类,提高效率
iptables -t mangle -A CONNMARK_SETUP -m mark ! --mark 0 -j ACCEPT
# 3. 对于 fwmark 仍然为 0 的包 (通常是 NEW 连接的第一个包),进行分类
iptables -t mangle -A CONNMARK_SETUP -j CLASSIFY_ES
# 4. 分类完成后 (如果 CLASSIFY_ES 给包打了标记),保存数据包标记 (fwmark) 到连接标记 (connmark)
# 这一步对那些刚刚在 CLASSIFY_ES 中被打上标记的 NEW 包生效
iptables -t mangle -A CONNMARK_SETUP -j CONNMARK --save-mark --mask 0xffffffff
# 5. 处理链结束
iptables -t mangle -A CONNMARK_SETUP -j RETURN
# --- CLASSIFY_ES 链 (只处理 fwmark 为 0 的包) ---
# 规则 1: 标记 ES 集群内部通信 (端口 9300)
# 匹配新 TCP 连接,目标端口或源端口是 9300
# 注意:需要同时考虑入站和出站,所以匹配 dport 和 sport
iptables -t mangle -A CLASSIFY_ES -p tcp -m multiport --ports 9300 -m state --state NEW -j MARK --set-mark 0x1
# 规则 2: 标记 ES 客户端通信 (端口 9200)
# 匹配新 TCP 连接,目标端口或源端口是 9200
iptables -t mangle -A CLASSIFY_ES -p tcp -m multiport --ports 9200 -m state --state NEW -j MARK --set-mark 0x2
# --- 分类链结束 ---
# 分类链的末尾隐含一个 RETURN
解释:
- 我们创建了
CONNMARK_SETUP
链来统一处理入口 (PREROUTING
) 和出口 (OUTPUT
) 流量。 - 首先尝试恢复标记 (
--restore-mark
)。这一步对ESTABLISHED,RELATED
状态的包有效。 - 如果恢复成功(
fwmark
非零),则-j ACCEPT
跳过后续处理。这是个优化,避免重复劳动。 - 如果
fwmark
仍然是 0(说明是新连接的第一个包,或者是非 TCP/UDP 等无连接状态的包,或者是未被我们关心的规则匹配的连接的包),则跳转到CLASSIFY_ES
链进行分类。 CLASSIFY_ES
链中,我们使用-m state --state NEW
确保只对新连接的第一个包进行判断和MARK
操作。我们使用-m multiport --ports
同时匹配源端口和目标端口,因为连接是双向的。- 从
CLASSIFY_ES
返回后,如果数据包刚刚被打了MARK
(fwmark
非零),则执行--save-mark
将这个标记保存到连接跟踪条目中。
现在,所有属于 ES 9300 端口连接的数据包(无论新旧、进出)都会被打上 fwmark 0x1
,而 9200 端口的连接则被打上 fwmark 0x2
。
场景 2:标记特定用户的 SSH 流量
假设你想优先保证管理员(比如 UID 为 1000)的 SSH 会话响应速度,而普通用户的 SSH 优先级可以低一些。
目标:
- 管理员 (UID 1000) 发起的 SSH (端口 22) 连接,标记为
0x3
。 - 其他用户发起的 SSH 连接,标记为
0x4
。
iptables 规则 (mangle
表):
我们需要修改或扩展之前的 CLASSIFY_ES
链(或者创建一个新的 CLASSIFY_SSH
链,然后在 CONNMARK_SETUP
中调用)。这里我们假设在 CONNMARK_SETUP
中直接添加 SSH 分类逻辑(或者跳转到 CLASSIFY_SSH
)。
# 假设 CONNMARK_SETUP 链结构如上,在跳转到 CLASSIFY_ES 之后 (或者之前,顺序取决于优先级)
# 添加 SSH 分类逻辑 (这里以内联方式展示,也可放入 CLASSIFY_SSH 链)
# --- 在 CONNMARK_SETUP 链中,CLASSIFY_ES 调用之后,save-mark 之前 ---
# 规则 3: 标记管理员 SSH (UID 1000, 出站连接)
# 只在 OUTPUT 链生效,因为 PREROUTING 无法获取本地进程 UID
# 匹配新 TCP 连接,目标端口 22,用户 ID 是 1000
iptables -t mangle -A CONNMARK_SETUP -p tcp --dport 22 -m state --state NEW -m owner --uid-owner 1000 -j MARK --set-mark 0x3
# 规则 4: 标记其他用户的 SSH (出站连接)
# 匹配新 TCP 连接,目标端口 22,排除管理员 UID (如果需要明确标记的话)
# 注意:如果管理员规则已经命中,这里就不会执行了 (因为 iptables 按顺序匹配)
# 如果只想标记管理员,这条可以省略,或者标记为不同的值
iptables -t mangle -A CONNMARK_SETUP -p tcp --dport 22 -m state --state NEW -m owner ! --uid-owner 1000 -j MARK --set-mark 0x4
# 注意:入站 SSH 连接 (PREROUTING) 无法使用 -m owner 匹配发起者 UID。
# 如果需要对入站 SSH 也做区分,可能需要基于源 IP 或其他信息,或者在应用层处理。
# 这里我们主要演示了基于用户标记出站连接。
# --- CONNMARK_SETUP 链的 --save-mark 和 RETURN 部分保持不变 ---
解释:
- 我们使用了
-m owner --uid-owner <UID>
来匹配特定用户启动的进程发出的数据包。这对于标记从服务器发起的连接(如 SSH 客户端、scp
、sftp
等)非常有用。 - 重要限制:
-m owner
模块只能在OUTPUT
链中使用,因为它需要关联数据包与本地进程。对于进入服务器的 SSH 连接(PREROUTING
链),我们无法直接得知连接发起方的用户 ID(除非有其他机制,如身份验证后的动态规则)。因此,这个方法主要适用于控制从本机发起的、由特定用户产生的 SSH 流量。 - 如果你的目标是控制 SSH 服务(即入站连接)的优先级,你可能需要基于源 IP 地址范围(比如来自管理网络的 IP)或其他不太精确的特征来打标记,或者接受对所有入站 SSH 连接应用统一的标记。
结合 tc
实现基于 fwmark
的 QoS
一旦我们成功地使用 CONNMARK
和 MARK
给数据包打上了合适的 fwmark
,就可以利用 Linux 的 tc
(Traffic Control) 子系统来根据这些标记进行流量调度了。
tc
的核心思想是使用队列规定(qdisc)、类别(class)和过滤器(filter)来管理网络接口上的数据包发送。
- Qdisc (Queueing Discipline): 决定数据包如何排队和发送。常见的有
pfifo_fast
(默认),htb
(Hierarchical Token Bucket),fq_codel
(Fair Queueing with Controlled Delay) 等。 - Class: 在支持分类的 qdisc (如
htb
) 下,可以将带宽划分为不同的类别,赋予不同的优先级或速率限制。 - Filter: 用于将数据包分类到不同的 qdisc 或 class。这正是
fwmark
发挥作用的地方!
我们可以使用 tc filter
的 fw
分类器来匹配数据包的 fwmark
。
示例:使用 HTB 实现基于 fwmark
的优先级
假设我们想在 eth0
接口上实现以下策略:
- 标记为
0x1
(ES 集群通信) 的流量进入高优先级类别。 - 标记为
0x3
(管理员 SSH) 的流量进入次高优先级类别。 - 其他流量(包括标记为
0x2
的 ES 客户端流量和0x4
的普通用户 SSH)进入普通优先级类别。
# 1. 在 eth0 上设置 HTB qdisc (根句柄 1:)
# default 12 表示未分类流量进入类别 1:12
tc qdisc add dev eth0 root handle 1: htb default 12
# 2. 创建一个根类别,分配总带宽 (例如 1000Mbit)
tc class add dev eth0 parent 1: classid 1:1 htb rate 1000mbit
# 3. 创建优先级类别 (子类别)
# 类别 1:10 (高优先级), 保证 500Mbit, 最高可达 1000Mbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 500mbit ceil 1000mbit prio 1
# 类别 1:11 (次高优先级), 保证 100Mbit, 最高可达 1000Mbit
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 100mbit ceil 1000mbit prio 2
# 类别 1:12 (普通优先级), 保证 100Mbit, 最高可达 800Mbit (给高优先级留些空间)
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 100mbit ceil 800mbit prio 3
# 4. 创建过滤器,根据 fwmark 将流量导入相应类别
# 过滤器优先级 prio 越小越先匹配
# handle <mark> fw 表示匹配 fwmark
# flowid <classid> 表示将匹配的流量导入指定类别
tc filter add dev eth0 parent 1: protocol ip prio 1 handle 1 fw flowid 1:10 # fwmark 0x1 -> class 1:10
tc filter add dev eth0 parent 1: protocol ip prio 2 handle 3 fw flowid 1:11 # fwmark 0x3 -> class 1:11
# 注意:标记为 0x2 和 0x4 的流量,以及其他未标记或标记值不匹配的流量
# 会根据根 qdisc 的 default 12 设置,进入类别 1:12。
# 如果需要更精细控制,可以为 0x2 和 0x4 也添加 filter。
# tc filter add dev eth0 parent 1: protocol ip prio 3 handle 2 fw flowid 1:12
# tc filter add dev eth0 parent 1: protocol ip prio 4 handle 4 fw flowid 1:12
# 查看 tc 配置
# tc qdisc show dev eth0
# tc class show dev eth0
# tc filter show dev eth0
解释:
- 我们设置了 HTB qdisc,并创建了三个具有不同优先级 (
prio
) 和带宽保证 (rate
) / 上限 (ceil
) 的类别。
Prio 值越小,优先级越高。当带宽充足时,所有类别都能达到其ceil
;当发生拥塞时,HTB 会优先满足高prio
类别的rate
需求。 - 关键在于
tc filter ... handle <mark> fw flowid <classid>
命令。它告诉tc
,凡是fwmark
等于<mark>
(这里用十进制表示,0x1
是1
,0x3
是3
) 的 IP 数据包,都应该被导向flowid
指定的类别。 - 未被
filter
明确匹配的流量会进入htb default
指定的类别 (1:12)。
通过这种方式,CONNMARK
负责在 iptables
层面进行有状态的连接识别和标记,而 tc
则根据这些标记(fwmark
)执行实际的流量调度策略。
CONNMARK + tc
vs. DSCP:优劣势深度对比
除了使用 CONNMARK
+ fwmark
+ tc
在本地进行 QoS,另一种常见的方法是使用 IP 头中的 DSCP (Differentiated Services Code Point) 字段来标记服务等级。iptables
也可以设置 DSCP 值(使用 DSCP
或 CLASSIFY
target),tc
同样可以基于 DSCP 值进行过滤(例如使用 u32
filter 匹配 IP 头)。那么这两种方法孰优孰劣呢?
特性 | CONNMARK + tc (基于 fwmark) |
DSCP + tc |
分析与权衡 |
---|---|---|---|
标记范围 | 连接级别 (connmark ) |
数据包级别 (DSCP 字段) |
CONNMARK 天然适合有状态的 QoS 策略,标记一次,后续包自动恢复。DSCP 需要为每个包(或至少是流的初始包)设置。 |
状态化 | 强项:通过 save/restore 机制实现 |
较弱:需自行维护状态或依赖应用层设置 | CONNMARK 简化了对整个 TCP/UDP 会话应用统一策略的逻辑。DSCP 如果只标记 SYN 包,后续包如何处理需要额外考虑(除非路由器能基于五元组维护 QoS 状态)。 |
CPU 开销 | 可能稍高 | 可能稍低 | CONNMARK 每次 restore 都需要查询 conntrack 表,即使这个查询可能已被其他 netfilter 模块触发。save 操作只在连接初次标记时发生。设置 fwmark 开销不大。DSCP 设置是简单的 IP 头字段修改。tc 基于 fwmark (handle fw ) 过滤通常非常高效。tc 基于 DSCP 过滤(如 u32 )也比较高效。主要差异在于 CONNMARK 的 conntrack 交互可能带来额外开销,尤其在高连接数/高 PPS 场景下。 但现代 CPU 处理能力下,对于不是极端苛刻的场景,这种差异可能不明显。 |
配置复杂度 | 较高 | 中等 (本地配置) / 极高 (端到端) | CONNMARK 需要精心设计 iptables 规则顺序(restore , check mark , classify , save ),还要配置 tc 。DSCP 本地配置相对简单:iptables 设置 DSCP 值 + tc 匹配 DSCP 值。但 DSCP 的真正挑战在于跨网络设备的兼容性和策略一致性。 |
跨设备兼容性 | 本地有效 | 设计上跨网络,但现实中不一定可靠 | connmark 和 fwmark 都是 Linux 内核本地概念,不离开主机。DSCP 是 IP 标准字段,理论上所有路由器都应理解,但实际上很多中间设备可能会忽略、重置或重新映射 DSCP 值(特别是跨越不同管理域,如 ISP 网络)。确保端到端 DSCP 有效需要整个传输路径上的设备都支持并正确配置 DiffServ。 |
灵活性 | 高 (基于 iptables 的任意条件) |
中等 (通常基于 L3/L4 信息,除非应用支持) | CONNMARK 可以结合 iptables 的各种匹配模块(包括 owner , string , ipset 等)进行非常灵活的分类。DSCP 标记通常基于 IP/端口,或者需要应用本身支持设置 DSCP (如 VoIP 软件)。 |
适用场景 | 主机本地 QoS 精细控制,不依赖外部网络设备。 | 端到端 QoS 信号,需要网络路径支持 DiffServ。 | 当你需要在一台 Linux 服务器/路由器上严格控制不同应用/用户的出口流量优先级,且不关心或无法控制下游网络设备的行为时,CONNMARK 是理想选择。当你需要向整个网络路径(如果路径支持)宣告流量优先级期望时,DSCP 是标准方法。 |
总结一下权衡点:
- 如果你只需要在单台 Linux 机器上实现精细、有状态的 QoS,并且不依赖外部网络对标记的理解,
CONNMARK
+tc
是强大且可靠的选择。 虽然配置可能复杂,但效果是可控的。 - 如果你希望实现跨网络的 QoS,并且你的网络基础设施(路由器、交换机)支持并配置了 DiffServ/DSCP,那么使用 DSCP 标记是标准做法。 你需要接受 DSCP 可能在传输途中被修改或忽略的风险,并且本地分类逻辑可能不如
CONNMARK
灵活(除非应用原生支持 DSCP)。 - 性能方面,
CONNMARK
可能引入轻微的额外 CPU 开销,但在多数场景下可能不是瓶颈。 如果面临极端性能压力,需要仔细基准测试。
实践中,两者并非完全互斥。有时可能会结合使用:例如,内部使用 CONNMARK
进行精细分类和本地 tc
调度,同时对于需要跨网络传递优先级的流量,也设置相应的 DSCP 值,寄希望于下游网络能够利用它。
CONNMARK 规则的持久化
iptables
规则在系统重启后默认会丢失。为了让我们的 CONNMARK
和 tc
配置持久生效,需要将它们保存并在系统启动时恢复。
保存和恢复 iptables
规则:
- 手动保存:
iptables-save > /etc/iptables/rules.v4 # (Debian/Ubuntu 路径示例) ip6tables-save > /etc/iptables/rules.v6 # (如果配置了 IPv6)
- 手动恢复:
iptables-restore < /etc/iptables/rules.v4 ip6tables-restore < /etc/iptables/rules.v6
- 使用持久化服务 (推荐):
- Debian/Ubuntu: 安装
iptables-persistent
包。sudo apt update sudo apt install iptables-persistent # 安装过程中会询问是否保存当前 IPv4/IPv6 规则 # 保存后,规则会存储在 /etc/iptables/rules.v4 和 rules.v6 # 服务会在启动时自动加载这些规则 # 如果后续修改了规则,需要手动再次保存: sudo netfilter-persistent save
- CentOS/RHEL 7+: 使用
firewalld
(通常不直接操作 iptables),或者禁用firewalld
并使用iptables-services
。sudo systemctl disable firewalld sudo systemctl stop firewalld sudo yum install iptables-services sudo systemctl enable iptables sudo systemctl enable ip6tables # 保存当前规则: sudo service iptables save # (规则保存在 /etc/sysconfig/iptables 和 /etc/sysconfig/ip6tables)
- Debian/Ubuntu: 安装
保存和恢复 tc
规则:
tc
规则通常没有像 iptables-persistent
那样标准化的跨发行版持久化工具。常见做法是:
将
tc
命令写入启动脚本:- 可以放在
/etc/rc.local
(如果系统支持)。 - 可以创建一个 systemd service单元,在网络接口启动后执行
tc
命令。 - 例如,创建一个脚本
/usr/local/sbin/apply_tc_rules.sh
包含所有tc qdisc
,tc class
,tc filter
命令,然后创建一个 systemd service (.service
文件) 在network-online.target
之后执行这个脚本。
示例 systemd service (
/etc/systemd/system/my-tc-rules.service
):[Unit] Description=Apply Custom TC QoS Rules After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/sbin/apply_tc_rules.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target
然后启用服务:
sudo systemctl enable my-tc-rules.service
。- 可以放在
使用特定于网络的工具:
ifupdown
(Debian/Ubuntu 的/etc/network/interfaces
) 允许使用post-up
指令来执行tc
命令。NetworkManager
也有 dispatcher scripts (/etc/NetworkManager/dispatcher.d/
) 可以在接口状态变化时触发脚本。
选择哪种方法取决于你的系统和偏好。关键是确保 tc
规则在网络接口可用后、需要 QoS 的流量产生之前被应用。
结语
iptables CONNMARK
提供了一种强大而灵活的机制,用于在 Linux 系统上实现基于连接状态的流量识别和标记。通过结合 MARK
target 以及 CONNMARK
的 --save-mark
和 --restore-mark
功能,我们可以为复杂的应用流(如区分不同端口的服务、特定用户的会话)打上持久的连接标记。
这些标记 (connmark
) 可以被恢复到数据包的 fwmark
上,进而被 tc
(Traffic Control) 使用 handle fw
过滤器进行识别,实现精细的流量整形、带宽分配和优先级调度。
与使用 DSCP 标记相比,CONNMARK
+ tc
的主要优势在于其 本地控制的可靠性 和 基于 iptables
丰富匹配条件的分类灵活性,特别适合在单个服务器或网关上实施 QoS 策略,而不依赖外部网络设备的支持。其代价是可能略高的 CPU 开销(源于 conntrack 交互)和相对复杂的 iptables
规则配置。
DSCP 则更侧重于跨网络的 QoS 信号传递,配置本身可能更简单,但端到端的效果严重依赖于网络路径上所有设备的配合,这在复杂或不受信任的网络环境中往往难以保证。
理解 CONNMARK
的工作原理、掌握正确的 iptables
规则编写顺序、并了解如何将其与 tc
集成,将使你能够更精准地控制 Linux 系统上的网络流量,优化关键应用性能,提升服务质量。选择 CONNMARK
还是 DSCP,亦或是两者结合,取决于你的具体需求、控制范围以及网络环境。