HOOOS

精通 iptables CONNMARK:实现复杂应用流量的精准识别与优先级控制

0 39 防火墙规则诗人 iptablesCONNMARK流量优先级tcLinux网络
Apple

在复杂的网络环境中,我们常常需要对不同类型的网络流量进行区分对待,特别是要保证关键应用的服务质量(QoS)。比如,你可能希望优先处理集群内部节点间的通信流量,或者为特定用户的 SSH 会话提供更低的延迟。传统的基于 IP 地址和端口的 iptables 规则在处理有状态连接(如 TCP)时,只能匹配连接的第一个数据包,后续数据包无法直接应用相同的策略,这限制了我们进行精细化控制的能力。这时候,iptablesCONNMARK 扩展就派上用场了。

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

核心流程通常是这样的:

  1. 识别新连接的第一个包: 使用 -m state --state NEW 或其他特定条件(如 TCP SYN 包)匹配新连接的第一个数据包。
  2. 分类并设置数据包标记 (fwmark): 根据应用、端口、用户 ID 等条件,使用 -j MARK --set-mark <value> 给这个初始数据包打上一个临时的 fwmark
  3. 保存标记到连接 (connmark): 紧接着,使用 -j CONNMARK --save-mark 将上一步设置的 fwmark 保存到该连接的 connmark 中。
  4. 恢复后续包的标记: 对于该连接的后续数据包(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 返回后,如果数据包刚刚被打了 MARKfwmark 非零),则执行 --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 客户端、scpsftp 等)非常有用。
  • 重要限制: -m owner 模块只能在 OUTPUT 链中使用,因为它需要关联数据包与本地进程。对于进入服务器的 SSH 连接(PREROUTING 链),我们无法直接得知连接发起方的用户 ID(除非有其他机制,如身份验证后的动态规则)。因此,这个方法主要适用于控制从本机发起的、由特定用户产生的 SSH 流量。
  • 如果你的目标是控制 SSH 服务(即入站连接)的优先级,你可能需要基于源 IP 地址范围(比如来自管理网络的 IP)或其他不太精确的特征来打标记,或者接受对所有入站 SSH 连接应用统一的标记。

结合 tc 实现基于 fwmark 的 QoS

一旦我们成功地使用 CONNMARKMARK 给数据包打上了合适的 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 filterfw 分类器来匹配数据包的 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> (这里用十进制表示,0x11, 0x33) 的 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 值(使用 DSCPCLASSIFY 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 的真正挑战在于跨网络设备的兼容性和策略一致性。
跨设备兼容性 本地有效 设计上跨网络,但现实中不一定可靠 connmarkfwmark 都是 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 规则在系统重启后默认会丢失。为了让我们的 CONNMARKtc 配置持久生效,需要将它们保存并在系统启动时恢复。

保存和恢复 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)
      

保存和恢复 tc 规则:

tc 规则通常没有像 iptables-persistent 那样标准化的跨发行版持久化工具。常见做法是:

  1. 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

  2. 使用特定于网络的工具:

    • 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,亦或是两者结合,取决于你的具体需求、控制范围以及网络环境。

点评评价

captcha
健康