使用 RouterOS 做策略路由时,被 conntrack「标记污染」坑了一次的完整排查记录

这篇是一次真实排查过程的整理:
场景是用 MikroTik RouterOS 做双线/多 WAN 策略路由,本机需要访问某个特定 IP 走指定 WAN。
看起来很简单的一件事,结果却被 conntrack / mangle / output 链联合作死了一回。

这里把「问题现象 → 原因分析 → 排查过程 → 最终解决方案和建议」完整记录下来,方便以后自己和别人少踩坑。


一、问题背景

需求很简单:

让路由器本机访问某个外部 IP(例如 47.109.74.244)时,强制走 cell 这个 WAN(比如 4G/5G 蜂窝)。

常规做法是:

  1. /ip firewall mangle 里给这类连接打 connection-mark
  2. 再根据 connection-mark 给数据包打 routing-mark
  3. 再在 /ip route 里根据 routing-mark 指向对应 WAN。

示意配置(简化版):

# 1. 匹配去 47.109.74.244 的连接,打连接标记
/ip firewall mangle
add chain=output dst-address=47.109.74.244 \
    action=mark-connection new-connection-mark=cell-wan-mark passthrough=yes

# 2. 根据连接标记打路由标记
add chain=output connection-mark=cell-wan-mark \
    action=mark-routing new-routing-mark=cell passthrough=no

看上去没问题:
路由器本机访问 47.109.74.244 → 连接被标记 → 数据包打上 routing-mark=cell → 走目标路由表即可。


二、异常现象

然而实际运行时,发现了很诡异的情况:

  • 明明只想让去 47.109.74.244 的流量走 cell
  • 结果 路由器本机通过 WireGuard 出去的流量(endpoint 在 192.168.1.2 上)也被打上了 routing-mark=cell
  • 换句话说:47.109.74.244 完全无关的流量,被强行扯到 cell 上走了。

直观感受就是:
有些连接突然不走原本预期的 WAN 了(比如原本应该走光纤出口),而是绕路从 cell 出去了。

更让人困惑的是:
在 mangle 里明明写的是 dst-address=47.109.74.245,它是怎么跑去影响 WireGuard 到 192.168.1.2 的流量的?


三、最初的误解:以为 conntrack 只看「源 + 目的」

一开始的直觉是这样的:

conntrack 判定一个连接,不就是基于五元组(源地址、目的地址、源端口、目的端口、协议)吗?
既然 WireGuard endpoint 在 192.168.1.2,和 47.109.74.2436完全不一样,怎么会是“同一个连接”的一部分?

换句话说,我以为:

  • mangle 规则只会命中 dst-address=47.109.74.243 的连接;
  • 其它目的地址的连接不会受到 connection-mark=cell-wan-mark 的影响。

事实证明,这个理解不完整


四、关键点:conntrack 还有「RELATED」和 NAT 的影响

RouterOS(底层是 Linux conntrack)的连接跟踪有几个容易踩坑的特性:

  1. 连接簇(Connection family)
    conntrack 不只有简单的「NEW / ESTABLISHED / INVALID」,还有一类叫 RELATED 的状态。
    意思是:某些包虽然 src/dst 地址不一样,但被认为是原连接的派生流量,归入同一个连接簇。
  2. ICMP 错误、TCP RST 等都可能是 RELATED
    比如对 47.109.74.247 建立 TCP 连接时发生错误,产生了 ICMP error,
    这个 ICMP 包的 src/dst 地址已经变了,但它属于 RELATED,会 继承原连接的 conntrack 信息
  3. NAT 前后地址不同
    conntrack 在内部以 NAT 后的地址为主记录连接信息,
    而你在 mangle 里看的是 NAT 前或 NAT 后的地址;
    十分容易出现「看起来不是同一连接,但实际上被认为是同一个连接簇」的情况。
  4. RouterOS 的系统流量会共享 conntrack/连接
    Router 本身的各种服务(DNS、路由探测、ICMP error、系统进程)可能挂在同一个 connection family 里,
    再加上 output 链比较特殊,极容易产生「标记跨连接扩散」的现象。

核心问题就在这:

有一些包(比如系统产生的 ICMP/TCP error、NAT 相关包、系统服务流量)被 conntrack 归为与原连接 RELATED,
于是它们继承了 connection-mark=cell-wan-mark
然后你在 chain=output 上有规则“凡是 connection-mark=cell-wan-mark 就 mark-routing=cell”,
于是这些本来完全无关的包,也被强行打上了 routing-mark=cell
最终导致包括 WireGuard 流量在内的其它连接走错了出口。

这就是典型的 “连接标记污染(connection-mark pollution)”


五、排查过程

1. 观察症状:WireGuard 流量被错误打标

  • /ip firewall mangleoutput 链查看计数器;
  • 发现去 WireGuard endpoint(192.168.1.2)的包,也命中了 connection-mark=cell-wan-mark 对应的规则;
  • 即使根本没有匹配 dst-address=47.109.74.24 的条件。

2. 确认是 conntrack 导致的继承,而不是规则写错

  • 检查所有 mangle 规则,确认没有写错 dst-address;
  • 确认没有其它地方给这些连接打相同的 connection-mark
  • 排除简单的配置错误后,就只能从「conntrack 继承」的角度去考虑。

3. 分析链路:问题规则都在 chain=output

关键观察:

  • 所有「打 connection-mark」和「打 routing-mark」的规则,都写在 chain=output
  • RouterOS 本机所有发出的流量都经过 output,包括:
  • WireGuard handshake
  • 系统 DNS 请求
  • 路由探测、ICMP error
  • NAT 辅助包
  • 以及各种系统服务包

因此:

只要 有一个来自本机的「系统包」被错误地归入某个 connection family
然后在 output 链中继承/匹配到 connection-mark=cell-wan-mark
就会导致后续同族流量全部被打上 routing-mark=cell
于是本机到其它目的地址的流量也跑偏。

4. 验证:加「排除 LAN / WireGuard」规则后问题立即消失

尝试在 output 链前面加入排除规则,例如:

# 例:排除 LAN
/ip firewall mangle
add chain=output dst-address=192.168.1.0/24 action=return

# 例:排除 WireGuard 接口
add chain=output out-interface=wg1 action=return

结果:

  • 和 LAN/WireGuard 相关的流量,输出时直接 return,不会再往后面的标记规则走;
  • 立刻不再出现 WireGuard 流量被 routing-mark=cell 污染的问题。

这从侧面证明:问题根源确实是 chain=output + connection-mark 的组合,导致了继承和污染。


六、问题本质总结

  1. conntrack 不只是简单地「源地址 + 目的地址」
    它还会通过 RELATED 等机制,把看起来“地址不一样”的包归入同一连接簇。
  2. chain=output 中对 connection-mark 做逻辑,风险极高
    因为 RouterOS 本机的系统流量太多、太杂,极容易通过 conntrack 产生「无意继承」。
  3. 一旦某个连接被打上错误的 connection-mark,后续所有 RELATED 流量都会一起带坑
    这就是所谓的「标记污染」。

七、解决方案一:在 output 链做「严格白名单式排除」

如果仍然想在 chain=output 里处理本机流量,可以这样改:

  1. 首先排除所有你不想被策略路由影响的流量,比如:
   # 排除 WireGuard 接口
   /ip firewall mangle
   add chain=output out-interface=wg1 action=return

   # 排除 LAN 段
   add chain=output dst-address=192.168.1.0/24 action=return

   # 排除运营商网关/探测地址(按需)
   add chain=output dst-address=10.254.15.129 action=return

   # 排除 DNS(如需要)
   add chain=output protocol=udp dst-port=53 action=return
  1. 然后才写真正的标记规则:
   # 只对本机去 47.109.74.243 的连接做 connection-mark
   add chain=output dst-address=47.109.74.243 \
       action=mark-connection new-connection-mark=cell-wan-mark passthrough=yes

   # 再根据 connection-mark 打 routing-mark
   add chain=output connection-mark=cell-wan-mark \
       action=mark-routing new-routing-mark=cell passthrough=no

这种做法能明显降低风险,前提是你:

  • 把所有重要的内部流量都排除出去;
  • 并且后续规则只针对非常明确的目标地址/端口。

缺点是:排除列表需要维护,而且理论上仍然有被 RELATED 影响的可能(只是概率非常低)。


八、解决方案二:用 /routing rule 直接按目的地址选路(推荐)

本次排查之后,发现更稳、更“干净”的办法是:

不再用 mangle + connection-mark + routing-mark 来控制「本机到某特定 IP 的出口」,
而是直接用 /routing ruledst-address 绑定路由表。

配置示例:

/routing rule
add action=lookup dst-address=47.109.74.243 table=cell

然后在 /ip route 中,确保 table=cell 有正确的默认路由,指向 cell 这个 WAN:

/ip route
add dst-address=0.0.0.0/0 gateway=<cell-gw> routing-table=cell

为什么这种方法更好?

  1. routing rule 工作在路由查找层,而非防火墙层
  • 不经过 mangle;
  • 不依赖 conntrack;
  • 不受 NAT / RELATED / ASSURED 等状态影响;
  • 纯粹是「对哪些目标地址,用哪个 routing-table」。
  1. 对本机流量生效,不需要特别说明 RouterOS 的 routing decision 对转发流量和本机发出的流量,都适用同一套规则,
    所以 /routing rule 中写 dst-address=47.109.74.243 table=cell
    会自然而然影响 router 自己访问这个 IP 的流量。
  2. 不会产生“标记污染” 不使用 connection-mark / routing-mark,自然也就不可能有「继承错误标记」的问题。

对于“仅仅是想让本机访问某几个 IP 走某个 WAN”的场景来说,
/routing rule 是当前 RouterOS 推荐的现代做法,既简单又稳定。


九、解决方案三:在 prerouting 上处理 router 源地址(进阶)

如果确实需要用 mangle(比如做更复杂的策略路由,涉及转发流量),
可以将「本机流量」的策略移动到 prerouting 链,并通过 固定源地址 来识别 router 的流量。

大致思路

  1. 给 router 配一个稳定的“逻辑源 IP”(例如一个 loopback IP);
  2. 让本机某类流量使用这个 IP 作为源地址;
  3. chain=prerouting 中用 src-address=<loopback IP> 来标记。

示例(仅是示意,不是完整配置):

/ip address
add address=172.31.255.1/32 interface=loopback

/ip firewall mangle
add chain=prerouting src-address=172.31.255.1 dst-address=47.109.74.243 \
    action=mark-routing new-routing-mark=cell passthrough=no

这种做法:

  • 本质是在「进入路由决策前」对特定源/目的做标记;
  • 避开了 chain=output 的各种系统流量干扰;
  • 更适合做大规模策略路由,把 router 自己和转发流量统一纳入一个设计框架里。

缺点是实现复杂度更高,适合大规模/复杂网络,而不是简单的“本机几个 IP 要走某个 WAN”。


十、经验教训与建议

  1. 不要轻易在 chain=output 里做 connection-mark 除非非常清楚自己在做什么,否则很容易出现「标记污染」——本机各种系统流量被误导到错误的出口。
  2. 如果必须在 output 链操作,一定要先做“白名单式排除”dst-addressout-interface、协议/端口等条件,把所有不希望被策略路由影响的流量先 action=return
  3. 对“本机到特定目标 IP 走特定 WAN”的需求,优先考虑 /routing rule 用法简单清晰:
   /routing rule
   add action=lookup dst-address=<目标IP或网段> table=<对应路由表>

不依赖 mangle 和 conntrack,是最稳、最干净的方式。

  1. 避免滥用 connection-mark 做一切策略 连接标记适合处理“按连接维度”的复杂策略,但其副作用(尤其配合 RELATED/NAT)也不容忽视。
    对于简单的策略,routing rule + 多 routing-table 往往更优雅。

十一、总结

这次问题的本质是:

  • 用传统的 mangle + connection-mark + routing-mark 方式,在 chain=output 上控制本机流量出口;
  • 忽略了 conntrack 的 RELATED/NAT 行为,导致 connection-mark 被“继承”到本不相关的流量上;
  • 进而造成 WireGuard 等流量也被错误打上 routing-mark,走错 WAN;
  • 最终通过「排除 LAN/WG」以及采用 /routing rule 直接按目的地址选路,解决了问题。

简单一句话的教训:

本机策略路由,能用 /routing rule 就尽量用它;
真的要用 mangle,就远离 chain=output,或者至少先把不该碰的流量统统排除掉。

如果你也在 RouterOS 上做多 WAN 或策略路由,希望这篇踩坑记录能帮你少走一些弯路。

机器学习发展史:从传统算法到 Transformer 时代

机器学习的发展跨越了数十年,从最初基于数学与规则的传统方法,逐步走向大规模深度神经网络,再到如今以 Transformer 为核心的统一架构。以下按时间脉络进行较为详细的回顾。


一、萌芽期(1950s–1980s):传统方法奠基

1. 感知机与早期神经网络

  • 1957 年,Rosenblatt 提出 Perceptron(感知机),这是最早的人工神经网络之一。
  • 能处理线性可分问题,但无法解决异或等非线性任务。
  • 1970 年代,神经网络研究陷入低潮,被称为“AI 寒冬”。

2. 统计学习方法兴起

随着计算机性能提升、数据量增加,基于统计学的传统 ML 算法逐渐占据主流:

  • k-NN(1967):最简单的非参数方法之一。
  • 朴素贝叶斯:基于概率和条件独立假设,适用于文本分类等任务。
  • 线性/逻辑回归:经典线性模型,至今仍在工业界广泛使用。
  • 决策树与集成学习
  • 1984 年 CART 方法提出。
  • 随后发展出随机森林、Gradient Boosting 等强大模型。
  • 支持向量机(SVM)
  • 1990 年代中期起飞,核方法的提出使得 SVM 在小规模数据集上表现极佳。

这一时期的特点是:
模型结构简单,可解释性强,但依赖人工特征工程。


二、深度学习复兴(1990s–2010s):以 CNN 和 RNN 为代表

1. 深度学习的回潮

  • 1986 年,反向传播算法被系统地提出,使多层神经网络训练成为可能。
  • 但受限于数据规模和计算能力,真正的突破发生在 2010 年代。

三、卷积神经网络(CNN)主导视觉(1998–2015)

1. LeNet(1998)

  • LeCun 提出的 LeNet-5 是 CNN 的先驱,用于手写数字识别。
  • 由于硬件限制,当时影响有限。

2. AlexNet(2012):深度学习的里程碑

  • 在 ImageNet 上取得压倒性胜利,使深度学习一举成为主流。
  • 两个关键因素使这件事成为转折点:
  • GPU 大幅加速训练
  • 更大规模的数据集

3. 更深、更高效的 CNN(2013–2016)

  • VGG(2014):以简单堆叠卷积带来高性能。
  • GoogLeNet(2014):提出 Inception 结构。
  • ResNet(2015):通过残差结构成功突破 100 层、1000 层深度。

CNN 彻底改变了图像识别、目标检测、分割等计算机视觉任务。


四、RNN 与序列模型(1990s–2016)

1. RNN 及其改进

  • RNN(1980s)能处理序列数据,但存在梯度消失问题。
  • LSTM(1997)和 GRU(2014)缓解了长依赖问题,使 RNN 在以下任务中成为主流:
  • 语音识别
  • 机器翻译
  • 文本生成
  • 时间序列预测

2. seq2seq(2014)

  • Google 提出的 Encoder–Decoder 结构,使神经机器翻译能力大幅提升。

但 RNN 的缺点也很明显:

  • 难以并行计算
  • 对长序列依赖建模仍不够理想
  • 训练成本高

这些限制为下一代架构的出现创造了机会。


五、Transformer —— 统一架构的到来(2017 至今)

1. Transformer(2017)

  • Vaswani 等人发表《Attention Is All You Need》。
  • 核心创新是 自注意力机制(Self-attention),并完全移除循环结构。

其优势包括:

  • 并行度高,训练速度远超 RNN
  • 能捕捉任意距离的依赖关系
  • 易扩展到大模型

这篇论文直接改变了 NLP 的发展方向。


六、大规模预训练模型时代(2018–2024)

1. BERT 系列(2018)

  • 基于 Transformer 的双向编码器架构。
  • 在一系列 NLP 任务上刷新记录。
  • 推动了“预训练 + 微调”的范式。

2. GPT 系列(2018–至今)

  • 以自回归 Transformer 为基础,用规模驱动性能。
  • GPT-3 证实了“大模型能力涌现”现象,使语言模型成为通用智能的基础。

3. Vision Transformer(2020)

  • 推广 Transformer 至视觉领域,第一次挑战 CNN 的核心地位。
  • 随后发展出 ViT、Swin Transformer 等大量变体。

4. 多模态模型(2021–2025)

  • CLIP、DALL·E、Flamingo 等模型展示了跨模态理解与生成能力。
  • 统一架构进一步扩展到图像、视频、音频和强化学习。

七、现状与趋势:统一架构与大模型

当前 Transformer 已成为主流,与之相关的趋势包括:

  • 大规模预训练模型成为基础设施
  • 多模态统一架构不断发展
  • 大模型驱动的应用爆发,如智能助手、自动驾驶、设计辅助
  • 更高效的注意力机制和推理优化方法持续涌现

机器学习正朝着更加通用、更具推理能力的方向迈进。


总结

从传统算法到深度学习,再到大规模 Transformer 模型,机器学习经历了三个重要阶段:

  1. 传统机器学习:依赖特征工程,模型结构简单。
  2. 深度学习时期(CNN/RNN):自动学习特征,突破视觉与序列建模。
  3. Transformer 时代:统一的架构,具备可扩展性和大模型能力,推动 AI 进入跨领域与通用阶段。

这一发展过程不仅是技术的演化,更折射出数据、算力与算法三大要素共同推动智能演进的规律。

cache分类

直接映射缓存(Direct mapped cache)

直接映射缓存将ROM里地址按照一定的映射关系固定映射到1个cache line。如下图所示:

比如对于1个64KB的ROM,现在有1个512字节的cache,cache line的size为64byte,共8个cache line,物理地址将按以下结构进行拆分

[15:9][8:6][5:0]
tagcache line indxeoffset

然后按照下面顺序进行数据操作

  1. 直接根据index判断当前对应的cache line。
  2. 将需要取数的地址tag同cache line的tag进行比较,判断数据是否命中。
  3. 如果没有命中,则刷新cache line的数据,否则直接返回数据。

直接映射的特点就是硬件结构简单,只需要根据地址即可快速判断出是否命中。但直接映射容易发生cache 颠簸(cache thrashing)的现象。

由于CPU程序的特定,大部分取值操作都发生在一段地址空间内,因此取值地址大概率会发生在同1cache line地址的映射区域内,对与上面的示例,当cpu在0x00fc和0x2fc之间反复取指令的时候,因为总是发生在cache line0,导致每次都没有命中,从而降低了cache效率。

全相联映射缓存(Full associative cache)

全相联映射情况下,每一个cache line都可以映射ROM内的任意一个块,示意图如下

比如对于同样1个64KB的ROM,现在有1个512字节的cache,cache line的size为64,共8个cache line,物理地址将按以下结构进行拆分

[15:6][5:0]
tagoffset

此时,tag的位宽将变为9bit。同样当CPU取数时,cache的操作如下:

  1. 将地址的tag同cache line存储的tag进行比对,遍历所有cache line的tag,判断是否命中。
  2. 如果没有命中,则更新某个cache line的内容,否则返回数据。

可以看出,全相联的情况下,灵活度很高,cache 利用率高,可以避免cache颠簸的发生,但同样存在以下缺点
需要存储的tag内容较多。当cache size较大时,会明显增大面积。
Cache控制器的复杂性增加,如果采用顺序查表的机制,查表延迟最多可达16个是时钟走起,采用并行查表,较多的比较器同样会增加面积以及恶化时序。因此适用于小缓存的cache。

组相联映射缓存(Set associative cache)

组相联通过多路直接映射的缓存同时工作来避免直接映射缓存带来的cache颠簸现象。示意图如下:

对比直接映射,cache分成两部分,分别为way0和way1,每个way采用直接映射,两个way相同index的cache line组成set。从set的角度看,它可以同时映射两个不同的块,从而避免了cache颠簸的现象。

同样对于1个64KB的ROM,现在有1个512字节的cache,cache line的size为64,共8个cache line,分成2个way,每个way4个cache line。物理地址将按以下结构进行拆分

[15:8][7:6][5:0]
tagset indxeoffset

当CPU访问数据时,cache控制器然以下方式进行缓存判断:

  1. 直接根据set index索引到对应的set。
  2. 在set内根据tag进行遍历,判断set内是否有命中,如果命中则到对应的cache line内进行取数,否则更新某个cache line。

组相联在全相联和直接映射直接做了平衡,兼具两者的优点。

UPF-Power Domain

Power Domains Why Do We Use Them?

Advanced low power designs look to reduce dynamic and/or leakage power by:
Creating sub regions of the design (power domains) that use advanced LP techniques
such as

  • Shut down
  • Multi voltage
  • DVFS, AVFS
  • Low Vdd standby
  • Well biasing

802.11 non-HT Phy

概述

non-HT作为WIFI的第一个OFDM物理层,第一次引入是在802.11a协议中,802.11a工作在5G,随后在802.11g中加入2.4g频段支持。

帧结构

non-HT的物理层帧格式如下:

主要包含Preable,SIGNAL以及DATA三部分。

PREAMBLE

Preamble详细格式如下图

前8us为STF,由频域为

以上的序列OFDM调制得到,由于频域插值效果为时域周期扩展,所以时序信号为4个重复的序列,加上16个CP,所以可以得到5个重复的的序列,2个组合得到10个重复序列。用于粗同步以及粗频偏估计。

后8us为LTF。包含两个重复的OFDM符号。用于精同步,以及小数频偏纠正以及信道估计。

SIGNAL

SIGANL部分紧随PREAMBLE部分之后,表示DATA部分的调制已经长度相关信息,接收端解出这些信息之后才能正确对DATA部分进行解调。SIGNAL部分由以下信息调制而来。

  1. RATE:定义调制方式以及编码效率。
  • LENGTH:指示当前帧携带的PSDU实际数据长度,单位为字节。
  • PARTY:前面所有信息的奇偶校验。
  • TAIL:卷积编码器尾bit。

以上所有bit数据经1/2的BCC编码之后,经OFDM调制之后,得到1个OFDM符号。处理过程同DATA部分。

DATA

DATA部分由SERVICE字段,实际的PSDU字段,TAIL,以及填充bit PAD经BCC编码,OFDM调制之后得到。

  1. SERVICE:16bit,用于解扰模块的初始化。
  • PSDU:MAC层来的数据。
  • TAIL:卷据编码器的尾bit,用于归零译码器。
  • PAD:由于需要发送的bit数量不一定能刚好填充完所有的OFDM符号,因此需要在数据尾部填0。填0的数量向上取整计算能够需要的OFDM符号。然后减去实际发送的BIT数量。最终得到需要PAD的BIT数量。