Openswan 之 NAT穿越分析

IPsec与NAT之间的冲突缘由

NAT服务器对内网来的数据包,需要修改其源地址和源端口为服务器自身的地址和端口(或者其他NAT方式),然后才将其进行转发。这种修改破坏了IPsec数据的完整性,导致接收方验证失败;另外,对于ESP封装的数据包,端口信息已经被加密,NAT服务器无法获得,使得NAT转换无法进行下去。这就是IPsec和NAT之间的冲突。

最常见的解决这种冲突的办法,就是UDP封装,即在IPsec协议数据包外包裹一层UDP头,这样NAT修改的东西就仅仅局限于UDP头内部了,不会损伤IPsec数据。

openswan对NAT穿越的支持就是采用UDP封装:
数据发送过程,依据策略SP决定是否需要进行穿越处理;
数据接收过程,则是对内核打补丁,对udp处理过程挂钩HOOK点。
以下是简要分析。

NAT-T发包分析
IPsec的NAT穿越秘诀在于用UDP包裹ESP、AH协议包;所以openswan必须要在安全封装之前获取端口等必要的信息,不然当进行ESP封装之后,端口信息将不可得。

openswan在ipsec_xmit_encap_bundle_2中,在调用ipsec_xmit_encap_once之前获取这些信息并保存到ixs发送描述符中:
if ((ixs->ipsp->ips_natt_type) && (!ixs->natt_type)) {如果在策略中启用了NAT-T
                     ixs->natt_type = ixs->ipsp->ips_natt_type;
                     ixs->natt_sport = ixs->ipsp->ips_natt_sport;
                     ixs->natt_dport = ixs->ipsp->ips_natt_dport;
                     switch (ixs->natt_type) {
                            case ESPINUDP_WITH_NON_IKE:
                                   ixs->natt_head = sizeof(struct udphdr)+(2*sizeof(__u32));
                                   break;
                                   
                            case ESPINUDP_WITH_NON_ESP:
                                   ixs->natt_head = sizeof(struct udphdr);
                                   break;
                                   
                            default:
                              KLIPS_PRINT(debug_tunnel & DB_TN_CROUT
                                         , "klips_xmit: invalid nat-t type %d"
                                         , ixs->natt_type);
                              bundle_stat = IPSEC_XMIT_ESPUDP_BADTYPE;
                              goto cleanup;
                                         
                                   break;
                     }
                     ixs->tailroom += ixs->natt_head;
}

此时,用于封装UDP的必要信息已经获得,接下来进行IPsec安全流程处理(略);而最终UDP封装是在虚拟网卡xmit函数的末尾进行的:
int ipsec_tunnel_start_xmit(struct sk_buff *skb, struct net_device *dev){
       struct ipsec_xmit_state *ixs = NULL;   
       ixs = ipsec_xmit_state_new ();      ixs->dev = dev;      ixs->skb = skb;
       stat = ipsec_xmit_sanity_check_dev(ixs); //检查dev,并从中取值填充ixs
       stat = ipsec_xmit_sanity_check_skb(ixs); //检查skb,并从中取值填充ixs
       stat = ipsec_tunnel_strip_hard_header(ixs); //获取数据包的硬件头长度
      
       stat = ipsec_tunnel_SAlookup(ixs);  //查找相对应的SA,参考下文SADB章节
                                                          //其实就是填充 ixs->outgoing_said
       stat = ipsec_xmit_encap_bundle(ixs); //根据SA和策略等进行处理
       stat = ipsec_nat_encap(ixs); //处理一下NAT-T(将ESP数据包用UDP进行包裹)
stat = ipsec_tunnel_restore_hard_header(ixs); //恢复硬件头
stat = ipsec_tunnel_send(ixs); //替换发送物理设备,查找新路由,将封包发出
                                          //ip_route_output,ip_send
ipsec_xmit_cleanup(ixs);       //清理skb等占用的内存
ipsec_xmit_state_delete (ixs); //回归缓存池
}

ipsec_nat_encap内部先判断策略是否启用了NAT-T,没启用的话则什么也不做直接返回成功;如果启用了,则进行UDP封装。封装过程比较简单,就是从IP头往下,将ESP协议数据整体下移,留出UDP头部大小的空间,将UDP头信息填入,修改IP头部的协议字段为IPPROTO_UDP,并重新计算IP头部校验和等。
(为啥不将IP头上移,这样拷贝数据能少很多,who knows?)


Openswan中NAT-T补丁代码分析(收包分析)

在KLIPS初始化函数中,向UDP内核代码中挂入一个HOOK点klips26_rcv_encap,用于处理ESP协议:
int ipsec_klips_init(void){
#if defined(NET_26) && defined(CONFIG_IPSEC_NAT_TRAVERSAL)
       /* register our ESP-UDP handler */
       if(udp4_register_esp_rcvencap(klips26_rcv_encap, &klips_old_encap)!=0) {
          printk(KERN_ERR "KLIPS: can not register klips_rcv_encap function/n");
       }
#endif
}

以下是打在udp.c中的补丁:
static xfrm4_rcv_encap_t xfrm4_rcv_encap_func = NULL; //hook点
int udp4_register_esp_rcvencap(xfrm4_rcv_encap_t func, xfrm4_rcv_encap_t *oldfunc){
        if (oldfunc != NULL)
                *oldfunc = xfrm4_rcv_encap_func;
        xfrm4_rcv_encap_func = func;
        return 0;
}
int udp4_unregister_esp_rcvencap(xfrm4_rcv_encap_t func){
        if (xfrm4_rcv_encap_func != func)
                return -1;
        xfrm4_rcv_encap_func = NULL;
        return 0;
}
最终,HOOK点xfrm4_rcv_encap_func = klips26_rcv_encap。


而在UDP内核代码内部,udp_lib_setsockopt内部,将up->encap_rcv改写:
case UDP_ENCAP_ESPINUDP:
case UDP_ENCAP_ESPINUDP_NON_IKE:
#if defined(CONFIG_XFRM) || defined(CONFIG_IPSEC_NAT_TRAVERSAL)
if (xfrm4_rcv_encap_func)
up->encap_rcv = xfrm4_udp_encap_rcv_wrapper;
else
#endif
up->encap_rcv = xfrm4_udp_encap_rcv;

内核中UDP接收流程如下:
int udp_rcv(struct sk_buff *skb)
à __udp4_lib_rcv(skb, udp_hash, IPPROTO_UDP);
              à udp_queue_rcv_skb(sk, skb);
                     à (*up->encap_rcv)(sk, skb); //有可能是一个封装UDP,需要先解封装
                     à sock_queue_rcv_skb(sk,skb); //非封装UDP了,提交
                            à skb_queue_tail(&sk->sk_receive_queue, skb); //放到sock的接收队列
                            à sk->sk_data_ready(sk, skb_len);                    //向上通知数据准备好了
在udp_queue_rcv_skb中首先检查udp_sock-> encap_type,如果发现是一个封装UDP的话,就会调用up->encap_rcv进行解封装,这就会调用到xfrm4_udp_encap_rcv_wrapper。
xfrm4_udp_encap_rcv_wrapper跟内核原有的xfrm4_udp_encap_rcv基本一样,去掉UDP头部,只是在最后后者调用内核中的ESP引擎ret = xfrm4_rcv_encap(skb, IPPROTO_ESP, 0, encap_type),而前者调用openswan注册的HOOK点函数xfrm4_rcv_encap_func:
iph->protocol = IPPROTO_ESP; /* modify the protocol (it's ESP!) */
ret = (*xfrm4_rcv_encap_func)(skb, encap_type); /* process ESP */

这样,openswan中的ESP钩子klips26_rcv_encap被调用,该函数调用IPsec解包循环ipsec_rcv_decap(irs)。

你可能感兴趣的:(IPSec)