分类: LINUX
2008-08-26 09:09:46
Openswan 之 NAT穿越分析
β-song @ 2008-8-25
NAT服务器对内网来的数据包,需要修改其源地址和源端口为服务器自身的地址和端口(或者其他NAT方式),然后才将其进行转发。这种修改破坏了IPsec数据的完整性,导致接收方验证失败;另外,对于ESP封装的数据包,端口信息已经被加密,NAT服务器无法获得,使得NAT转换无法进行下去。这就是IPsec和NAT之间的冲突。
最常见的解决这种冲突的办法,就是UDP封装,即在IPsec协议数据包外包裹一层UDP头,这样NAT修改的东西就仅仅局限于UDP头内部了,不会损伤IPsec数据。
openswan对NAT穿越的支持就是采用UDP封装:
数据发送过程,依据策略SP决定是否需要进行穿越处理;
数据接收过程,则是对内核打补丁,对udp处理过程挂钩HOOK点。
以下是简要分析。
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?)
在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)。
欢迎转载,转载请注明出处,谢谢。
http://blog.chinaunix.net/u/8754/showart_1145897.html
2008-08-25