ESP分片丢包

网络拓扑

image-20240412103437761

网络配置

  1. 基础网络配置(略)。

  2. 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
    end
    
  3. FW2的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
    
  4. 查看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
    

问题现象

  1. PC1与HTTP Server可以互相Ping通,通过SMB传输文件也没有问题。

  2. 当PC1和HTTP Server使用较大的data-size(当前IPSec算法 + 非NAT-T环境下,超过1410 Bytes)互Ping时,无法Ping通。

    image-20240412112637265

  3. 较大的UDP报文(产生了分片)无法传输。

问题原因

  1. 当较大的ICMP、UDP等报文无法通过IPSec隧道传输时,就要考虑运营商是否有丢弃ESP分片报文的动作了。

  2. 以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% 丢失)
    
  3. 在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
    
  4. 同时在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分配。

    image-20240412142353337

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

    image-20240412150406571

  6. 基本可以确定FW1发出的ESP分片在Internet上被丢弃,与FortiGate无关。

解决方法

  • 联系运营商检查线路上的ESP分片丢包。
  • 开启IPSec的预封装功能(pre-encapsulation),避免在Internet线路上出现ESP分片报文,本文着重介绍这种方法。

IPSec预封装

  1. 在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隧道不存在此问题。

  2. 查看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
    
  3. 再次查看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
    
  4. 使用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
    
  5. 在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)
    
  6. 同时在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。

    image-20240412163132681

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

    image-20240412163719324

  8. 这样就规避了Internet线路上丢弃ESP分片包导致的业务不通问题。

IPSec预封装对DF置位IP包的影响

问题现象

  1. 修改拓扑如下,PC1通过FW1与FW2之间的IPSec隧道,从FW2的port3访问Internet,FW1和FW2的IPSec均开启预封装(pre-encapsulation)功能。

    image-20240415151649505

  2. PC在日常访问HTTP应用时,TCP SYN中的TCP-MSS一般都为1460 Bytes。大部分HTTP服务器返回的TCP业务报文,IP头部中的DF(Don't fragment)选项都会置位,表示不允许传输路径上的设备对此报文进行分片。

  3. 如上述拓扑所示PC1(10.10.1.100)通过IPSec隧道访问HTTP服务器(www.zhihu.com),无法打开该网站。

    image-20240415154316984

  4. 在FW2的port3接口抓包,HTTP服务器向客户端返回的Server Hello报文较大,去除二层头部帧后的大小为1438 Bytes,这超过了此例中IPSec Tunnel默认的MTU 1420 Bytes。

    image-20240415093828287

  5. 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。

    image-20240415095633481

  6. 同时在FW2的IPSec Tunnel接口上抓包,没有看到这个Server Hello报文,说明被FortiGate丢弃。

    image-20240415152243127

解决方法1-修改TCP-MSS

  1. 在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
  2. 在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
    
  3. 再次使用PC1(10.10.1.100)通过IPSec隧道访问HTTP服务器(www.zhihu.com),可以正常打开该网站。

    image-20240415161722138

  4. 在FW2的port3接口抓包,可以看到PC1访问HTTP服务器发出的TCP SYN报文中的TCP-MSS被FortiGate修改为了1380 Bytes。HTTP服务器返回的TCP-MSS为 14680 Bytes。

    image-20240415162101404

    image-20240415163634530

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

    image-20240415163858400

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

    image-20240415162248084

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

    image-20240415162951010

  8. Internet传输路径上未产生任何ESP分片。

解决方法2-关闭honor-df

  1. 在FW1和FW2上的全局配置下关闭honor-df(默认开启),关闭后,将忽略任何IP头部中的DF位,对超过接口MTU的报文执行分片操作。

    该配置不仅影响预封装模式的IPSec,也影响其他非VPN流量,请谨慎使用。

    config system global
        set honor-df disable
    end
    
  2. 再次使用PC1(10.10.1.100)通过IPSec隧道访问HTTP服务器(www.zhihu.com),可以正常打开该网站。

    image-20240415161722138

  3. 在FW2的port3接口抓包,HTTP服务器向客户端返回的Server Hello报文较大,去除二层头部帧后的大小为1438 Bytes,这超过了此例中IPSec Tunnel默认的MTU 1420 Bytes,且IP头部中的DF置位。

    image-20240415165123004

  4. 同时在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。

    image-20240415165802988

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

    image-20240415170206517

  6. Internet传输路径上未产生任何ESP分片。

总结

  1. 当遇到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,然后从公网接口发出。
  2. 当使用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报文可以使用此种方法。
  3. 更多关于IPSec分片的知识请参考:https://summerice.notion.site/376bdd5796914707b0560a8a0e429bde?v=033f688f25f34b5cacc6df3f21a7560b&pvs=4

Copyright © 2024 Fortinet Inc. All rights reserved. Powered by Fortinet TAC Team.
📲扫描下方二维码分享此页面👇
该页面修订于: 2024-05-17 15:36:14

results matching ""

    No results matching ""