Cisco IOS IPsec 配置专题(3) – 使用 Crypto Map 时 Fragmentation 的问题

不同的 IPsec 配置方式会导致数据包的 overhead 不同,通过本文我们对 overhead 产生的原因进行梳理,对使用 crypto map 的情况下 MTU 的取值进行讨论。其他 IPsec 配置方式的 MTU 取值放在相对应的章节里。

Overhead 形成的原因

Overhead 形成的原因主要是因为我们对原始数据进行了加密和二次封装 (例如使用 GRE over IPsec),不同的协议和加密方式会造成不同大小的 overhead,具体的参数我们可以使用 Cisco 提供的一个工具 IPSec Overhead Calculator Tool 来查看 (https://cway.cisco.com/tools/ipsec-overhead-calc/)。

值得注意的是并不是原始数据越大产生的 overhead 就越大,例如一个原始大小为 500 bytes 的数据包使用 GRE 封装,IPsec 设置为 transport mode,ESP-DES 加密,MD5 做验证,会产生 60 bytes 的 overhead。

如果将原始数据包增大到 1000 bytes,使用同样的 IPsec 参数,只会产生 56 bytes 的 overhead。

这是我们使用的 ESP 协议的 padding 机制造成的,这里就不深究了。

Overhead 造成的影响

由于 overhead 导致了数据包的增大,增大后的数据包就可能超过了 MTU,这样就会出现 fragmentation。在之前的文章中我们已经讲过 fragmentation 是我们不希望见到的,即使迫不得已要进行 fragmentation 我们也是希望在数据加密前进行,而不是对已经加密的数据包做 fragmentation 因为这样 throughput 严重下降。

如何避免 Fragmentation

在使用 crypto map 的方式配置 IPsec 时我们没有使用 tunnel,所以我们不需要考虑 tunnel 带来的影响。下面我们对几种典型的情况进行分析。

原始数据包过大,DF bit 没有设定

  1. H1 发出一个 1500 bytes 的数据包 (20 bytes IP header + 1480 bytes payload)
  2. 数据经过路由器 IPsec 加密后变为 1552 bytes (假设在某种配置的情况下产生了 52 bytes 的 overhead)
  3. 1552 bytes 超过了路由器出站接口的 MTU,由于 DF bit 没有设定路由器将会对数据包进行 fragmentation,变为两个新的数据包大小分别为 1500 bytes 和 72 bytes(52 bytes + 20 bytes 新的 IP header)
  4. 远端路由器收到两个数据包,重组成原始数据包
  5. IPsec 解密,转发给 H2

这是最糟糕的一种情况,我们对加密后的数据包进行了 fragmentation,浪费了大量 CPU 资源。Cisco 使用了一种叫 Pre-Fragmentation for IPsec VPNs 的功能,该功能在使用非 tunnel 的 IPsec 配置时 默认开启,路由器会先对数据包进行 fragmentation 再进行 IPsec 加密。能否成功使用该功能有很多附加条件,具体可以参考 IPsec Data Plane Configuration Guide 里的 Pre-Fragmentation for IPsec VPNs 章节 (https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/sec_conn_dplane/configuration/15-mt/sec-ipsec-data-plane-15-mt-book/sec-pre-frag-vpns.html)。

原始数据包过大,DF bit 有设定

假定 IPsec 的配置采用

      • IPsec tunnel mode transform
      • ESP MD5 authentication
      • DES encryption

  1. H1 发出一个 1500 bytes 的数据包 (20 bytes IP header + 1480 bytes payload),数据在路由器上加密后变为 1552 bytes (在我们假定的 IPsec 配置情况下将产生 52 bytes 的 overhead)。因为 DF bit 设定,所以无法 fragment,路由器丢包。
  2. 路由器返回一个 ICMP Error 给 H1,并告诉 H1 只能发送 payload 大小为 1442 bytes 的数据包 (1500 – 58 = 1442)。为什么减去了 58 bytes 的 overhead 呢,H1 发送 1442 bytes 的数据包产生的 overhead 明明是 52 bytes。在使用 IPsec tunnel mode transform + MD5 + DES 的情况下,最大可以产生的 overhead 是 58 bytes。我们知道 overhead 的大小是随着 payload 大小变化的,所以为了避免一直发送 ICMP Error 给 H1,路由器干脆用最大的 overhead 进行计算。
  3. H1 减小数据包大小,发送一个 1442 bytes 的数据包,经过第一跳路由 IPsec 加密后数据包变为 1496 bytes (52 bytes 的 overhead)。该数据包被中间的路由器丢弃,因为中间的路由器接口 MTU 只有 1400 bytes。
  4. 中间的路由器返回一个 ICMP Error 给第一跳路由器,并通告其 MTU 只有 1400。
  5. 此时 H1 还不知情,仍然向往发送 1442 bytes 的数据包,由于第一跳路由器已经知道了中间路由器的 MTU 只有 1400 bytes,所以第一跳路由器会将 H1 的数据包丢弃。
  6. 一个 ICMP Error 由第一跳路由器返回给 H1,并通告最大 payload 只能为 1342 bytes (1400 – 58 = 1342)。同理这里使用了最大可能的 overhead 58 bytes 进行计算。
  7. H1 根据收到的 ICMP Error 减小 payload,将数据包大小减为 1342 bytes。

在这个案例中我们使用了 PMTUD,需要注意的问题就是在实际部署的时候防火墙往往会拒绝 ICMP Error 或者叫 ICMP Unreachable (type 3, code 4) 通过。一旦主机收不到路由器的 ICMP 反馈也就无法动态的调整 MTU 大小了。

理解了 PMTUD 如何让主机动态调整 MTU 以后我们可以结合ip mtu命令将靠近发送方的路由器的出站接口的 MTU 值设置得小一点,尽量在我们可控的范围里利用提前利用 PMTUD 减小数据包的大小从而避免数据包在传输的中间段被 fragment。

在利用ip mtu的同时我们也可以针对 TCP 的数据包使用ip tcp adjust-mss将 mss 值改小从而使双方最终协商的 MTU 减小。

ip mtuip tcp adjust-mss 看似功能重复,其实不然。后者是在 TCP Syn 阶段就开始改动 MTU,这样对 TCP 数据来说在协议的初始阶段就解决了 MTU 的问题,远比等到 TCP 连接建立以后再使用 ip mtu 结合 PMTUD 来的快。

ip mtuip tcp adjust-mss 的取值是多少没有固定的答案,在使用 GRE tunnel 的情况下 Cisco 有一个指导值是将 MTU 设定为 1400。我们这里讨论的是没有使用 tunnel 的情况,1400 也适用因为没有使用 tunnel 就意味着 overhead 更少。TCP MSS 的取值自然而然就是 1360 了 (1400 IP MTU – 20 IP Header – 20 TCP Header)

解决方案总结

  1. 让发送方尽量减小 MTU,可以通过ip mtuip tcp adjust-mss来实现
  2. 使用  PMTUD,尽量让防火墙允许 ICMP Unreachable 可以通过
  3. 如果一定要进行 fragmentation 则优先使用 Pre-Fragmentation
  4. 如果一定要对 IPsec 加密的数据包进行 Fragmentation 我们可以使用 crypto ipsec df-bit clear强行清除 DF bit

     

发表评论

电子邮件地址不会被公开。