基於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);
/*move ESP hdr after UDP hdr */
memmove((void*)((char *)udp + ixs->natt_head),
(void*)(udp),
ntohs(ipp->tot_len)- ixs->iphlen - ixs->natt_head);//把IP數據報中IP頭後面的部分向後移動一個UDP首部長度。
/*clear UDP & Non-IKE Markers (if any) */
memset(udp,0, ixs->natt_head);//騰的空間初始化爲零。
/*fill UDP with usefull informations ;-) */
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
/*set protocol */
ipp->protocol= IPPROTO_UDP;//重新設置IP頭中的協議爲UDP。
/*fix IP checksum */
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: /* with Non-IKE (older version) */
caseESPINUDP_WITH_NON_ESP: /* 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) {
/* Eat the packet .. */
kfree_skb(skb);
return 0;
}
if (ret < 0) {
/* process the ESP packet */
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 /* CONFIG_XFRM_MODULE ||CONFIG_IPSEC_NAT_TRAVERSAL */
+
+
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 /* either CONFIG_XFRM orCONFIG_IPSEC_NAT_TRAVERSAL */
struct udp_sock *up = udp_sk(sk);
struct udphdr *uh;
struct iphdr *iph;
@@ -1018,10 +1044,27 @@
return 0;
}
int ret;
if (ret < 0) {
- /* process the ESP packet */
- 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
/* 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
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;
/*Don't unlink in the middle of a turnaround */
KLIPS_INC_USE;
memset(irs,0, sizeof(*irs));
/*XXX fudge it so that all nat-t stuff comes from ipsec0 */
/* eventually, the SA itself will determinewhich device
* itcomes from
*/
{
skb->dev = ipsec_get_device(0);
}
/*set up for decap loop */
irs->hard_header_len= skb->dev->hard_header_len;
skb= ipsec_rcv_unclone(skb, irs);
#if IP_FRAGMENT_LINEARIZE
/*In Linux 2.4.4, we may have to reassemble fragments. They are
not assembled automatically to save TCP fromhaving to copy
twice.
*/
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 /* IP_FRAGMENT_LINEARIZE */
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
KLIPS_DEC_USE;
return0;
rcvleave:
if(skb){
ipsec_kfree_skb(skb);
}
KLIPS_DEC_USE;
return0;
}