基于openswan klips的IPsec實作分析(十一)NAT穿越
轉載請注明出處:http://blog.csdn.net/rosetta
本節介紹openswan klips的NAT穿越,應用層IKE協商時的NAT在以後的文章再做介紹。
簡介
IPsec和NAT的沖突:
NAT伺服器對内網來的資料包,需要修改其源位址和源端口為伺服器自身的位址和端口,然後才将其進行轉發。這種修改破壞了IPsec資料的完整性,導緻接收方驗證失敗;另外,對于ESP封裝的資料包,端口資訊已經被加密,NAT伺服器無法獲得,使得NAT轉換無法進行下去。這就是IPsec和NAT之間的沖突。
最常見的解決這種沖突的辦法,就是UDP封裝,即在IPsec協定資料包外包裹一層UDP頭,這樣NAT修改的東西就僅僅局限于UDP頭内部了,不會損傷IPsec資料。
openswan對NAT穿越的支援就是采用UDP封裝:
資料發送過程,依據natt_type類型及UDP長度是否已經指派給natt_head來決定是否需要進行穿越處理;
資料接收過程,因為接收UDP包是由核心接收處理的,openswanklips的首要工作就是給核心打更新檔,在處理UDP包時加入自己的處理函數。
控制宏:
#define CONFIG_IPSEC_NAT_TRAVERSAL
結構體成員:
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
__u8 natt_type;
__u16 natt_sport;//源端口
__u16 natt_dport;//目的端口
int natt_len;
#endif
natt_type 類型:ESPINUDP_WITH_NON_IKE、ESPINUDP_WITH_NON_ESP。
發送過程:
在資料加密之前首先需要擷取端口等必要資訊,否則進行ESP加密後就無法擷取。需要加密的資料完成加密後,封裝UDP資訊的操作是在ipsec_tunnel_start_xmit()函數尾部的ipsec_tunnel_restore_hard_header()函數中進行的。
int ipsec_tunnel_start_xmit(structsk_buff *skb, struct net_device *dev)
{
……
stat = ipsec_xmit_encap_bundle(ixs);//加密IP資料包
……
stat=ipsec_tunnel_restore_hard_header(ixs);
if(stat!= IPSEC_XMIT_OK) {
}
stat= ipsec_tunnel_send(ixs);//發送資料
return0;
}
enum ipsec_xmit_value
ipsec_tunnel_restore_hard_header(structipsec_xmit_state*ixs)
{
……
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
if(ixs->natt_type && ixs->natt_head) {
structiphdr *ipp = ixs->skb->nh.iph;
structudphdr *udp;
KLIPS_PRINT(debug_tunnel& DB_TN_XMIT,
"klips_debug:ipsec_tunnel_start_xmit:"
"encapsuling packet into UDP(NAT-Traversal) (%d %d)\n",
ixs->natt_type, ixs->natt_head);
//以ESP隧道,ICMP資料,3DES_MD5為例。
ixs->iphlen= ipp->ihl << 2;//IP首部長度(不太任何選項的首部)。5*4=20Byte
ipp->tot_len= htons(ntohs(ipp->tot_len) +ixs->natt_head); //112B+8B=120B
if(skb_tailroom(ixs->skb)< ixs->natt_head) {
printk(KERN_WARNING"klips_error:ipsec_tunnel_start_xmit: "
"triedto skb_put %d, %d available. "
"Thisshould never happen, please report.\n",
ixs->natt_head,
skb_tailroom(ixs->skb));
ixs->stats->tx_errors++;
returnIPSEC_XMIT_ESPUDP;
}
skb_put(ixs->skb,ixs->natt_head);//在skb中騰出UDP首部大小空間。
udp= (struct udphdr *)((char *)ipp + ixs->iphlen);
memmove((void*)((char *)udp + ixs->natt_head),
(void*)(udp),
ntohs(ipp->tot_len)- ixs->iphlen - ixs->natt_head);//把IP資料報中IP頭後面的部分向後移動一個UDP首部長度。
memset(udp,0, ixs->natt_head);//騰的空間初始化為零。
udp->source= htons(ixs->natt_sport);//UDP源端口
udp->dest= htons(ixs->natt_dport);//UDP目的端口
udp->len= htons(ntohs(ipp->tot_len) - ixs->iphlen);//UDP資料報長度(UDP首部+後面的資料)=8B+92B=100B
ipp->protocol= IPPROTO_UDP;//重新設定IP頭中的協定為UDP。
ipp->check= 0;
ipp->check= ip_fast_csum((unsigned char *)ipp, ipp->ihl);//重新設定IP頭中的校驗和。
}
#endif
……
returnIPSEC_XMIT_OK;
}
以上内容中關鍵是ixs->natt_type和 ixs->natt_head何時為真?這兩個值是在
ipsec_xmit_encap_bundle()中确定的,
ipsec_xmit_encap_bundle()函數的部分代碼片段:
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
if((ixs->ipsp->ips_natt_type) &&(!ixs->natt_type)) {
ixs->natt_type= ixs->ipsp->ips_natt_type;//給natt_type指派。
ixs->natt_sport= ixs->ipsp->ips_natt_sport;
ixs->natt_dport= ixs->ipsp->ips_natt_dport;
switch(ixs->natt_type) {
caseESPINUDP_WITH_NON_IKE:
ixs->natt_head= sizeof(struct udphdr)+(2*sizeof(__u32));
break;
caseESPINUDP_WITH_NON_ESP:
ixs->natt_head= sizeof(struct udphdr);//給natt_head指派為upd頭長度。
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;
}
#endif
這時還剩餘最後一個問題,變量ixs->ipsp->ips_natt_type為何為真?這是structipsec_sa的一個成員,說明在sadb資料庫會存放此成員的值,而sadb資料庫中的資料可以由應用層依據組成複雜的sadb資料包發送過來增加的(如何封裝可看第6節應用層SADB操作)。
相關資料結構:
struct sadb_x_nat_t_type {
uint16_t sadb_x_nat_t_type_len;
uint16_t sadb_x_nat_t_type_exttype;
uint8_t sadb_x_nat_t_type_type;
uint8_t sadb_x_nat_t_type_reserved[3];
};
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
int
pfkey_x_nat_t_type_process(struct sadb_ext*pfkey_ext, struct pfkey_extracted_data* extr)
{
interror = 0;
structsadb_x_nat_t_type *pfkey_x_nat_t_type = (struct sadb_x_nat_t_type *)pfkey_ext;
if(!pfkey_x_nat_t_type){
printk("klips_debug:pfkey_x_nat_t_type_process:"
"null pointer passed in\n");
SENDERR(EINVAL);
}
KLIPS_PRINT(debug_pfkey,
"klips_debug:pfkey_x_nat_t_type_process: %d.\n",
pfkey_x_nat_t_type->sadb_x_nat_t_type_type);
if(!extr|| !extr->ips) {
KLIPS_PRINT(debug_pfkey,
"klips_debug:pfkey_nat_t_type_process:"
"extr or extr->ips is NULL,fatal\n");
SENDERR(EINVAL);
}
switch(pfkey_x_nat_t_type->sadb_x_nat_t_type_type){
caseESPINUDP_WITH_NON_IKE:
caseESPINUDP_WITH_NON_ESP:
extr->ips->ips_natt_type= pfkey_x_nat_t_type->sadb_x_nat_t_type_type;
break;
default:
KLIPS_PRINT(debug_pfkey,
"klips_debug:pfkey_x_nat_t_type_process: "
"unknown type %d.\n",
pfkey_x_nat_t_type->sadb_x_nat_t_type_type);
SENDERR(EINVAL);
break;
}
errlab:
returnerror;
}
接收過程:
接收資料需要給核心源碼打更新檔,因為UDP包的接收處理預設是在核心的upd_rcv()函數中進行的,這樣對ESPinUDP的包的控制權就在核心中,是以如果要支援NAT,必須給核心打更新檔,其目的是讓處理UDP包的控制權轉移給openswan klips。
這裡主要說明下更新檔程式修改的地方以及如何打更新檔以及為何這麼打。
更新檔檔案:nat-t/net/ipv4/udp.c.os2_6.patch,更新檔檔案其實是對原來檔案不同部分的标記,前面為加号的是新增代碼,減号為删除代碼。打更新檔的過程就是把這些不同點修改到原始檔案中,這個過程通過patch指令。如下執行個體。
把此檔案拷貝至Linux源碼根目錄,執行:patch-p1 < udp.c.os2_6.patch
如果不出錯的話,在net/ipv4/目錄下會有一個udp.c.orig用以儲存修改之前的原始檔案,而udp.c就是打好更新檔的源碼了。下面先看下核心對于UDP包的處理過程,再看下更新檔程式增加的内容,就可以明白更新檔程式為何要如此改。
核心UDP包函數調用流程:
udp_rcv()
->__udp4_lib_rcv()
->udp_queue_rcv_skb()
intudp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb)
{
……
ret = udp_encap_rcv(sk, skb);//此函數的功能判斷是否是ESPinUDP包,如果是的話去掉UDP部分資料,并設定ip首部的協定字段為ESP類型(IPPROTO_ESP)。
if (ret == 0) {
kfree_skb(skb);
return 0;
}
if (ret < 0) {
ret = xfrm4_rcv_encap(skb,up->encap_type); //原始的核心是調用此函數對UDP做進一步處理
UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);
return -ret;
}
……
}
更新檔程式增加的主要内容:
+#if defined(CONFIG_XFRM) ||defined(CONFIG_IPSEC_NAT_TRAVERSAL)
+
+static xfrm4_rcv_encap_txfrm4_rcv_encap_func = NULL;
+int udp4_register_esp_rcvencap(xfrm4_rcv_encap_tfunc
+ , 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_tfunc, xfrm4_rcv_encap_t old)
+{
+ if(xfrm4_rcv_encap_func != func)
+ return -1;
+
+ xfrm4_rcv_encap_func = old;
+ return 0;
+}
+#endif
+
+
static int udp_encap_rcv_skb(structsock * sk, struct sk_buff *skb)
{
-#ifndef CONFIG_XFRM
+#if !defined(CONFIG_XFRM) &&!defined(CONFIG_IPSEC_NAT_TRAVERSAL)
return 1;
-#else
+#else
struct udp_sock *up = udp_sk(sk);
struct udphdr *uh;
struct iphdr *iph;
@@ -1018,10 +1044,27 @@
return 0;
}
int ret;
if (ret < 0) {
-
- ret = xfrm4_rcv_encap(skb,up->encap_type); //去掉核心處理UDP包的函數
- UDP_INC_STATS_BH(UDP_MIB_INDATAGRAMS);
- return -ret;
+ if(xfrm4_rcv_encap_func != NULL)
+ ret =(*xfrm4_rcv_encap_func)(skb, up->encap_type);//調用klips實作的UDP包處理函數。
+
+ switch(ret) {
+ }
這裡主要增加了兩個函數udp4_register_esp_rcvencap()和udp4_unregister_esp_rcvencap(),注冊新的udp接收處理函數和解除函數。
openswan klips是在ipsec_klips_init()中調用的,如下所示,是以最終會把klips26_rcv_encap()函數賦給函數指針xfrm4_rcv_encap_func。
#if defined(NET_26) && defined(CONFIG_IPSEC_NAT_TRAVERSAL)//notgo here
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
klips26_rcv_encap()主要是確定skb是一份拷貝,再取出網絡層IP資料等并對irs->natt_type指派,最後調用ipsec_rcv_decap(irs);對ESP資料解密,其處理過程和普通的ESP包處理過程是一樣的。
int klips26_rcv_encap(structsk_buff *skb, __u16 encap_type)
{
structipsec_rcv_state nirs, *irs = &nirs;
structiphdr *ipp;
KLIPS_INC_USE;
memset(irs,0, sizeof(*irs));
{
skb->dev = ipsec_get_device(0);
}
irs->hard_header_len= skb->dev->hard_header_len;
skb= ipsec_rcv_unclone(skb, irs);
#if IP_FRAGMENT_LINEARIZE
if(skb_is_nonlinear(skb)) {
#ifdef HAVE_NEW_SKB_LINEARIZE
if(skb_linearize_cow(skb) != 0)
#else
if(skb_linearize(skb, GFP_ATOMIC) != 0)
#endif
{
gotorcvleave;
}
}
#endif
ipp= skb->nh.iph;
{
structin_addr ipsaddr;
structin_addr ipdaddr;
ipsaddr.s_addr= ipp->saddr;
addrtoa(ipsaddr,0, irs->ipsaddr_txt
,sizeof(irs->ipsaddr_txt));
ipdaddr.s_addr= ipp->daddr;
addrtoa(ipdaddr,0, irs->ipdaddr_txt
,sizeof(irs->ipdaddr_txt));
}
irs->iphlen= ipp->ihl << 2;
KLIPS_IP_PRINT(debug_rcv,ipp);
irs->stats=NULL;
irs->ipp = ipp;
irs->ipsp= NULL;
irs->ilen= 0;
irs->authlen=0;
irs->authfuncs=NULL;
irs->skb= skb;
#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
switch(encap_type){
caseUDP_ENCAP_ESPINUDP:
irs->natt_type =ESPINUDP_WITH_NON_ESP;
break;
caseUDP_ENCAP_ESPINUDP_NON_IKE:
irs->natt_type = ESPINUDP_WITH_NON_IKE;
break;
default:
if(printk_ratelimit()) {
printk(KERN_INFO "KLIPS receivedunknown UDP-ESP encap type %u\n",
encap_type);
}
return -1;
}
#endif
ipsec_rcv_decap(irs);
KLIPS_DEC_USE;
return0;
rcvleave:
if(skb){
ipsec_kfree_skb(skb);
}
KLIPS_DEC_USE;
return0;
}