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)。