
IPv6-重定向、PMTU、GRE隧道这三个词被摆在一起通常不是教材的章节安排而是某次排查中被同时撞上的三个坑。我在处理跨站点网络互联时最怕遇到的组合就是隧道建起来了、小包能 ping 通、路由表看着也没问题但一传大文件就卡死SSH 登录到一半没反应某个业务页面转圈直到超时。这类现象背后IPv6 的重定向机制、路径 MTU 发现PMTU以及 GRE 隧道封装开销三者往往是连在一起的隧道改了路径路径决定了 MTUMTU 触发 ICMPv6 差错报文而差错报文和重定向又都可能被策略拦掉或误信。这篇内容讲的就是这三件事怎么咬合在一起以及怎么把它们一个个拆开、验证、落地配置。适合已经能配通隧道、但对为什么大包过不去说不清楚的网络运维和系统工程师也适合刚接触 IPv6、只会照抄配置模板的新手——我会把 MTU 的每一字节开销都算给你看把验证命令按顺序列出来照着敲就能复现。全文的地址一律使用标准文档保留段配置基于通用开源系统上的 iproute2 工具集实际环境里换掉地址和接口名即可。1. 这三个词为什么会出现在同一个故障现场1.1 一次能通小包、通不了大包的现场还原先说一个典型的现场。两端各有出口设备中间隔着一段不属于自己管辖的网络为了把两侧的 IPv6 网段连起来在两端设备之间拉了一条 GRE 隧道隧道接口上配了 IPv6 地址两侧内网路由指向隧道。配置完成ping -6通traceroute也正常邻居关系一度看起来没问题。然后业务上线问题来了SSH 能连上但敲命令没回显HTTP 请求发出去了响应回不来某些 API 调用超过几秒就断。抓包会看到一个很有规律的现象——握手阶段的小包全部成功一旦进入数据传输阶段特定大小的报文就开始消失发送方反复重传最后超时。这时候如果去看隧道接口的 MTU多半还是默认值如果去看防火墙策略多半只放行了必要的协议ICMPv6 差错报文被当作诊断流量挡在外面。这三个词在这类现场里的分工非常明确。GRE 隧道决定了这条路径上真正可用的 MTU 是多少——封装要占字节占完剩下的才是内层可用的PMTU 决定了当报文超过这个值时网络该怎么通知发送方IPv6 重定向则可能在隧道建好之后悄悄把某台主机的下一跳改回物理接口让流量绕开隧道走一条你以为不存在的路径。三者任意一个出问题表现出来的症状都可能是时通时不通非常容易被误判成链路质量或者应用层问题。我个人的经验是只要涉及隧道先把 MTU 账算清楚再把 ICMPv6 的放行策略理一遍最后才去看路由和重定向。顺序反过来往往会在路由表里绕很久最后发现问题根本不在路由上。1.2 三个机制在报文路径上的分工把一条报文从主机 A 送到主机 B 的完整过程摊开看三个机制作用的位置完全不同。GRE 隧道作用在封装层。它在原始报文外面再套一层 IP 头和 GRE 头外层头由隧道两端负责解释中间网络只看外层。这意味着中间网络看到的最大报文长度限制是针对原始报文 封装开销的而不是针对原始报文本身。这是所有隧道类技术的共性也是 MTU 问题的根源。PMTU 作用在传输层与网络层的交界。IPv6 规定中间路由器不再对报文分片只有源端可以分片。于是当一台路由器发现报文超过了出接口的 MTU它唯一能做的是丢弃并回一个 ICMPv6 的报文过大报文让源端自己把报文改小。这个机制能不能生效完全取决于那个差错报文能不能回到源端。IPv6 重定向作用在最后一跳的选择上。它不改封装也不改 MTU它改的是主机的下一跳缓存。当一台路由器发现某个主机把报文发给了自己但报文的目的地其实有更直接的第一跳同链路上的另一台路由器它会发一个重定向报文告诉主机下次直接发给那台。在多出口、双上行、隧道旁挂的环境里这个好意很容易让流量跑到预期之外的地方去。三者的关系可以这么理解隧道把路修窄了PMTU 是路边的限高提示牌重定向是路口的热心人给你指了另一条道。提示牌被别人摘了你就会一直撞限高杆热心人指错了道你就绕远了还不知道。2. IPv6重定向善意的指路与它的代价2.1 ICMPv6 Type 137 报文里装了什么IPv6 的重定向由 ICMPv6 的 Type 137 报文承载报文类型名叫 Redirect。它的结构里除了常规的 ICMPv6 头类型、代码、校验和还有三个关键字段保留位、目标地址Target Address、目的地址Destination Address后面跟着可选的选项字段。目标地址是你下次应该发给谁——也就是更优的下一跳目的地址是对于去往这个目的地的流量——限定了这条建议的适用范围。这两个字段组合起来等价于一条主机路由去往 Destination Address 的流量下一跳改成 Target Address。选项字段里通常包含目标链路层地址让主机不用再发一次邻居请求就能直接封装。这个设计很省事但也意味着一条重定向报文本质上就是一次远程修改主机路由表的操作。主机收到之后如果直接采信路由缓存就被改写了。所以在受控环境里这个报文既可以用来优化路径也可以被用来做流量牵引甚至做中间人。这就是为什么边界设备、服务器、以及任何开启了转发的设备上通常都建议把它关掉。还有一点容易被忽略ICMPv6 的重定向报文本身没有认证机制。主机无法验证这条建议是不是真的来自当前的第一跳路由器只能靠源地址检查和跳数限制Hop Limit 必须为 255做基本校验。这两道校验能挡住跨网段伪造但挡不住同链路上的伪造。2.2 触发条件、主机接受策略与关闭场景路由器什么时候会发重定向条件比想象中严格。收到的报文必须是从某个接口进来、又从同一个接口出去也就是这台的设备对这个目的地来说不是必经之路报文源地址必须和它查到的更优下一跳在同一个链路上那个更优下一跳不能就是报文源本身而且这条重定向不能指向默认路由器自己。这几个条件同时满足才会发。主机侧接受与否由内核参数决定。在通用开源系统上IPv6 的重定向接受开关是net.ipv6.conf.all.accept_redirects和net.ipv6.conf.接口.accept_redirects默认通常是开启的。但这里有个隐含前提一旦设备开启了转发net.ipv6.conf.all.forwarding1内核在转发路径上就不会处理重定向报文了因为它自己就是路由器不应该再听别的路由器指路。把常用参数和适用场景整理一下参数常见默认值建议值主机建议值路由器/边界net.ipv6.conf.all.accept_redirects11受控内网或 00net.ipv6.conf.default.accept_redirects1跟随 all0net.ipv6.conf.all.forwarding001net.ipv6.conf.all.accept_ra依发行版1需要自动配置时0手工配地址关闭的操作很直接sysctl -w net.ipv6.conf.all.accept_redirects0 sysctl -w net.ipv6.conf.default.accept_redirects0 # 持久化写进 /etc/sysctl.d/ 下的配置文件注意关闭重定向接受之后如果网络里确实存在双出口且依赖重定向来优化路径主机会一直走默认网关可能造成次优路径。这不是错只是要多绕一跳。判断标准是这条路径的额外延迟你能不能接受。能接受就关掉换取可预测性不能接受就得用策略路由手工指定而不是依赖重定向。在隧道场景里重定向最典型的副作用是流量旁路。假设主机 A 与隧道端点在同一网段A 的默认网关是隧道端点 R1但同网段里还有另一台路由器 R2 也能到达目的网段。R1 转发 A 的报文时会发现你这个目的地让 R2 转更直接于是发重定向。A 采信之后去往该目的地的流量就绕过隧道走 R2 了。如果 R2 那条路径的 MTU 或策略与隧道不同就会出现一部分业务正常、一部分业务异常的割裂现象而且从 A 的路由表主表里根本看不出变化因为改写的是缓存条目。排查这类问题的办法是看主机的路由缓存或直接抓 ICMPv6 报文# 抓重定向报文 tcpdump -ni eth0 icmp6 and ip6[40] 137 # 查看去往某目的地实际选中的下一跳 ip -6 route get 2001:db8:200::10ip -6 route get的输出里会带上它最终选中的网关和出口如果这个网关不是你预期的那个那就是缓存被动过了。3. PMTUIPv6世界里路由器不再帮你分片3.1 Packet Too Big 报文与源端的分片责任IPv6 和 IPv4 在分片这件事上的态度完全不同。IPv4 里中间路由器遇到超出出接口 MTU 的报文只要 DF 位没置位它可以自己切一刀分片转发。IPv6 把这个能力拿掉了中间路由器一律不分片超了就丢然后回一个 ICMPv6 Type 2报文名就叫 Packet Too Big里面带一个 MTU 字段告诉源端下次别超过这个数。这个设计的好处是把分片责任收敛到源端和目的端中间设备不用维护分片状态转发效率更高也不会出现分片重组带来的攻击面。代价是把可靠性押在了差错报文能不能送达上。一旦差错报文被丢弃源端就永远不知道要改小会一直重传同一个超大报文形成所谓的 PMTU 黑洞。MTU 字段有个下限要求不能小于 1280 字节。这是 IPv6 规定的最小链路 MTU所有实现都必须能转发 1280 字节的报文。所以在 IPv6 里配隧道隧道接口的 MTU 如果被算到 1280 以下等于直接违反协议要求很多实现会拒绝或者行为异常。这条红线在做嵌套隧道时必须反复确认。源端收到 Packet Too Big 之后该做什么RFC 里有明确的建议更新该目的地的 PMTU 缓存减小发送尺寸并且后续报文继续按新的尺寸发。同时源端应该周期性地尝试增大以便路径变化后能恢复吞吐。实际实现里这个周期性尝试做得很保守很多系统只在新连接建立时才重新探测所以一次错误的 PMTU 缓存可能影响很长时间。3.2 PMTU黑洞的三种成因PMTU 黑洞在实际网络里出现的频率远高于理论预期成因基本可以归成三类。第一类是差错报文被策略拦截。很多安全策略的思路是只放行必要流量而 ICMPv6 被笼统地当作诊断协议挡掉。问题在于 ICMPv6 不只是 ping它承担了邻居发现、地址自动配置、差错通知等基础功能。把 Type 2 挡掉等于把整条路径的 MTU 反馈通道切断了。IPv4 里类似的问题是挡 ICMP Type 3 Code 4需要分片IPv6 里就是 Type 2。第二类是路径上存在非对称转发。报文去程走一条 MTU 较大的路径回程走一条 MTU 较小的路径差错报文从回程方向发回来中间某一跳把它丢了。或者去程和回程的出口设备不同差错报文回来的接口不是发出接口被反向路径检查拦下。这类问题的特点是单向大包不通用 ping 测的时候如果只测一个方向很容易漏掉。第三类是应用层不响应。有些自定义协议或者老旧的 UDP 应用根本不处理 ICMP 差错收到 Packet Too Big 也无动于衷继续按原尺寸发。TCP 有内核兜底UDP 就只能靠应用自己做。现在常见的基于 UDP 的传输协议大多实现了自己的探测机制但并不是全部。提示判断是不是 PMTU 黑洞最快的办法是找一个确定超大的报文用不分片的方式发过去同时在两端和中间关键节点抓 ICMPv6 Type 2。如果发送端一直在重传同样的尺寸、而中间节点确实发了 Type 2 但发送端没收到那基本就锁定了。抓包过滤器写icmp6 and ip6[40] 2。3.3 Linux侧影响PMTU行为的开关在通用开源系统上和 PMTU 相关的可调项主要有几个理解它们各自的作用范围很重要不要一股脑全改。net.ipv4.tcp_mtu_probing控制 TCP 的 MTU 探测行为取值 0/1/2。0 是关闭完全依赖 ICMP 反馈1 是在检测到重传超时后启用探测2 是一开始就启用。开启探测的好处是在 ICMP 被挡的环境里 TCP 能自己找到合适的尺寸代价是要多发一些探测报文且探测过程本身有延迟。这个参数名字里带 v4但对 IPv6 上的 TCP 连接同样生效因为它是 TCP 层的行为。路由上的mtu属性可以用来手工锁定某个目的地的 PMTU。当你知道某条路径的实际 MTU 且不指望 ICMP 反馈时这是最直接的办法# 锁定去往某目的网段的 PMTU ip -6 route add 2001:db8:200::/64 via 2001:db8:100::2 dev gre1 mtu 1400 # 或者改一条已有路由 ip -6 route change 2001:db8:200::/64 mtu 1400接口的mtu属性决定这个接口发出的报文最大长度。隧道接口的 MTU 必须手工设成小于物理接口 MTU 减去封装开销的值否则内核会按物理接口的 MTU 来发封装之后直接被外层链路丢弃。advmss是路由属性里控制 TCP MSS 通告值的配合 MTU 使用能避免对端按过大的尺寸发。它只影响 TCP对 UDP 无效。# 查看当前某条路由的完整属性 ip -6 route show 2001:db8:200::/64 # 查看接口 MTU ip -d link show gre1参数/属性作用层影响范围使用建议tcp_mtu_probingTCP全局ICMP 不可控时设为 1接口mtu链路单接口隧道接口必须手工设路由mtu网络单个前缀已知路径 MTU 时锁定路由advmssTCP单个前缀配合 MTU 使用tcp_base_mssTCP全局探测起始值一般不动一个常见的误区是把隧道接口 MTU 设得很小以求保险。设成 1280 确实不会丢包但所有经过这条隧道的 TCP 连接 MSS 都会被压到 1220 左右吞吐直接打折而且大量小包让封装效率变得很难看。正确做法是算出精确值宁可贴着上限也不要过度保守。4. GRE隧道先把封装开销算清楚再动手4.1 GRE头长度与可选字段GRE 的全称是通用路由封装它的定位很朴素把一种协议的报文当成载荷塞进另一种协议的报文里传。外层可以是 IPv4 也可以是 IPv6内层同样可以随意组合这就给了四种基本组合。基础的 GRE 头是 4 个字节里面有一个标志位字段、一个协议类型字段。协议类型告诉对端我里面装的是什么比如 0x0800 是 IPv40x86DD 是 IPv6。除了这 4 个字节GRE 还支持三个可选字段每个 4 字节校验和C 位置位时出现、密钥K 位置位时出现、序列号S 位置位时出现。这三个字段不是必须的但对端必须能识别——如果一端发了带 Key 的报文而另一端没配 Key报文会被丢弃隧道看起来就是起来了但不通。所以在 Linux 上配置时key参数要么两端都配且一致要么两端都不配。校验和字段在现代实现里基本不用因为外层 IP 头本身就有校验和再加一层纯属浪费。序列号字段用于乱序检测在有多个外层路径的场景下有意义但在点对点隧道里通常也不需要。外层 IP 头是固定的外层用 IPv4 时是 20 字节不含选项外层用 IPv6 时是 40 字节。这 20 字节的差异在 MTU 计算里非常关键很多人算错就是因为默认按 IPv4 的 20 算结果外层换成了 IPv6 就差了 20 字节。所以最小开销公式是封装开销 外层IP头长度 GRE基础头(4) 可选字段长度之和举例IPv6 over IPv4 的 GRE 隧道不开可选字段开销 20 4 24 字节。物理链路 MTU 是 1500那么隧道接口可用的 MTU 就是 1476。4.2 四种内外层组合的MTU对照把四种组合的开销和隧道 MTU 都算出来做成表放在手边配置的时候直接查比每次现算靠谱。内层载荷外层封装外层IP头GRE头总开销物理1500时隧道MTUIPv4IPv4204241476IPv6IPv4204241476IPv4IPv6404441456IPv6IPv6404441456表里要注意一个容易混淆的点隧道接口的 MTU 指的是内层报文的最大长度也就是隧道接口看起来能发多大的包。它的计算只跟外层头和 GRE 头有关跟内层是 IPv4 还是 IPv6 无关。内层是 IPv6 还是 IPv4 影响的是这个 MTU 够不够用——IPv6 要求至少 1280IPv4 没有这个硬性要求。如果开了 Key 字段每种组合再减 4同时开 Key 和序列号减 8。这个减法必须做否则大包会以极低的概率、在特定长度上丢非常难查。再往下算一层TCP 的 MSS 该怎么设。MSS 是 TCP 载荷的最大长度等于 MTU 减去 IP 头减去 TCP 头。以 IPv6 over IPv4 GRE、隧道 MTU 1476 为例MSS 1476 - 40(IPv6头) - 20(TCP头) 1416而如果主机不知道隧道存在按物理口 1500 去算MSS 会是 1500 - 40 - 20 1440加上头就是 1500 字节封装后变成 1524超过物理链路 1500直接被丢或者触发 Packet Too Big。这就是握手正常、传数据卡死的经典成因握手包很小怎么都能过数据包填满 MSS一过隧道就超。4.3 隧道叠加与1280字节红线比单层隧道更麻烦的是隧道叠加——在一条隧道里再跑一条隧道。这种结构在跨多个管理域的互联里并不罕见每一层都要扣一次开销。假设物理 1500第一层是 IPv6 over IPv4 GRE扣 24剩 1476。第二层在这条隧道上再建一条 IPv4 over IPv6 GRE外层是 IPv640 字节扣 44剩 1432。看起来还行。但如果第一层的外层换成 IPv6第一层扣 44 剩 1456第二层再扣 44 剩 1412。三层叠加就接近 1360 了。再考虑某些链路本身 MTU 就不是 1500比如某些承载网络给的是 1400 或者 1280。如果物理链路只有 1400单层 IPv6 over IPv6 GRE 扣 44 剩 1356勉强过 1280 红线再叠一层就掉到 1312第三层直接跌破 1280IPv6 内层就跑不起来了。注意IPv6 的 1280 字节最小 MTU 是硬性要求不是建议值。隧道接口 MTU 低于 1280 时IPv6 的邻居发现、地址自动配置、甚至基本的 TCP 握手都可能出问题表现为隧道 up 但完全不通。做多层设计时每一层的 MTU 都要写进设计文档而不是配完再看。缓解手段有两个方向。一个是压缩封装能不用 Key 就不用外层尽量选 IPv4省 20 字节。另一个是降低内层协议的 MSS 期望值同时对 UDP 类流量做应用层的尺寸控制。如果两条路都走不通就得考虑换一种承载方式而不是硬扛。5. 实操搭一条IPv6 over IPv4的GRE隧道并验证PMTU5.1 拓扑、地址规划与前置检查为了能完整复现我按最常见的场景设计拓扑两台设备 A 和 B 分处两个网络中间隔着一段只能跑 IPv4 的承载网。两侧各有一个 IPv6 内网段目标是让两个内网段互通。地址规划全部使用标准文档保留段实际部署时换成自己的用途设备A设备B承载网接口IPv4192.0.2.1/24198.51.100.2/24承载网互联地址段192.0.2.0/24 与 198.51.100.0/24 之间假设可达同左隧道接口IPv62001:db8:100::1/642001:db8:100::2/64内网段IPv62001:db8:200::/642001:db8:300::/64物理接口 MTU15001500配置前先做三项前置检查这三项漏掉任何一项后面都会白折腾。第一确认两端 IPv4 地址互相可达这是外层承载的前提。第二确认物理接口实际的 MTU 是多少不要假设一定是 1500用ip link show看承载网如果中间经过了别的隧道实际值可能更小。第三确认两端对 GRE 协议IP 协议号 47没有策略拦截中间如果有策略设备需要放行这个协议号而不是某个端口。# 前置检查 ip -4 addr show ip link show eth0 | grep -o mtu [0-9]* ping -c 3 198.51.100.25.2 两端接口配置与MTU落地先在 A 上建隧道接口。用 iproute2 的ip link add ... type gre语法外层地址用local和remote指定ttl显式设成 64避免某些实现默认 0 导致外层 TTL 从 0 开始、被中间设备直接丢弃。# 设备A ip link add gre1 type gre local 192.0.2.1 remote 198.51.100.2 ttl 64 ip link set gre1 mtu 1476 up ip -6 addr add 2001:db8:100::1/64 dev gre1# 设备B ip link add gre1 type gre local 198.51.100.2 remote 192.0.2.1 ttl 64 ip link set gre1 mtu 1476 up ip -6 addr add 2001:db8:100::2/64 dev gre1这里的 MTU 1476 是算出来的不是拍的1500 减去外层 IPv4 头 20 减去 GRE 基础头 4。如果配置里加了key比如key 100那 MTU 就要改成 1472。这一步必须在建接口之后、加地址之前做否则内核会用默认值先走一遍邻居发现可能留下错误的缓存。还有个细节ip link add默认会把隧道接口当作点对点设备加 IPv6 地址时用 /64 是为了让两端在同一前缀下实际点对点链路用 /127 更规范能避免同网段地址扫描之类的干扰。如果改成 /127两端地址分别是 2001:db8:100::0/127 和 2001:db8:100::1/127邻居发现的开销也更小。配完先确认状态ip -d link show gre1 ip -6 addr show dev gre1ip -d会把隧道参数全部打出来包括 local、remote、ttl、key。这个输出建议存一份后面排查时对照。5.3 路由、MSS钳制、重定向开关隧道接口通了接下来要把内网路由指过去同时处理 MSS 和重定向。# 设备A去往对端内网走隧道 ip -6 route add 2001:db8:300::/64 dev gre1 mtu 1476 advmss 1416 # 设备B对称配置 ip -6 route add 2001:db8:200::/64 dev gre1 mtu 1476 advmss 1416mtu 1476是给这条路由锁定 PMTUadvmss 1416是让经过这条路由的 TCP 连接在 SYN 里通告 1416 的 MSS。1416 的算法是 1476 减 40IPv6 头减 20TCP 头。这两个值必须一致改了 MTU 就要同步改 advmss否则通告值和实际能发的大小对不上。如果内网主机很多逐条路由改 advmss 不现实可以在转发路径上做 MSS 钳制# 通用开源系统上用 nftables 的写法 nft add table inet mangle nft add chain inet mangle forward { type filter hook forward priority mangle; } nft add rule inet mangle forward tcp flags syn tcp option maxseg size set rt mtu这条规则的意思是把经过的 SYN 报文里的 MSS 选项直接改成当前路由的 MTU 对应的值。好处是不用逐条路由配置坏处是只在转发路径生效本机发起的连接不经过 forward 钩子还得靠advmss。然后处理重定向。设备 A 和 B 都开启了 IPv6 转发因为要转发内网流量按照内核行为它们在转发路径上不会处理重定向报文这一点不用额外配。但它们自己作为主机发起的流量比如管理流量仍然可能接受重定向所以建议显式关掉sysctl -w net.ipv6.conf.all.accept_redirects0 sysctl -w net.ipv6.conf.default.accept_redirects0内网主机侧则要评估如果内网是单出口直接关掉最省心如果内网有双出口且依赖重定向就得保留但要确保重定向指的那条路径 MTU 和隧道一致否则又回到老问题上。5.4 四步验证从ping到抓包配完不能只看接口 up 就算完必须按顺序验证四层。第一步验证连通性用小包ping -6 -c 3 2001:db8:100::2第二步验证大包。IPv6 没有 DF 位源端本来就不分片所以直接加大尺寸试到失败为止失败的那个尺寸加 1 就是实际的 PMTU# 逐步加大找到刚好能过的尺寸 ping -6 -c 3 -s 1400 2001:db8:100::2 ping -6 -c 3 -s 1436 2001:db8:100::2 ping -6 -c 3 -s 1437 2001:db8:100::21436 这个数字是算出来的隧道 MTU 1476 减去 IPv6 头 40等于 ICMPv6 载荷能带的最大值。如果 1436 能过、1437 不能过说明隧道 MTU 设置正确PMTU 反馈链路也是通的。第三步验证端到端。从 A 的内网主机 ping B 的内网主机同样加大尺寸。这一步能测出内网接口的 MTU 有没有也需要调整——如果内网接口还是 1500而流量要经过隧道那么大包在内网段就已经按 1500 发出去了到了隧道口才被丢。这种情况要在内网侧也做 MSS 钳制或者把内网接口的 MTU 一起调下来。第四步抓包确认。在 A 的承载口上抓 ICMPv6 差错报文看有没有 Type 2 发出来以及发送频率tcpdump -ni eth0 icmp6 and ip6[40] 2 -c 20如果看到大量 Type 2 反复出现说明发送端没有正确响应或者响应了但下一次又按大尺寸发通常是应用层没处理。如果一条都没有说明要么 MTU 设置正确没有超限要么差错报文在中间就被拦了——这时候要在中间节点上抓确认到底在哪一段消失。最后用ss看实际连接的 MSS 和 PMTU这是最直观的ss -6 -tin dst 2001:db8:300::10输出里会带mss和pmtu两个字段对照你配的值对得上就说明整条链路是一致的。6. 排查实录六类典型故障的定位路径6.1 症状—根因—处置速查表隧道类问题最难的地方是症状高度相似但根因分散在好几层。我把实际遇到过的情况整理成表按症状反查能省不少时间。症状可能根因快速确认方法处置小包通、大包不通隧道 MTU 未调或 MSS 未钳制ping -6 -s 1436试探设隧道 MTU 与 advmss隧道 up 但完全不通两端 Key 不一致或一端配了一端没配ip -d link show对比参数两端参数对齐间歇性丢大包开了 Key 但 MTU 没减 4计算开销是否含可选字段MTU 再减可选字段长度首包通、后续全断PMTU 黑洞ICMPv6 Type 2 被拦中间节点抓ip6[40]2放行 ICMPv6 差错报文某些目的地绕路主机接受了重定向缓存被改写ip -6 route get dst看网关关闭 accept_redirects单向不通非对称路由 反向路径检查两端分别抓包调整路由或放宽检查邻居关系反复震荡协议报文超过隧道 MTU抓协议报文尺寸调 MTU 或调协议定时器表里最值得展开的是首包通、后续全断这一类。它和大包不通看起来像但成因不同后者是尺寸问题前者是反馈通道问题。判断方法是看失败的时间点——如果连接建立后第一个数据包就卡住且重传次数不断累积基本就是 PMTU 黑洞如果只有特定大小的包失败小包一直正常那是 MTU 配置问题。6.2 几个容易反复踩的坑第一个坑是只改隧道接口 MTU不改路由 advmss。隧道接口 MTU 改了内核发出的报文尺寸确实变小了但 TCP 的 MSS 是在握手时协商的协商值来自路由的 advmss 或者出接口 MTU。如果路由上没有 advmss内核会按接口 MTU 算这时候如果流量是从内网主机发起、经过设备转发主机算 MSS 用的是它自己的接口 MTU1500根本不知道隧道存在。结果就是主机按 1440 的 MSS 发到隧道口被丢。解决办法要么在转发设备上做 MSS 钳制要么把内网主机的接口 MTU 也调下来后者影响面大不推荐。第二个坑是抓包位置选错。查 PMTU 问题时很多人只在两端抓看不到中间的差错报文。实际上 Packet Too Big 是丢弃报文的那台设备发出的如果丢弃发生在承载网中间两端的抓包都看不到。正确做法是在隧道入口和出口分别抓再在承载网的关键节点抓三点对照才能定位。第三个坑是忽略外层协议的 TTL 和跳数。GRE 隧道默认继承或者设一个固定的 TTL如果设得太小报文在承载网里跑几跳就过期了表现出来是距离近的对端能通距离远的不能通。设成 64 是常规做法设成 255 也可以但不要留默认的 0。第四个坑是在虚拟化环境里忘了底层 MTU。物理机上是 1500但虚机网卡可能被设成 1450 或者更低容器网络的 overlay 还会再扣一层。这种情况下你在虚机里算出来的隧道 MTU 是基于错误的物理 MTU怎么调都不对。上车之前先ip link show把所有接口的 MTU 打印一遍从上往下捋。第五个坑是用 ping 测通就认为没问题。ICMPv6 的 echo 报文默认尺寸很小而且很多实现会对 echo 做特殊处理。真正能暴露问题的是大尺寸的 TCP 传输或者带载荷的 UDP。验证时至少要跑一次实际的文件传输或者iperf之类的大流量测试别只看 ping 结果。提示把所有涉及隧道的接口 MTU、路由 advmss、MSS 钳制规则整理成一张表写进变更文档。这类配置最怕的是后人接手时只看到一半改了一个值没改另一个问题复现时又要从头查一遍。我在实际做跨域互联时最深的体会是隧道这东西的配置部分其实很简单几条命令的事真正花时间的是把开销算清楚、把反馈通道打通、把不该有的优化关掉。MTU 这件事没有大概配一下的空间差 4 个字节就是差 4 个字节表现出来就是大量连接卡在某个固定阶段。每次动手之前先把那张开销表填一遍把 ICMPv6 的放行策略单独列一条把重定向的开关状态记下来后面出问题时能少绕很多路。至于多层隧道我的建议是能不加就不加每加一层就多一份需要长期维护的账等到某天承载网悄悄把 MTU 从 1500 调到 1400翻车的时候连是哪一层出的问题都要查半天。