乐趣区

IPsec与NAT-TraversalNATT

背景

IPsec在两个通信实体之间建立安全的数据传输通道, 但它却与网络中广泛存在的 NAT 设备 (以及PAT) 有天生的不兼容性(incompatible)。

我们以一个 TCP 报文为例来看看在不同 IPsec 的不同模式 (TransportTunnel)和协议 (AHESP)下,这种不兼容是如何发生的。

先来看 Transport 模式

AH 协议,由于其 Authenticate 范围是整个 IP 报文,所以如果两个 IPsec 之间存在 NAT 设备,修改了报文 IP Header 中的地址,就会导致接收方的 Authenticate 失败。
ESP 协议,其 Authenticate 返回不包括 IP Header, 所以接收方的Authenticate 会通过,但如果中间的 NAT 设备修改了 IP Header 中的地址,理论上后面的 TCP checksum 也会随之修改,但这部分在 ESP 协议中是
加密的,NAT设备没有办法修改,所以接收端在 TCP 接收时会出现 checksum 校验失败。

再来看 Tunnel 模式

AH 协议, Tunnel模式和 Transport 模式没什么不同,Authenticate范围包含了外层 IP Header,因此同样会造成接收方Authenticate 失败。
ESP 协议,与 Transport 模式不同的是,经过 NAT 设备。内层 IP Header 并不会改变,所以 TCP checksum 也不会变化,接收方不会出现 checksum 校验失败。

这样看起来,ESP-Tunnel似乎成为了在有 NAT 设备环境下,唯一可行的 协议 - 模式 组合。但即使是这种组合也是有缺点的:它只能支持 一对一 NAT(NAT设备后面只有一台内网主机)。在很多组网中,NAT设备通常作为网关使用,其背后可能有很多台主机。这时地址转换就不够了,它还需要端口转换,显然,NAT设备对 ESP-Tunnel 的报文是无能为力的,因为 TCP 部分已经被加密了,已经没有 端口 字段了。
所以,IPsec需要想办法能绕开 NAT 设备的影响,也就是进行 NAT 穿越(NAT-Tranversal)。

UDP-Encapsulate

IPsec采用的办法是在 ESP Header 前加上一个 UDP Header, 这个方法同时适用于ESP-TransportESP-Tunnel模式。
下图展示了 ESP-Transport 下的 UDP-Encapsulate 过程。

UDP Header是有端口字段的,有了端口,NAT设备便可以进行端口转换。RFC3948中规定 UDP Header 中的 端口 要使用和 IKE 协商时相同的端口号,这个端口号在 RFC3947 中规定为4500.

在下面这样的拓扑中,NAT设备背后有两台内网主机,它们都与 Server 建立 IPsec 连接。

Host 1Host 2 发出的 IPsec 报文都附加了一个 UDP HeaderNAT 网关替换该报文的 Source IPSource Port

还有一个问题, 对于 ESP-Transport 模式, 内层 TCP 报文的 checksum 校验的问题如何解决呢?要知道,经过 NAT 设备之后,报文的 IP 地址发生了变化,这势必导致接收端校验失败。IPsec采用的方法是在 IKE 协商时,就将自己原始 IP 地址信息发给对端,这样 Server 在解密出 TCP 报文后,可以根据这个信息修正checksum

NAT-T 协商过程

IPsec的通信实体之间需要在 IKE 时完成协商才能使用上面UDP-Encapsulate,完成NAT-T

IKEPHASE1

  • 双方探测出双方都支持NAT-T
  • 双方探测出了报文传输路径上存在 NAT 设备

IKEPHASE2

  • 双方协商 NAT-T 的封装模式,UDP-Encapsulated-Tunnel还是UDP-Encapsulated-Transport
  • (UDP-Encapsulated-Transport)模式向对方发送自己的原始 IP 地址, 让对方可以据此修正后续 TCP 报文的checksum

为了更好的说明我用虚拟机搭建了下面这个拓扑,用来展示 IPsecNAT-T协商过程

其中 AliceCarol上运行 Strongswan, 而Moon 作为 NAT 设备。配置 IPsecTransport模式,使用 IKEv1 进行协商

探测支持 NAT-T

IPsec的两端在 PHASE1 的消息 1 和消息 2 中会通过交换 vendor ID payload 来向对方通告自己支持NAT, 其内容正是字符串“rfc3947”

探测是否存在 NAT

IKE PHASE1 的消息 3 和消息 4,通信双方会交换自己的和自己眼中对方的IPPort的哈希值,如果中间存在 NAT 设备,则该值一定与该报文本身的 IPPort计算出的值不一致。

改变端口从 500 到 4500

IKE PHASE1的前 4 个消息都是使用 Sport=Dport=500 进行通信。但当探测到 NAT 设备存在时,作为 InitiatorAlice就再消息 5 需要将端口切换到 Sport=Dport=4500, 作为ResponderCarol在收到该消息后,如果解密成功,也会使用新的 4500 端口

在此之后,后续的 IKE PHASE2 和业务流量都会使用 4500 端口进行 UDP-Encapsulate。为了与业务流量进行区分,IKE 阶段的流量紧随 UDP Header 后的是一个 32bit 全为 0 的Non-ESP Marker (业务流量的这个地方是填写的是非零的SPI)

内核相关实现

内核使用 xfrm 框架完成 IPsec 报文收发功能。普通情况下, IP根据协议字段分流 IPsec 报文和 TCP UDP 报文。

NAT-T 场景中,IPsec为报文进行了 UDP-Encapsulate,那么,接收端看到的就是一个UDP 报文了,会调用 udp_rcv() 进行报文接收。那么此时又如何进入 xfrm 框架呢?

答案是:Strongswan通过设置 UDP 套接字 UDP_ENCAP 选项,内核为套接字绑定一个回调函数xfrm4_udp_encap_rcv()

int udp_lib_setsockopt(struct sock *sk, int level, int optname,
               char __user *optval, unsigned int optlen,
               int (*push_pending_frames)(struct sock *))
{
    // code omitted
    switch (optname) {
        case UDP_ENCAP:
        switch (val) {
            case 0:
            case UDP_ENCAP_ESPINUDP:
            case UDP_ENCAP_ESPINUDP_NON_IKE:
                up->encap_rcv = xfrm4_udp_encap_rcv;
        // code omitted
    }
}

而在 udp_rcv() 接收过程中,最终会调用到该回调函数

REF

RFC 3947 Negotiation of NAT-Traversal in the IKE
RFC 3948 UDP Encapsulation of IPsec ESP Packets

退出移动版