你的包到底是怎么卡在半路上的
一个小问题
你在电脑上 ping 一台服务器。回车按下去,屏幕显示 Request timeout。
包去哪了?谁把它丢了?
它不是"丢"的。它是在经过每一道关卡的时候,被不同的规则挡住了。这一篇带你走一遍完整路径,看看你的包从出门到消失,到底经历了什么。
路线图
先记住这张图。后面每一节,都对应这里的一个位置。

1. 路由表 —— 包该走哪个口
你的电脑里有张表。上面写着一行行的规则:去哪些地址的包,该从哪个口出去,交给谁。
开机连上 Wi-Fi,系统自动加了一条规则:去任何地址(0.0.0.0/0),都走 Wi-Fi 网卡,交给你家路由器。
你装了 VPN 软件之后,隧道软件又加了一条规则:去 100.x.x.x 的流量,走虚拟网卡。
现在路由表里有两行。系统查表的时候从上往下看,先匹配到的先走。
关键就在这里。
如果你的另一个网络程序先加了一条规则——把 100.x.x.x 这段地址也指向了 Wi-Fi ——那所有该走隧道的包就都走 Wi-Fi 出去了。但 Wi-Fi 门外没人能处理这些 100.x.x.x 地址,包就丢了。
这不是"路由坏了"。它只是听了先来的人的话。
第一个原理:路由表按顺序匹配,先注册的规则优先。很多时候不是"走不通",而是"走错了门"。
2. INPUT vs FORWARD —— 到站车还是过路车
包穿过网线,到了服务器的网卡。内核收下它,问了一个问题:
这个包的最终地址,是不是我自己的 IP?
只有两种答案:
- 是 → 交给我自己处理 → INPUT(到站车)
- 不是 → 我帮它转给别人 → FORWARD(过路车)

如果目的地是服务器 A,当前机器就是 A → INPUT,它自己处理。 如果目的地是服务器 B,当前机器是 A → FORWARD,它要把这包转出去。
这两条路走的规则完全不同。INPUT 是到站,FORWARD 是路过。转发的人要按另一套规则办事。
第二个原理:一台机器收到不是给自己的包就会转发。但转发必须经过 FORWARD 链——这里有一整套规则等着它。
3. NAT —— 不改寄件人,对方不知道怎么回
先说一个场景。
你的电脑有一个"怪地址"(比如隧道分配的 100.75.237.122),你用它 ping 一台内网服务器(服务器 B)。包不直接去 B——它先被你的隧道软件送到了中转服务器(服务器 A),由 A 再转发给 B。
A 收到包一看:目的地是 B,不是我。好,转发。
包从 A 的物理网卡发出,内容是这样的:
从谁来的:你的隧道地址
要去哪: 服务器 B 的物理地址
B 收到这个包,看了一眼寄件人:100.75.237.122?谁啊?不认识。它不在我的局域网里。我怎么回它?
B 只能把回包发给默认网关——路由器。路由器也不认识 100.75.237.122。丢掉了。

正确的做法是:A 在转发前,把"从谁来的"改成自己的地址。
这个操作叫 SNAT。你不用记术语,记住三个字就够了:改寄件人。
A 应该这样做:把包从虚拟网卡拿过来,把寄件人从"100.75.237.122"改成"172.16.0.1"(A 自己),再发出去。B 看到是从 A 来的,认识,回包。A 收到之后走隧道回传给你。
第三个原理:包在转发的时候,“从谁来的"必须改成转发者自己的地址。不改的话,对方不知道怎么回包。
4. 标记冲突 —— 两个程序抢一块黑板
前三关都过了。包到了 A 的转发链上,A 要执行"改寄件人”。
但为什么没改成?
问题出在 Linux 内核里的一个机制:打标记(packet mark)。
概念很简单:在包上写一个数字,告诉后面的处理程序"这个包做过什么了,你注意一下"。
隧道软件的流程是:
- 包从虚拟网卡进来,在 FORWARD 链上给它打个标记(比如 0x40000)
- 包走到最后一步(POSTROUTING,发出去之前的处理点),检查这个标记
- 有标记 → 做改寄件人
但中转机器上还跑着另一个网络策略软件。它也在用同一套标记系统。
它在 mangle 表的 POSTROUTING 里执行了一行命令:清掉某几位标记。

问题在于:隧道软件的标记(0x40000)正好落在这位被清掉的范围里。
时间顺序变成了这样:
包从虚拟网卡进来
→ FORWARD 链 → 隧道软件打上标记(done)
→ mangle POSTROUTING → 策略软件清掉那几位标记(gone)
→ nat POSTROUTING → 隧道软件查标记 → 没了 → 不改寄件人(broken)
隧道软件打了标记。然后包走到了 mangle 表的 POSTROUTING,策略软件在这里清掉了几位——恰好包含隧道软件的那个标记。接着包走到 nat 表的 POSTROUTING,隧道软件检查——标记没了。不改寄件人。
包被原样转发出去。目标服务器收到包,看到寄件人是一个不认识的地址,丢包。
第四个原理:两个程序用了同一块黑板记笔记。策略软件以为在擦自己的笔记,实际上把隧道软件的字也擦了。
5. 控制面 vs 数据面 —— 表面正常不代表真的正常
最后讲一个很容易被忽略的东西。
你装完隧道软件,用它的状态命令看——所有机器都在线。用它的内部 ping 测——也能测到延迟。
但系统的 ping 不通,ssh 也连不上。
这不是矛盾了吗?

原因是:隧道软件有两层,各管各的。
控制面:负责告诉所有机器"谁在线"、“延迟多少”、“路径是什么”。这一层用的是它自己的通讯协议,不依赖系统的网络栈。所以内部 ping 能通,状态能看到。
数据面:负责把你的 SSH、HTTP、所有真实流量加密、送过去、再解密。这一层靠底层的加密引擎运行。引擎停了,数据面就断了。
你之前跑了个停止命令——引擎停了。又跑了个启动命令——没有管理员权限,引擎没拉起来。
所以控制面还通着(你能看见状态),但数据面已经断了(你的 SSH 到不了)。
第五个原理:很多软件分两层——管理层和干活层。一层看起来没事,不代表另一层还在工作。
串起来看
你 ping 一台服务器,真相是:
包从应用层发出来 → 查路由表决定走哪个口 → 防火墙决定放不放行 → NAT 决定要不要改寄件人 → 出网线 → 到对方网卡 → 对方路由表决定交给本地程序还是转发 → 对方防火墙 → 对方 NAT……
每一层都可能卡住。
而大部分卡住的原因,归根结底只有一个模式:
存在两个程序,一个做了 A,另一个不知道 A 的存在,或者做了 B 把 A 覆盖了。
- 路由表——你的 VPN 先注册了一条规则,隧道软件后注册,被压住了
- 转发——策略软件在 mangle 表清标记,隧道软件的标记也被清了
- NAT——依赖标记触发的改寄件人因为标记被清而不触发
- 引擎——控制面正常不代表数据面正常,引擎停了就是停了
每一层都是同一个故事的不同版本:有人用着这块黑板,另一个人不知道,也过来写——后头的人擦了前头的人的字。
这个模式在计算机系统里比你想的更普遍。数据库的锁冲突是另一个版本,文件系统的权限竞争也是。
解决问题的方式也差不多。
要么让两个人用不同的黑板(调整配置,避开冲突),要么让一个人不依赖黑板做事。
在真实场景里,我们选了不依赖:
iptables -t nat -A POSTROUTING \
-s 隧道地址段 \
-d 内网地址段 \
-j MASQUERADE
这条规则说:来源是隧道地址段、去内网地址段的包,直接改寄件人。不查标记。不依赖任何人。
它不跟策略软件抢黑板了。
规避比争夺省力——这句话在计算机系统里成立,在公司里也成立。