ESP 分片丢包
ESP 分片丢包
网络拓扑

网络配置
基础网络配置(略)。
FW1 的 IPSec 配置,静态模式的 IPSec IKEv2 连接,中间没有 NAT 穿越。
重要
默认配置下 IPSec 的分片方式为 post-encapsulation(后封装),IPSec 隧道在收到任何需要 ESP 封装的报文时,不考虑 IPSec Tunnel 接口的 MTU,先将明文封装到 ESP 中(如果明文已经是分片包,则会先重组明文分片),直接发送至公网接口。从公网接口发出时,再根据公网接口的 MTU(默认为 1500 Bytes)对 ESP 报文进行分片。这种 post-encapsulation(后封装)方式符合 RFC 标准。
config vpn ipsec phase1-interface edit "VPN_to_FW2" set interface "port2" set ike-version 2 set peertype any set net-device disable set proposal aes128-sha256 aes256-sha256 aes128gcm-prfsha256 aes256gcm-prfsha384 chacha20poly1305-prfsha256 set ip-fragmentation post-encapsulation //默认配置// set dpd on-idle set remote-gw 202.103.13.2 set psksecret ENC A/oFySlyF2xkGInFX2pcoKpiQAgFb7xyVO4YaJ01IKQzYWPNOClbem+A/JpdLQUUaw+936tj6UC4LjW3Zivxgzl/9N3QZk8WlEBJUO/HrdWrUL= next end config vpn ipsec phase2-interface edit "VPN_to_FW2" set phase1name "VPN_to_FW2" set proposal aes128-sha1 aes256-sha1 aes128-sha256 aes256-sha256 aes128gcm aes256gcm chacha20poly1305 set auto-negotiate enable next endFW2 的 IPSec 配置。
config vpn ipsec phase1-interface edit "VPN_to_FW1" set interface "port2" set ike-version 2 set peertype any set net-device disable set proposal aes128-sha256 aes256-sha256 aes128gcm-prfsha256 aes256gcm-prfsha384 chacha20poly1305-prfsha256 set ip-fragmentation post-encapsulation //默认配置// set dpd on-idle set remote-gw 202.103.12.2 set psksecret ENC mBPuOPm3FhObt60JaaO6ac0T0YsD/8sYru2PKnbY5V1y/3ljX+fA22/WCKbqTAHzhd45dz9yTlZ/qQ/ajGemn+UuMcHnrdeVPEwTrbDi6oP0om= next end config vpn ipsec phase2-interface edit "VPN_to_FW1" set phase1name "VPN_to_FW1" set proposal aes128-sha1 aes256-sha1 aes128-sha256 aes256-sha256 aes128gcm aes256gcm chacha20poly1305 set auto-negotiate enable next end查看 IPSec 二阶段状态,可以看到 options 中有
frag-rfc标记,表示分片方式为 RFC 标准(post-encapsulation,后封装)。FW1 # diagnose vpn tunnel list | grep options bound_if=4 lgwy=static/1 tun=intf mode=auto/1 encap=none/552 options[0228]=npu frag-rfc run_state=0 role=primary accept_traffic=1 overlay_id=0 SA: ref=3 options=38203 type=00 soft=0 mtu=1230 expire=42024/0B replaywin=2048
问题现象
PC1 与 HTTP Server 可以互相 Ping 通,通过 SMB 传输文件也没有问题。
当 PC1 和 HTTP Server 使用较大的 data-size(当前 IPSec 算法 + 非 NAT-T 环境下,超过 1410 Bytes)互 Ping 时,无法 Ping 通。

较大的 UDP 报文(产生了分片)无法传输。
问题原因
当较大的 ICMP、UDP 等报文无法通过 IPSec 隧道传输时,就要考虑运营商是否有丢弃 ESP 分片报文的动作了。
以 ICMP 包为例,PC1 Ping HTTP Server,data-size=1600,无法 Ping 通。
C:\Users\Administrator.SUMMERICE2019>ipconfig Windows IP 配置 以太网适配器 Ethernet0: 连接特定的 DNS 后缀 . . . . . . . : 本地链接 IPv6 地址. . . . . . . . : fe80::3620:679d:7cf5:cc36%10 IPv4 地址 . . . . . . . . . . . . : 10.10.1.100 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 10.10.1.1 C:\Users\Administrator.SUMMERICE2019>ping 10.10.2.100 -t -l 1600 -n 1 正在 Ping 10.10.2.100 具有 1600 字节的数据: 请求超时。 10.10.2.100 的 Ping 统计信息: 数据包: 已发送 = 1,已接收 = 0,丢失 = 1 (100% 丢失)在 FW1 上抓取目标地址为 10.10.2.100 的数据包,PC1 发出的 Ping request 报文被分为两片(第 1 片去掉 IP 头的长度为 1480 Bytes,第 2 片去掉 IP 头的长度为 128 Bytes),进入 IPSec 接口后,被重组为一个包(加上 IP 头部的总长度为 1628 Bytes)。
重要
FortiGate 默认的 IP 分片方式为后封装,即先重组分片报文,再封装 ESP,然后从业务接口发出时根据接口 MTU 对 ESP 报文进行分片。
FW1 # diagnose sniffer packet any 'host 10.10.2.100' 4 0 l Using Original Sniffing Mode interfaces=[any] filters=[host 10.10.2.100] 2024-04-12 14:08:00.664087 port3 in 10.10.1.100 -> 10.10.2.100: icmp: echo request (frag 36352:1480@0+) 2024-04-12 14:08:00.664107 port3 in 10.10.1.100 -> 10.10.2.100: ip-proto-1 (frag 36352:128@1480) 2024-04-12 14:08:00.664179 VPN_to_FW2 out 10.10.1.100 -> 10.10.2.100: icmp: echo request同时在 FW1 的公网接口(port2)上抓取向 FW2 发送的 ESP 报文(如果是 NAT-T 环境,则需要抓取 UDP 4500 报文)并解密(抓取 ESP 及解密步骤请参考 VPN 技术 → IPSec VPN → IPSec VPN 排错 → IPSec 解密章节),可以看到步骤 3 中进入 IPSec 隧道并重组的 ICMP 报文被 ESP 封装,封装 ESP 后的总大小为 1688 Bytes(包含 IP 头部),从 port2 发送时,由于 port2 的 MTU 为 1500 Bytes(默认配置),需要分成 2 片后发出,这样就产生了 ESP 分片。其他的业务没有产生 ESP 分片。

在 FW2 的公网接口(port2)抓取 ESP 报文(如果是 NAT-T 环境,则需要抓取 UDP 4500 报文)并解密(解密步骤请参考 VPN 技术 → IPSec VPN → IPSec VPN 排错 → IPSec 解密章节),没有收到步骤 4 中抓到的两片 ESP 报文(封装 ICMP Request 报文),只能收到没有分片的 ESP 报文(如下所示的被 ESP 封装且未分片的 FTP 业务正常转发和使用,但没有抓到步骤 4 中的两片封装 ICMP 的 ESP)。

基本可以确定 FW1 发出的 ESP 分片在 Internet 上被丢弃,与 FortiGate 无关。
解决方法
- 联系运营商检查线路上的 ESP 分片丢包。
- 开启 IPSec 的预封装功能(pre-encapsulation),避免在 Internet 线路上出现 ESP 分片报文,本文着重介绍这种方法。
IPSec 预封装
在 FW1 和 FW2 的 IPSec 一阶段中将 IP 分片模式修改为预封装模式(pre-encapsulation),修改该配置后,隧道会发生重建。
重要
将 IPSec 的分片方式修改为 pre-encapsulation(预封装)后,IPSec 隧道在收到任何需要 ESP 封装的报文时,会根据当前 IPSec Tunnel 接口的 MTU,先将明文根据 Tunnel 接口的 MTU 进行分片(如果明文已经是分片包,则会先重组明文分片),对每一片明文分片封装 ESP,然后从公网接口发出。
在 IPSec 绑定的公网接口 MTU 为 1500(默认)的情况下,IPSec Tunnel 接口的 MTU 被设置为 1420 Bytes,确保封装 ESP 后的报文从 IPSec 绑定的公网接口发出时,不会超过 1500 Bytes,从而避免了在公网线路上产生 ESP 分片(如果是 AES256/SHA512 算法 + NAT-T 环境的组合,默认的 1420 是不行的,请参考这里)。
如果修改了公网接口的 MTU 值,IPSec Tunnel 接口上的 MTU 值不会自动跟随修改。需要在 Tunnel 接口上手动跟随修改 MTU 值:
- 例如使用 AES128/SHA1 算法组合,物理接口的 MTU 为 1500,IPSec Tunnel 接口的 MTU 为 1420。
- 客户因为网络需要将物理接口的 MTU 修改为 1400,那么需要同步地将 IPSec Tunnel 接口的 MTU 修改为 1320。
本文仅介绍物理口没有更改 MTU 的情况。
FW1: config vpn ipsec phase1-interface edit "VPN_to_FW2" set ip-fragmentation pre-encapsulation next end FW2: config vpn ipsec phase1-interface edit "VPN_to_FW1" set ip-fragmentation pre-encapsulation next end重要
在“拨号用户(dynamic)”类型的 IPSec 一阶段配置中设置
pre-encapsulation时:- 预封装功能不会立刻生效,必须重启设备或删除重配才能生效(在 FortiOS 7.X 上均存在此问题)。
- 在新配置的 IPSec 一阶段中直接配置
set ip-fragmentation pre-encapsulation可以直接生效。
“静态 IP 地址(static)”模式的 IPSec 隧道不存在此问题。
查看 IPSec Tunnel 接口的 MTU 值,IPSec Tunnel 接口的 MTU 值为 1420。
FW1 # diagnose netlink interface list name VPN_to_FW2 | grep mtu if=VPN_to_FW2 family=00 type=768 index=17 mtu=1420 link=0 master=0 FW2 # diagnose netlink interface list name VPN_to_FW1 | grep mtu if=VPN_to_FW1 family=00 type=768 index=16 mtu=1420 link=0 master=0再次查看 IPSec 二阶段状态,可以看到 options 中的
frag-rfc标记消失,表示分片方式为预封装(pre-encapsulation)。FW1 # diagnose vpn tunnel list | grep options bound_if=4 lgwy=static/1 tun=intf mode=auto/1 encap=none/40 options[0028]=npu run_state=0 role=primary accept_traffic=1 overlay_id=0 SA: ref=3 options=38203 type=00 soft=0 mtu=1230 expire=42899/0B replaywin=2048使用 PC1 Ping HTTP Server,data-size=1600,这次可以 Ping 通了。
C:\Users\Administrator.SUMMERICE2019>ipconfig Windows IP 配置 以太网适配器 Ethernet0: 连接特定的 DNS 后缀 . . . . . . . : 本地链接 IPv6 地址. . . . . . . . : fe80::3620:679d:7cf5:cc36%10 IPv4 地址 . . . . . . . . . . . . : 10.10.1.100 子网掩码 . . . . . . . . . . . . : 255.255.255.0 默认网关. . . . . . . . . . . . . : 10.10.1.1 C:\Users\Administrator.SUMMERICE2019>ping 10.10.2.100 -t -l 1600 -n 1 正在 Ping 10.10.2.100 具有 1600 字节的数据: 来自 10.10.2.100 的回复: 字节=1600 时间=2ms TTL=126 10.10.2.100 的 Ping 统计信息: 数据包: 已发送 = 1,已接收 = 1,丢失 = 0 (0% 丢失), 往返行程的估计时间(以毫秒为单位): 最短 = 2ms,最长 = 2ms,平均 = 2ms在 FW1 上抓取目标地址为 10.10.2.100 的数据包,PC1 发出的 Ping request 报文被分为两片(第 1 片去掉 IP 头的长度为 1480 Bytes,第 2 片去掉 IP 头的长度为 128 Bytes),进入 IPSec 接口后,被重组为一个包(加上 IP 头部的总长度为 1628 Bytes),随后根据 IPSec Tunnel 接口的 MTU = 1420 Bytes 进行预分片,被分为两片(第 1 片去掉 IP 头的长度为 1400 Bytes,第 2 片去掉 IP 头的长度为 208 Bytes)。
FW1 # diagnose sniffer packet any 'host 10.10.2.100' 4 0 l Using Original Sniffing Mode interfaces=[any] filters=[host 10.10.2.100] //Request方向// 2024-04-12 16:12:38.796118 port3 in 10.10.1.100 -> 10.10.2.100: icmp: echo request (frag 36382:1480@0+) 2024-04-12 16:12:38.796139 port3 in 10.10.1.100 -> 10.10.2.100: ip-proto-1 (frag 36382:128@1480) 2024-04-12 16:12:38.796191 VPN_to_FW2 out 10.10.1.100 -> 10.10.2.100: icmp: echo request (frag 36382:1400@0+) 2024-04-12 16:12:38.796271 VPN_to_FW2 out 10.10.1.100 -> 10.10.2.100: ip-proto-1 (frag 36382:208@1400) //Reply方向// 2024-04-12 16:12:38.798245 VPN_to_FW2 in 10.10.2.100 -> 10.10.1.100: icmp: echo reply (frag 6525:1400@0+) 2024-04-12 16:12:38.798297 VPN_to_FW2 in 10.10.2.100 -> 10.10.1.100: ip-proto-1 (frag 6525:208@1400) 2024-04-12 16:12:38.798317 port3 out 10.10.2.100 -> 10.10.1.100: icmp: echo reply (frag 6525:1400@0+) 2024-04-12 16:12:38.798478 port3 out 10.10.2.100 -> 10.10.1.100: ip-proto-1 (frag 6525:208@1400)同时在 FW1 的公网接口(port2)上抓取向 FW2 发送的 ESP 报文(如果是 NAT-T 环境,则需要抓取 UDP 4500 报文)并解密(解密步骤请参考 VPN 技术 → IPSec VPN → IPSec VPN 排错 → IPSec 解密章节),可以看到步骤 5 中被预分片的 2 个 ICMP 分片报文分别被 ESP 封装,加密后的两个 ESP 包大小均未超过 1500 Bytes(包含 IP 头部),从 port2 发送时,由于 port2 的 MTU 为 1500 Bytes(默认配置),不需要进行分片,这样就不会产生 ESP 分片发送到 Internet。

返回的 ICMP Reply 也一样被 FW2 的 IPSec 进行预封装分片,没有在 Internet 线路上产生 ESP 分片。

这样就规避了 Internet 线路上丢弃 ESP 分片包导致的业务不通问题。
IPSec 预封装对 DF 置位 IP 包的影响
问题现象
修改拓扑如下,PC1 通过 FW1 与 FW2 之间的 IPSec 隧道,从 FW2 的 port3 访问 Internet,FW1 和 FW2 的 IPSec 均开启预封装(pre-encapsulation)功能。

PC 在日常访问 HTTP 应用时,TCP SYN 中的 TCP - MSS 一般都为 1460 Bytes。大部分 HTTP 服务器返回的 TCP 业务报文,IP 头部中的 DF(Don't fragment)选项都会置位,表示不允许传输路径上的设备对此报文进行分片。
如上述拓扑所示 PC1(10.10.1.100)通过 IPSec 隧道访问 HTTP 服务器(
www.zhihu.com),无法打开该网站。
在 FW2 的 port3 接口抓包,HTTP 服务器向客户端返回的 Server Hello 报文较大,去除二层头部帧后的大小为 1438 Bytes,这超过了此例中 IPSec Tunnel 默认的 MTU 1420 Bytes。

IPSec 开启了预封装(pre-encapsulation)功能,需要根据此例中 IPSec Tunnel 接口的 MTU 1420 Bytes 进行预分片,但由于该 Server Hello 报文的 IP 头部的 DF(Don't fragment)选项被置位,该报文无法被 IPSec 预分片,所以被 FortiGate 丢弃。FortiGate 也会返回一个
fragmentation needed and DF set (Type3, Code4)的 ICMP 消息给服务器端,携带了 FortiGate IPSec Tunnel 接口的 MTU 值 1420 Bytes。
同时在 FW2 的 IPSec Tunnel 接口上抓包,没有看到这个 Server Hello 报文,说明被 FortiGate 丢弃。

解决方法 1 - 修改 TCP - MSS
在 FW2 或 FW1 的 IPSec Tunnel 接口或 VPN 策略中配置调低 TCP 握手时协商的 TCP - MSS,修改 TCP - MSS 的大小根据 IPSec 接口的 MTU 决定,默认的 IPSec Tunnel 接口 MTU 为 1420 Bytes,那么 TCP - MSS 建议修改为 IPSec Tunnel 接口的 MTU 减去 20 Bytes 的 IP 头部和 20 Bytes 的 TCP 头部,不同二阶段加密/验证算法和 NAT-T 环境下的建议 IPSec Tunnel 接口 MTU 和 TCP - MSS 如下:
重要
一些特殊情况下 TCP 头部可能达到 60 Bytes,如果修改 TCP - MSS 为 1380 Bytes 后仍然出现由于 TCP 头部超过 20 Bytes 导致的以上问题,请修改 TCP - MSS 为 1340 Bytes。
以下 IPSec Tunnel 接口的 MTU 均建立在其绑定的业务接口的 MTU 为 1500 Bytes 的前提下,如果调整了业务接口的 MTU(如此例中的 port2),IPSec Tunnel 接口的 MTU 以及 TCP - MSS 都要做相应修改。
算法组合 非 NAT-T NAT-T 建议 Tunnel MTU 建议 TCP - MSS 建议 Tunnel MTU 建议 TCP - MSS 3DES-SHA1 1420(默认,无需修改) 1380 1420(默认,无需修改) 1380 AES256-SHA256/AES256-SHA384/AES192-SHA256/AES192-SHA384/AES128-SHA1/AES128-SHA256 1420(默认,无需修改) 1380 1420(默认,无需修改) 1380 AES256-SHA512 1420(默认,无需修改) 1380 1406(需手工修改) 1366 在 FW2 或 FW1 上修改 IPSec Tunnel 接口或 VPN 策略的 TCP - MSS(仅需要在一台设备上修改,这里以 FW2 为例)为 1380。
config system interface edit "VPN_to_FW1" set tcp-mss 1380 next end 修改后可以使用如下CLI命令查看Tunnel接口的TCP-MSS: FW2 # diagnose netlink dstmac list VPN_to_FW1 dev=VPN_to_FW1 mac=00:00:00:00:00:00 rx_tcp_mss=1380 tx_tcp_mss=1380 egress_overspill_threshold=0 egress_bytes=0 egress_over_bps=0 ingress_overspill_threshold=0 ingress_bytes=0 ingress_over_bps=0 sampler_rate=0或基于策略的修改(两种方法取其一):
config firewall policy edit 2 set name "VPN_to_FW1" set srcintf "port3" "VPN_to_FW1" set dstintf "VPN_to_FW1" "port3" set action accept set srcaddr "all" set dstaddr "all" set schedule "always" set service "ALL" set tcp-mss-sender 1380 //发送方向的TCP SYN报文修改TCP-MSS为1380// set tcp-mss-receiver 1380 //接收方向的TCP SYN报文修改TCP-MSS为1380// next end再次使用 PC1(10.10.1.100)通过 IPSec 隧道访问 HTTP 服务器(
www.zhihu.com),可以正常打开该网站。
在 FW2 的 port3 接口抓包,可以看到 PC1 访问 HTTP 服务器发出的 TCP SYN 报文中的 TCP - MSS 被 FortiGate 修改为了 1380 Bytes。HTTP 服务器返回的 TCP - MSS 为 14680 Bytes。


在 FW2 的 IPSec Tunnel 接口抓包,可以看到 HTTP 服务器返回的 SYN ACK 的 TCP - MSS 被修改为 1380 Bytes。

在 FW2 的 port3 接口抓包,HTTP 服务器向客户端返回的 Server Hello 这次去除二层头部帧后的大小为 1420 Bytes,没有超过此例中 IPSec Tunnel 默认的 MTU 1420 Bytes,所以即使 IP 头中仍然有 DF 置位,但不会被 IPSec 预分片。

同时在 PC1 的网卡上抓包,可以收到这个 Server Hello 报文,说明没有被 FortiGate 丢弃,问题解决。

Internet 传输路径上未产生任何 ESP 分片。
解决方法 2 - 关闭 honor-df
在 FW1 和 FW2 上的全局配置下关闭
honor-df(默认开启),关闭后,将忽略任何 IP 头部中的 DF 位,对超过接口 MTU 的报文执行分片操作。重要
该配置不仅影响预封装模式的 IPSec,也影响其他非 VPN 流量,请谨慎使用。
config system global set honor-df disable end再次使用 PC1(10.10.1.100)通过 IPSec 隧道访问 HTTP 服务器(
www.zhihu.com),可以正常打开该网站。
在 FW2 的 port3 接口抓包,HTTP 服务器向客户端返回的 Server Hello 报文较大,去除二层头部帧后的大小为 1438 Bytes,这超过了此例中 IPSec Tunnel 默认的 MTU 1420 Bytes,且 IP 头部中的 DF 置位。

同时在 FW2 的 IPSec Tunnel 接口抓包,可以看到 FortiGate 无视了这个 Server Hello 报文 IP 头部的 DF 标志位,执行了 IPSec 预封装功能,根据 IPSec Tunnel 接口的 MTU 1420 Bytes 将 Server Hello 报文分为 2 片,这两个分片 IP 包的 DF 选项均没有置位。
重要
在 Tunnel 接口上抓取的报文没有 2 层帧头部,所以两个分片的大小为 1420 Bytes 和 38 Bytes。

同时在 PC1 的网卡上抓包,可以收到这个 Server Hello 报文的两个分片,问题解决。

Internet 传输路径上未产生任何 ESP 分片。
总结
- 当遇到 Internet 线路上丢 ESP 分片的情况,可以使用 FortiGate IPSec 的预封装(pre-encapsulation)功能规避此问题:
- IPSec 后封装(post-encapsulation):默认配置。IPSec 隧道在收到任何需要 ESP 封装的报文时,不考虑 IPSec Tunnel 接口的 MTU,先将明文封装到 ESP 中(如果明文已经是分片包,则会先重组明文分片),直接发送至公网接口。从公网接口发出时,再根据公网接口的 MTU(默认为 1500 Bytes)对 ESP 报文进行分片。这种 post-encapsulation(后封装)方式符合 RFC 标准。
- IPSec 预封装(pre-encapsulation):IPSec 隧道在收到任何需要 ESP 封装的报文时,会根据当前 IPSec Tunnel 接口的 MTU,先将明文根据 Tunnel 接口的 MTU 进行分片(如果明文已经是分片包,则会先重组明文分片),对每一片明文分片封装 ESP,然后从公网接口发出。
- 当使用 IPSec 预封装(pre-encapsulation)模式时,DF 置位且超过 IPSec Tunnel MTU 的 IP 报文在 IPSec 隧道中传输会被丢弃,可以通过以下 2 种方法解决:
- 解决方法 1 - 修改 TCP - MSS(推荐)。根据当前 IPSec 接口绑定的业务接口的 MTU 值决定 IPSec Tunnel 接口的 MTU,根据 IPSec Tunnel 接口的 MTU 决定 TCP - MSS 的大小,适用于 TCP 报文。
- 解决方法 2 - 关闭 honor-df。忽略任何 IP 头部中的 DF 置位,影响全局。对于 DF 置位的 UDP 或其他非 TCP 报文可以使用此种方法。
- 更多关于 IPSec 分片的知识请参考:https://summerice.notion.site/376bdd5796914707b0560a8a0e429bde?v = 033f688f25f34b5cacc6df3f21a7560b & pvs = 4 。