你的包到底是怎么卡在半路上的

一个小问题

你在电脑上 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(过路车)

INPUT vs 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。丢掉了。

NAT / 改寄件人

正确的做法是:A 在转发前,把"从谁来的"改成自己的地址。

这个操作叫 SNAT。你不用记术语,记住三个字就够了:改寄件人

A 应该这样做:把包从虚拟网卡拿过来,把寄件人从"100.75.237.122"改成"172.16.0.1"(A 自己),再发出去。B 看到是从 A 来的,认识,回包。A 收到之后走隧道回传给你。

第三个原理:包在转发的时候,“从谁来的"必须改成转发者自己的地址。不改的话,对方不知道怎么回包。


4. 标记冲突 —— 两个程序抢一块黑板

前三关都过了。包到了 A 的转发链上,A 要执行"改寄件人”。

但为什么没改成?

问题出在 Linux 内核里的一个机制:打标记(packet mark)。

概念很简单:在包上写一个数字,告诉后面的处理程序"这个包做过什么了,你注意一下"。

隧道软件的流程是:

  1. 包从虚拟网卡进来,在 FORWARD 链上给它打个标记(比如 0x40000)
  2. 包走到最后一步(POSTROUTING,发出去之前的处理点),检查这个标记
  3. 有标记 → 做改寄件人

但中转机器上还跑着另一个网络策略软件。它也在用同一套标记系统。

它在 mangle 表的 POSTROUTING 里执行了一行命令:清掉某几位标记

黑板冲突

问题在于:隧道软件的标记(0x40000)正好落在这位被清掉的范围里。

时间顺序变成了这样:

包从虚拟网卡进来
  → FORWARD 链 → 隧道软件打上标记(done)
  → mangle POSTROUTING → 策略软件清掉那几位标记(gone)
  → nat POSTROUTING → 隧道软件查标记 → 没了 → 不改寄件人(broken)

隧道软件打了标记。然后包走到了 mangle 表的 POSTROUTING,策略软件在这里清掉了几位——恰好包含隧道软件的那个标记。接着包走到 nat 表的 POSTROUTING,隧道软件检查——标记没了。不改寄件人。

包被原样转发出去。目标服务器收到包,看到寄件人是一个不认识的地址,丢包。

第四个原理:两个程序用了同一块黑板记笔记。策略软件以为在擦自己的笔记,实际上把隧道软件的字也擦了。


5. 控制面 vs 数据面 —— 表面正常不代表真的正常

最后讲一个很容易被忽略的东西。

你装完隧道软件,用它的状态命令看——所有机器都在线。用它的内部 ping 测——也能测到延迟。

但系统的 ping 不通,ssh 也连不上。

这不是矛盾了吗?

控制面 vs 数据面

原因是:隧道软件有两层,各管各的。

控制面:负责告诉所有机器"谁在线"、“延迟多少”、“路径是什么”。这一层用的是它自己的通讯协议,不依赖系统的网络栈。所以内部 ping 能通,状态能看到。

数据面:负责把你的 SSH、HTTP、所有真实流量加密、送过去、再解密。这一层靠底层的加密引擎运行。引擎停了,数据面就断了。

你之前跑了个停止命令——引擎停了。又跑了个启动命令——没有管理员权限,引擎没拉起来。

所以控制面还通着(你能看见状态),但数据面已经断了(你的 SSH 到不了)。

第五个原理:很多软件分两层——管理层和干活层。一层看起来没事,不代表另一层还在工作。


串起来看

ping 一台服务器,真相是:

包从应用层发出来 → 查路由表决定走哪个口 → 防火墙决定放不放行 → NAT 决定要不要改寄件人 → 出网线 → 到对方网卡 → 对方路由表决定交给本地程序还是转发 → 对方防火墙 → 对方 NAT……

每一层都可能卡住。

而大部分卡住的原因,归根结底只有一个模式:

存在两个程序,一个做了 A,另一个不知道 A 的存在,或者做了 B 把 A 覆盖了。

  • 路由表——你的 VPN 先注册了一条规则,隧道软件后注册,被压住了
  • 转发——策略软件在 mangle 表清标记,隧道软件的标记也被清了
  • NAT——依赖标记触发的改寄件人因为标记被清而不触发
  • 引擎——控制面正常不代表数据面正常,引擎停了就是停了

每一层都是同一个故事的不同版本:有人用着这块黑板,另一个人不知道,也过来写——后头的人擦了前头的人的字。

这个模式在计算机系统里比你想的更普遍。数据库的锁冲突是另一个版本,文件系统的权限竞争也是。

解决问题的方式也差不多。

要么让两个人用不同的黑板(调整配置,避开冲突),要么让一个人不依赖黑板做事。

在真实场景里,我们选了不依赖:

iptables -t nat -A POSTROUTING \
  -s 隧道地址段 \
  -d 内网地址段 \
  -j MASQUERADE

这条规则说:来源是隧道地址段、去内网地址段的包,直接改寄件人。不查标记。不依赖任何人。

它不跟策略软件抢黑板了。

规避比争夺省力——这句话在计算机系统里成立,在公司里也成立。