(九)洞悉linux下的Netfilter&iptables:網絡地址轉換原理之DNAT

網絡地址轉換:NAT服務器

     NetfitlerNAT在內核中維護了一張名爲nat的表,用來處理全部和地址映射相關的操做。諸如filternatmangle抑或raw這些在用戶空間所認爲的「表」的概念,在內核中有的是以模塊的形式存在,如filter;有的是以子系統方式存在的,如nat,但它們都具備「表」的性質。所以,內核在處理它們時有很大一部操做都是相同的,例如表的初始化數據、表的註冊、鉤子函數的註冊等等。關於NAT表的初始化模板數據和表的註冊流程並非本文的重點,你們能夠對照第四篇博文中filter表的相關分析來研究。本文仍是側重於從總體上對整個NAT子系統的設計思想和原理進行,固然,有時間我仍是會簡單和你們分析一NAT表的東西。由於最近確實太忙了,原本想着在四月份結束這個系列,無奈一轉眼就晃到了五月份,作IT的娃,都不容易啊!網絡

     經過前面的幾篇文章咱們已經知道,NAT的設計是獨立於鏈接跟蹤系統的,即鏈接跟蹤是NAT的基礎框架,咱們還了解到鏈接跟蹤不會修改數據包,它只是負責維護數據包和其所屬的業務會話或數據鏈接狀態的相關信息而已。鏈接跟蹤最終是被iptables模塊所使用的,它所定義的那些狀態信息如NEWESTABLISHEDRELEATED等等,NAT通通不用關心。框架

     根據前面的hook函數掛載圖咱們能夠清晰的知道,對於那些須要被本機轉發的數據包,註冊在NF_IP_PRE_ROUTING點的ip_nat_in ()函數完成對其目的地址轉換的操做,註冊在NF_IP_POST_ROUTING點的ip_nat_out()函數完成源地址轉換任務。若是在編譯Linux內核源碼時打開了CONFIG_IP_NF_NAT_LOCAL選項,則註冊在NF_IP_LOCAL_OUTNF_IP_LOCAL_IN點的hook函數就會工做,最多見的用法的是用NF_IP_LOCAL_OUT點的ip_nat_local_fn()函數來改變本機發出報文的目的地址。至於註冊在NF_IP_LOCAL_IN點的ip_nat_fn()函數通常不會起做用,只是當數據包到達該HOOK點後會例行被調用一下。由於,NAT的全部規則只可能被配置到nat表的PREROUTINGPOSTROUTINGOUTPUT三個點上,通常也不多有人去修改那些路由給本機的報文的源地址。函數

         NAT 的分類以下圖所示:

    相信你們在看iptables用戶指南都見過這麼一句解釋:this

    只有每條鏈接的第一個數據包纔會通過nat表,而屬於該鏈接的後續數據包會按照第一個數據包則會按照第一個報所執行的動做進行處理,再也不通過nat表。Netfilter爲何要作這個限制?有什麼好處?它又是如何實現的?咱們在接下來的分析中,將一一和你們探討這些問題。atom

    ip_nat_rule.c文件中定義了nat表的初始化數據模板nat_table,及相應的target實體:SNATDNAT,並將其掛在到全局xt[PF_INET].target鏈表中。關於NAT所註冊的幾個hook函數,其調用關係咱們在前幾篇博文中也見過:spa

所以,咱們的核心就集中在ip_nat_in()上。也就是說,當咱們弄明白了ip_nat_fn()函數,你就差很少已經掌握了nat的精髓。ip_nat_in()函數定義定在ip_nat_standalone.c文件裏。鏈接跟蹤做爲NAT的基礎,而創建在鏈接跟蹤基礎上的狀態防火牆一樣服務於NAT系統。設計

    關於 ip_nat_fn() 函數 咱們仍是先梳理總體流程,以便你們對其有一個宏觀總體的把握,而後咱們再來分析其實現細節。這裏須要你們對鏈接跟蹤的狀態躍遷有必定了瞭解。

    從流程圖能夠看出,牽扯到的幾個關鍵函數都土黃色標註出來了。ip_nat_setup_info()函數主要是完成對數據包的鏈接跟蹤記錄ip_conntrack對象中相應成員的修改和替換,而manip_pkt()中才是真正對skb裏面源/目的地址,端口以及數據包的校驗和字段修改的地方。代理


目的地址轉換:DNATorm

         DNAT 主要適用於將內部私有地址的服務發佈到公網的情形。情形以下:

    服務器上架設了Web服務,其私有地址是B,代理防火牆服務器有一個公網地址A。想在須要經過A來訪問B上的Web服務,此時就須要DNAT出馬才行。根據前面的流程圖,咱們立刻殺入內核。

client經過Internet訪問公網地址A時,經過配置在防火牆上的DNAT將其映射到了對於私網內服務器B的訪問。接下來咱們就分析一下當在clientserver的交互過程當中架設在防火牆上NAT是如何工做。

仍是看一下hook函數在內核中的掛載分佈圖。

 

    在 PREROUTING 點當一個 skb 被鏈接跟蹤事後,那麼 skb->ctinfo skb->nfct 兩個字段均被設置了值。在接下來的分析中,對那些梢枝末節的代碼咱們都將不予理睬。盜個圖:

這裏牽扯到一個變量ip_conntrack_untracked,以前咱們見過,可是還沒討論過。該變量定義在ip_conntrack_core.c文件裏,並在ip_conntrack_init()函數進行部分初始化:

atomic_set(&ip_conntrack_untracked.ct_general.use, 1);

set_bit(IPS_CONFIRMED_BIT, &ip_conntrack_untracked.status);

同時,在ip_nat_core.c文件裏的ip_nat_init()函數中又有以下設置:

ip_conntrack_untracked.status |= IPS_NAT_DONE_MASK;

該變量又是何意呢?咱們知道iptables維護的實際上是四張表,有一張raw不是很經常使用。該表以-300的優先級在PREROUTINGLOCAL_OUT點註冊了ipt_hook函數,其優先級要高於鏈接跟蹤。當每一個數據包到達raw表時skb->nfct字段缺省都被設置成了ip_conntrack_untracked,因此當該skb還沒被鏈接蹤的話,其skb->nfct就一直是ip_conntrack_untracked。對於沒有被鏈接跟蹤處理過的skb是不能進行NAT的,所以遇到這種狀況代碼中直接返回ACCEPT

從上面的流程圖能夠看出,不管是alloc_null_binding_confirmed()alloc_null_binding()仍是ip_nat_rule_find()函數其本質上最終都調用了ip_nat_setup_info()函數。

 

ip_nat_setup_info()函數:

該函數中主要完成了對鏈接跟蹤記錄ip_conntrack.status字段的設置,同時根據可能配置在nat表中的DNAT規則對鏈接跟蹤記錄裏的響應tuple進行修改,最後將該ip_conntrack實例掛載到全局雙向鏈表bysource裏。

    在鏈接跟蹤系統里根據 skb 的源 / 目的 IP 分別已經構建生成初始 tuple 和響應 tuple ,咱們經過一個簡單的示意圖來回顧一下其流程,並加深對 ip_nat_setup_info() 函數執行過程的理解。

在圖1中,根據skb的源、目的IP生成了其鏈接跟蹤記錄的初始和響應tuple

在圖2中,以初始tuple爲輸入數據,根據DNAT規則來修改將要被改變的地址。這裏是當咱們訪問目的地址是A的公網地址時,DNAT規則將其改爲對私網地址B的訪問。而後,計算被DNAT以後的數據包新的響應。最後用新的響應tuple替換ip_conntrack實例中舊的響應tuple,由於數據包的目的地址已經被改變了,因此其響應tuple也必須跟着變。

在圖3中,會根據初始tuple計算一個hash值出來,而後以ip_conntrack結構中的nat.info字段會被組織成一個雙向鏈表,將其插入到全局鏈表bysource裏。

最後,將ip_conntrack.status字段的IPS_DST_NATIPS_DST_NAT_DONE_BIT位均置爲1

這裏必須明確一點:在ip_nat_setup_info()函數中僅僅是對ip_conntrack結構實例中相關字段進行了設置,並無修改原始數據包skb裏的源、目的IP或任何端口。

 

ip_nat_packet()函數的核心是調用manip_pkt()函數:

manip_pkt()裏主要完成對數據包skb結構中源/目的IP和源/目的端口的修改,而且修改了IP字段的校驗和。從mainip_pkt()函數中返回就回到了ip_nat_in()函數中(節選)

ret = ip_nat_fn(hooknum, pskb, in, out, okfn);

if (ret != NF_DROP && ret != NF_STOLEN&& daddr != (*pskb)->nh.iph->daddr) {

         dst_release((*pskb)->dst);

         (*pskb)->dst = NULL;

}

return ret;

    正常狀況下返回值ret通常都爲NF_ACCEPT,所以會執行if條件語句,清除skb原來的路由信息,而後在後面協議棧的ip_rcv_finish()函數中從新計算該數據包的路由。

在數據包即將離開NAT框架時,還有一個名爲ip_nat_adjust()的函數。參見hook函數的掛載示意圖。該函數主要是對那些執行了NAT的數據包的序列號進行適當的調整。若是調整出錯,則丟棄該skb;不然,將skb繼續向後傳遞,即將到達鏈接跟蹤的出口ip_confirm()。至於,ip_confirm()函數的功能說明咱們在鏈接跟蹤章節已經深刻討論了,想不起來的童鞋能夠回頭複習一下鏈接跟蹤的知識點。

 

前面咱們僅分析了從client發出的第一個請求報文到server服務器時,防火牆的處理工做。緊接着咱們順着前面的思路繼續分析,當server收到該數據包後迴應時防火牆的處理狀況。

server收到數據包時,該skb的源地址(記爲X)從未變化,但目的地址被防火牆從A改爲了Bserver在響應這個請求時,它發出的迴應報文目的地址是X,源地址是本身的私有地址B。你們注意到這個源、目的地址恰好匹配被DNAT以後的那個響應tuple

當該迴應報文到達防火牆後,首先是被鏈接跟蹤系統處理。顯而易見,在全局的鏈接跟蹤表ip_conntrack_hash[]中確定能夠找到這個tuple所屬的鏈接跟蹤記錄ip_conntrack實例。關於狀態的變遷參見博文八。

而後,該回應報文到達 NAT 框架的 ip_nat_in() 函數,流程和前面同樣,但處理方式確定不一樣。咱們仍是先看一下截止到目前爲止,這條鏈接跟蹤結構圖:

直接跳到ip_nat_packet()函數裏,當netlink通知機制將鏈接跟蹤狀態由NEW變爲REPLY後,此時dir=1,那麼根據初始tuple求出原來的響應tuple:源地址爲A,目的之爲X此時,server的響應報文,源地址爲私有網段B,目的地址爲X。路由尋址是以目的地址爲依據,防火牆上有直接到client的路由,因此響應報文是能夠被client正確收到的。可是,但但是,蛋炒西紅柿,對於UDP來講client收到這樣的回覆沒有任何問題的,可是對於TCPU而言確實不行的。這就引出咱們接下來將要討論的SNAT

    未完,待續
相關文章
相關標籤/搜索