Chinaunix首页 | 论坛 | 博客
  • 博客访问: 917308
  • 博文数量: 194
  • 博客积分: 7991
  • 博客等级: 少将
  • 技术积分: 2067
  • 用 户 组: 普通用户
  • 注册时间: 2007-12-09 22:03
文章分类

全部博文(194)

文章存档

2010年(8)

2009年(71)

2008年(109)

2007年(6)

我的朋友

分类: LINUX

2008-08-26 09:09:46

Openswan NAT穿越分析

  

β-song @ 2008-8-25

  

 

NAT之间的冲突缘由

 

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

 

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

 

openswanNAT穿越的支持就是采用UDP封装:

数据发送过程,依据策略SP决定是否需要进行穿越处理;

数据接收过程,则是对内核打补丁,对udp处理过程挂钩HOOK点。

以下是简要分析。

 

发包分析

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

 

openswanipsec_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_outputip_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)

 

 

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

 

KLIPS初始化函数中,向UDP内核代码中挂入一个HOOKklips26_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;

}

最终,HOOKxfrm4_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)

 

欢迎转载,转载请注明出处,谢谢。

http://sxg.cublog.cn

http://blog.chinaunix.net/u/8754/showart_1145897.html

2008-08-25

宋贤广

 

阅读(3762) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

月是夜的明2015-11-12 18:26:47

谢谢分享,好文,openswan讲的很清晰