背景
IPsec在两个通信实体之间建立安全的数据传输通道, 但它却与网络中广泛存在的 NAT 设备 (以及PAT) 有天生的不兼容性(incompatible)。
我们以一个 TCP 报文为例来看看在不同 IPsec 的不同模式 (Transport 和Tunnel)和协议 (AH 和ESP)下,这种不兼容是如何发生的。
先来看 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-Transport 和ESP-Tunnel模式。
下图展示了 ESP-Transport 下的 UDP-Encapsulate 过程。
UDP Header是有端口字段的,有了端口,NAT设备便可以进行端口转换。RFC3948中规定 UDP Header 中的 端口 要使用和 IKE 协商时相同的端口号,这个端口号在 RFC3947 中规定为4500.
在下面这样的拓扑中,NAT设备背后有两台内网主机,它们都与 Server 建立 IPsec 连接。
Host 1与 Host 2 发出的 IPsec 报文都附加了一个 UDP Header。NAT 网关替换该报文的 Source IP 和Source Port。
还有一个问题, 对于 ESP-Transport 模式, 内层 TCP 报文的 checksum 校验的问题如何解决呢?要知道,经过 NAT 设备之后,报文的 IP 地址发生了变化,这势必导致接收端校验失败。IPsec采用的方法是在 IKE 协商时,就将自己原始 IP 地址信息发给对端,这样 Server 在解密出 TCP 报文后,可以根据这个信息修正checksum
NAT-T 协商过程
IPsec的通信实体之间需要在 IKE 时完成协商才能使用上面UDP-Encapsulate,完成NAT-T。
在 IKE 的PHASE1
- 双方探测出双方都支持NAT-T
- 双方探测出了报文传输路径上存在 NAT 设备
在 IKE 的PHASE2
- 双方协商 NAT-T 的封装模式,UDP-Encapsulated-Tunnel还是UDP-Encapsulated-Transport
- (UDP-Encapsulated-Transport)模式向对方发送自己的原始 IP 地址, 让对方可以据此修正后续 TCP 报文的checksum
为了更好的说明我用虚拟机搭建了下面这个拓扑,用来展示 IPsec 的NAT-T协商过程
其中 Alice 和Carol上运行 Strongswan, 而Moon 作为 NAT 设备。配置 IPsec 为Transport模式,使用 IKEv1 进行协商
探测支持 NAT-T
IPsec的两端在 PHASE1 的消息 1 和消息 2 中会通过交换 vendor ID payload 来向对方通告自己支持NAT, 其内容正是字符串“rfc3947”
探测是否存在 NAT
在 IKE PHASE1 的消息 3 和消息 4,通信双方会交换自己的和自己眼中对方的IP 和Port的哈希值,如果中间存在 NAT 设备,则该值一定与该报文本身的 IP 和Port计算出的值不一致。
改变端口从 500 到 4500
IKE PHASE1的前 4 个消息都是使用 Sport=Dport=500 进行通信。但当探测到 NAT 设备存在时,作为 Initiator 的Alice就再消息 5 需要将端口切换到 Sport=Dport=4500, 作为Responder 的Carol在收到该消息后,如果解密成功,也会使用新的 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