Openswan 之 NAT穿越分析

來源:互聯網
上載者:User

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.