一次IPSec VPN隧道因MTU不匹配导致ERP系统远程访问异常的排查实录
问题背景
公司在全国有8个分支机构,均通过FortiGate防火墙与总部建立IPSec VPN隧道,日常办公依赖总部的SAP ERP系统进行订单录入、库存查询和财务审批。周一上午9点,华南分公司的30多名员工集中登录ERP系统时,纷纷反馈页面加载极慢——登录界面要等2-3分钟才能显示,进入后点击菜单经常卡死超时,只能反复刷新碰运气。
奇怪的是,Ping总部的服务器完全正常,延迟稳定在15ms左右,traceroute也每一跳都通畅。其他通过VPN访问的服务(如邮件OA)虽然也有点慢,但不像ERP这样完全不可用。网络监控平台上VPN隧道状态显示绿灯”Connected”,流量统计正常,没有任何告警。问题持续了整个上午,严重影响了华南分公司的业务运转,紧急程度极高——财务月结审批被迫延迟,客户订单无法及时录入。
故障现象
故障的表现非常”诡异”,具有典型的MTU问题特征——小包通、大包不通:
现象一:Ping正常但ERP不可用
从华南分公司Ping总部ERP服务器10.10.1.100,100字节的小包100%成功,延迟15ms;但当Ping包大小超过1400字节时:
1 | C:\Users\zhao> ping 10.10.1.100 -l 100 -n 10 |
现象二:ERP登录页面加载极慢或超时
浏览器访问https://erp.company.com时,登录页的HTML框架能加载出来(小数据包),但包含CSS/JS资源的大响应体经常卡在”传输中”状态,最终浏览器报ERR_CONNECTION_TIMED_OUT。
现象三:SSH大文件传输失败
通过SSH从华南服务器向总部scp一个50MB的备份文件,传输开始后几秒就卡死:
1 | $ scp backup.tar.gz [email protected]:/tmp/ |
但小文件(几KB的配置文件)可以正常传输。
现象四:TCP连接建立正常但数据传输中断
用Wireshark在华南分公司的客户端抓包,可以看到TCP三次握手顺利完成,客户端发送HTTP GET请求后,服务器回复了第一个数据包(SYN+ACK后的初始窗口),但当服务器连续发送多个满载(满1460字节)的TCP段时,客户端迟迟收不到后续数据,最终触发TCP重传,重传若干次后连接被reset。
关键发现:抓包中看不到任何ICMP Fragmentation Needed消息,说明IP分片所需的通知被某处拦截了。
排查过程
第一步:确认问题范围——是否只影响华南分公司
第一时间联系其他7个分支,发现华东分公司(同样通过IPSec VPN接入)也反馈ERP访问缓慢,但华北分公司(通过专线接入,不走VPN)完全没有问题。这个对比非常关键——专线正常、VPN异常,问题锁定在VPN隧道层面。
第二步:排除ERP服务器本身的问题
直接在总部内网访问ERP服务器,响应迅速,没有任何异常。检查ERP服务器的系统负载、数据库连接池、Web服务器线程池,全部在正常范围内。确认问题不在服务器端。
第三步:排除VPN隧道连通性问题
VPN隧道状态在FortiGate监控面板上显示为绿色”Up”,路由表正确,Ping小包100%通过。初步排除隧道本身断裂的可能。但注意到隧道接口的MTU配置——FortiGate上VPN隧道接口默认MTU值:
1 | # 总部 FortiGate (FGT-HQ) |
隧道MTU为1436,这是因为IPSec ESP封装会占用额外头部空间(ESP头8字节 + ESP尾2字节 + ICV 12字节 + 外层IP头20字节 = 42字节,1500-42=1458…但实际FortiGate的默认值是1436,预留了更多空间给可能的UDP封装和NAT-T)。隧道MTU本身看起来没有异常。
第四步:用路径MTU探测定位关键问题
使用Linux的tracepath命令进行路径MTU探测:
1 | # 从华南Linux服务器探测到总部ERP |
关键发现:数据包经过VPN隧道后,路径MTU从1500降到了1436。这意味着从ERP服务器发往华南客户端的TCP数据包如果满载1460字节,在经过VPN隧道时会被封装成超过1500字节的IP包,需要分片。
第五步:检查TCP MSS协商
查看FortiGate防火墙的TCP MSS配置:
1 | # 华南 FortiGate |
MSS值为0表示”未设置”——FortiGate不会在TCP SYN/SYN-ACK中主动修改MSS值。这意味着TCP连接建立时,ERP服务器(在1500 MTU的内网环境中)会在SYN-ACK中通告MSS=1460(MTU 1500 - 40字节IP+TCP头),华南客户端同理也在SYN中通告MSS=1460。
这就是问题所在:双方协商的MSS=1460,但数据实际要经过MTU=1436的VPN隧道。ERP服务器按MSS=1460发送满载TCP段,封装后IP包总长 = 1460 + 40(IP头) + 42(IPSec封装) = 1542字节,超过了出接口MTU 1500!这要么需要IP分片,要么被丢弃。
第六步:验证——为什么Ping小包通但大包不通
ICMP Echo Request小包(100字节)加上IPSec封装后仍远小于1500,无需分片,顺利通过。但1500字节的Ping包(IP层已达1500)加上IPSec封装后变成1542字节,超过了物理接口MTU,需要分片。
第七步:为什么没有ICMP Fragmentation Needed反馈
正常情况下,当路由器发现出接口MTU不足以转发不分片的包,且包的DF(Don’t Fragment)位为1时,应该返回ICMP Type 3 Code 4(Fragmentation Needed and DF Set)消息,通知发送方降低包大小。但我们在抓包中完全没看到这类ICMP消息!
进一步排查发现两个原因:
FortiGate的IPS策略拦截了ICMP Fragmentation Needed消息——查看IPS Sensor配置,发现有一条默认规则
ICMP.DOS.FRGNeeded将其标记为”Drop”,这是误将路径MTU发现的必要ICMP消息当作DOS攻击了。ERP服务器的Linux内核默认在TCP包中设置DF位——Linux的
net.ipv4.tcp_df默认行为倾向于设置DF位以避免分片带来的性能损失。当DF=1的TCP包在VPN封装后超MTU且被FortiGate丢弃时,本应返回ICMP Fragmentation Needed,但被IPS拦截了。
双重打击:服务器设置了DF位禁止分片 → FortiGate需要返回ICMP通知 → IPS策略把ICMP通知拦截了 → 服务器永远不知道自己发的包太大 → 只能不断重传同样大小的包 → 连接卡死。
第八步:确认根因链路
完整的因果链路:
- IPSec隧道封装增加42字节开销 → 隀道MTU从1500降到1436
- TCP MSS协商未适配隧道MTU → 双方协商MSS=1460(应为1396)
- ERP服务器按MSS=1460发送满载TCP段 → IPSec封装后超物理接口MTU
- TCP包DF位=1 → 不能分片,由FortiGate丢弃
- FortiGate IPS策略拦截ICMP Fragmentation Needed → 发送方收不到PMTU通知
- 服务器不断重传超大的TCP段 → 连接永远无法完成数据传输
- 小包(<1436-40=1396字节)不受影响 → Ping小包正常、ERP小响应体能加载
解决方案
紧急止血(5分钟内生效)
方案一:在FortiGate隧道接口上启用TCP MSS协商
这是最标准的修复方式,在VPN隧道接口上设置MSS Sender和MSS Receiver值,让FortiGate在TCP SYN/SYN-ACK经过隧道时自动修改MSS值,将其限制在隧道MTU允许的范围内:
1 | # 华南 FortiGate |
MSS值计算:隧道MTU 1436 - 40(IP头+TCP头) = 1396。设置后,FortiGate会在TCP SYN经过隧道接口时将MSS Option从1460修改为1396,确保双方协商出的最大段大小不超过隧道承载能力。
修改后立即生效,无需重启隧道,已有连接会在下次TCP握手时使用新MSS值。
方案二:释放IPS对ICMP Fragmentation Needed的拦截
同时修复IPS策略,放行必要的PMTU Discovery ICMP消息:
1 | # 总部 FortiGate IPS Sensor配置 |
将ICMP.DOS.FRGNeeded规则从Drop改为Pass,确保路径MTU发现的ICMP消息能正常传递。这是TCP PMTU Discovery机制正常工作的必要条件。
验证修复效果
修改后从华南分公司测试:
1 | # Ping大包测试 |
全量修复——所有分支机构VPN隧道
紧急修复华南后,立即对所有8个分支的VPN隧道接口统一配置MSS:
1 | # 批量配置脚本(总部侧) |
并在每台分支FortiGate上同样配置对应的隧道接口。
根因分析
问题的根本原因是 IPSec VPN隧道的MTU开销未被TCP层感知和适配,叠加 IPS策略误杀PMTU Discovery的ICMP消息,形成了”黑盒”效应:
核心根因一:TCP MSS未适配隧道MTU
IPSec ESP封装会在原始IP包外增加约42字节的头部开销,导致物理链路上可承载的有效载荷从1500字节降到约1458字节(考虑NAT-T UDP封装则更低)。但TCP层在握手时协商的MSS是基于直连网段MTU 1500计算得出的1460,完全不知道中间有一个MTU更低的隧道。FortiGate的隧道接口默认未开启MSS协商修改功能(tcp-mss-sender/receiver=0),导致这个信息鸿沟无法自动弥合。
核心根因二:IPS拦截ICMP Fragmentation Needed阻断PMTU发现
RFC 1191定义的Path MTU Discovery机制本应作为MSS协商的后备方案——当TCP包因DF位无法分片而被中间路由器丢弃时,路由器应返回ICMP Type 3 Code 4消息通知发送方降低包大小。但FortiGate的IPS默认规则将此类ICMP消息标记为潜在DOS攻击并丢弃,使得PMTU Discovery机制完全失效。
两个根因叠加:TCP不知道隧道MTU限制(MSS过大),又无法通过ICMP得知自己发的包太大(PMTU Discovery被阻断),导致发送方陷入”盲重传”的死循环。
预防措施
1. 标准化VPN隧道TCP MSS配置
制定VPN隧道配置标准,所有新建隧道接口必须设置MSS值:
1 | # FortiGate VPN隧道MSS配置标准 |
将此标准纳入VPN隧道配置Checklist,新建或修改隧道时强制执行。
2. IPS策略优化——放行PMTU Discovery ICMP
在所有FortiGate的IPS Sensor中,将以下ICMP规则从默认的Drop改为Pass或至少Disable:
ICMP.DOS.FRGNeeded(Type 3 Code 4 - Fragmentation Needed)ICMP.Info.SourceQuench(Type 4 - Source Quench,虽已废弃但部分设备仍使用)
这些ICMP消息是IP网络正常运行的必要组成部分,误杀它们会导致PMTU Discovery失效,远比所谓的DOS风险危害更大。
3. 定期路径MTU探测巡检
编写自动化巡检脚本,每周对所有VPN隧道执行路径MTU探测:
1 |
|
当路径MTU低于阈值时自动告警,防止MSS配置被意外修改或隧道MTU因封装方式变化而降低。
4. 网络变更流程纳入MTU审查
在FortiGate配置变更审批流程中加入MTU/MSS审查环节:
- 新增VPN隧道 → 必须检查MSS配置
- 更改IPSec封装方式(如从ESP改为ESP+UDP封装)→ 重新计算MSS值
- 修改IPS策略 → 检查是否影响PMTU Discovery ICMP放行
- 更换ISP或链路类型 → 测试路径MTU是否变化
5. 文档化MTU计算方法
编写内部网络运维手册中的MTU计算章节,记录所有VPN隧道的:
| 隧道名称 | 封装方式 | 预留开销 | 隧道MTU | MSS值 | 物理接口MTU |
|---|---|---|---|---|---|
| vpn-HN | ESP+NAT-T | 64字节 | 1436 | 1396 | 1500 |
| vpn-HD | ESP | 42字节 | 1458 | 1418 | 1500 |
让每位运维工程师都能快速查阅和计算正确的MSS值。
总结
这次故障的排查给我留下了深刻的教训:
教训一:Ping通不代表网络通。 Ping只是ICMP小包测试,它能证明链路可达,但完全不能证明大包传输能力。在涉及VPN隧道的故障排查中,必须用不同大小的Ping包(ping -l 1500)或tracepath进行路径MTU探测,才能发现隐藏的MTU问题。
教训二:MTU/MSS不匹配是VPN环境的”经典杀手”。 IPSec封装带来的MTU缩减是必然的、可计算的,但TCP层对此毫不知情。如果不主动在隧道接口配置MSS协商修改,就等于让TCP盲目发送超大数据包,轻则分片降速,重则完全卡死。这是VPN运维的基本功课,不能等出事了才补。
教训三:IPS策略不能一刀切地拦截ICMP。 安全策略的”拦截一切ICMP”看似防御了ICMP-based DOS攻击,实则破坏了PMTU Discovery这一网络基础机制。安全与可用必须平衡,对于RFC定义的必要ICMP消息(Fragmentation Needed、Time Exceeded等),必须放行。
教训四:故障排查要善于利用”小包通、大包不通”这个特征。 当你发现小请求能成功但大数据传输失败,或者Ping通但应用不通,第一反应就应该是检查MTU/MSS。这个特征太过典型,不必浪费时间去查服务器性能、应用日志、路由表——直接从MTU入手往往能最快定位。
最终,一个1436的隧道MTU和未设置的MSS参数,加上一条过于激进的IPS规则,三者合力制造了一个”Ping正常但业务瘫痪”的经典假象。修复只需两行配置,但理解它需要整个IP/TCP栈的知识。运维不只是改配置,更是理解协议。