为什么IPv6配置好了,Ping能通,网站却怎么都打不开

ping -6一测,延迟正常、一个包都不丢,看着IPv6配置得妥妥的;结果打开一个网站,页面加载到一半就卡死不动,SSH倒是能连上,登录成功之后敲命令却半天没反应。小包一路畅通,大包寸步难行,这种诡异的"选择性失灵",在IPv6下有个专门的名字——PMTU黑洞

IPv6做了一个跟IPv4不一样的设计决定

IPv4时代,如果一个数据包大小超过了某段链路能承受的上限(MTU),中间的路由器可以直接把这个包拆开(分片)之后继续转发,麻烦是麻烦,但好歹能自己想办法解决。IPv6在设计上彻底废掉了路由器这个能力——中间路由器不允许分片,遇到超过链路MTU的包,唯一能做的就是把包直接丢弃,然后回发一个ICMPv6"Packet Too Big"消息告诉发送方"你这个包太大了,改用这个尺寸重新发"。这是个刻意的取舍:简化了路由器的工作、提升了转发效率,但代价是把"发现最优包大小"这件事完全甩给了两端,整个流程必须依赖这条"回复消息"能顺畅地传回发送方,这套机制叫路径MTU发现(PMTUD)。

黑洞出在哪一步

麻烦就出在这条"回复消息"上——路径上只要有任何一个防火墙或者中间设备,出于"安全考虑"或者单纯配置疏忽,把ICMPv6这类消息不分青红皂白地拦截或者丢弃掉,发送方就永远收不到"包太大了"这个提醒,只会一直傻乎乎地按原来的大尺寸继续发送,这些包在半路被反复丢弃,TCP只能靠自己超时之后重传来兜底,慢得让人抓狂,严重的时候干脆卡死不动。

这也是为什么ping能通但网站打不开——ping用的探测包体积很小,怎么都不会撞上MTU这道坎,畅通无阻;而网页的实际内容数据、SSH认证成功之后的正常会话数据,动辄接近甚至超过完整MTU尺寸,一旦触发"包太大需要缩小"这个反馈机制,恰好又被沿途某个防火墙拦下了这条反馈消息,就成了黑洞——连接建立起来了(因为握手包够小),真正传数据的时候却卡死。有人真的用tcpdump抓包验证过这个现象,能清楚看到对端发回的ICMP6, packet too big, mtu 1285这条消息,说明路径中某一段链路的MTU只有1285字节,远小于常见的1500。

怎么确认自己遇到的是这个问题

对照几个典型症状,如果你现在的情况能对上大半,基本可以锁定就是PMTU黑洞:TCP连接能正常建立(因为握手包小);网页加载到一半卡住不动;SSH能连上,认证成功后却卡死;文件下载开始几KB之后就停滞不前。

怎么解决

核心思路是确保ICMPv6的"Packet Too Big"这条消息(Type 2)在整条路径上畅通无阻,检查自己VPS上的防火墙规则,明确放行这类消息:

1
2
ip6tables -I INPUT 1 -p icmpv6 --icmpv6-type packet-too-big -j ACCEPT
ip6tables -I OUTPUT 1 -p icmpv6 --icmpv6-type packet-too-big -j ACCEPT

如果自己这边的防火墙确认没问题,问题还是没解决,说明黑洞出在你控制不了的上游链路某处,这种情况下自己这台VPS改配置帮不上忙,只能等对方网络修复,或者换一条不经过这段问题链路的路由。

顺带一提

同样是"IPv6看着配置好了,实际却在拖后腿"这类问题,之前写的为什么VPS各种连接、下载都很慢,罪魁祸首可能是IPv6讲的是另一种情况——那篇是连接建立阶段就被拖慢,这篇是连接建立之后传数据被卡死,两种症状容易混在一起,遇到问题先分清楚是"连不上"还是"连上了传不动",排查方向完全不同。如果确认问题出在SSH这个具体场景,另外还有一篇专门讲VPS SSH连接很慢是什么原因,讲的是跟MTU完全无关的另外两个常见原因,也可以对照排除。