1. 程式人生 > >徹底理解Cisco NAT內部的一些事

徹底理解Cisco NAT內部的一些事

                為了配一條NAT,發生了很多事。

一.Inside和Outside

很多在Cisco配置過NAT的人都有過一個疑問,那就是inside和outside的區別!以下是Cisco官方文件上關於NAT執行順序的說明:注意紅色和藍色圈住的部分,對於inside-outside而言,NAT發生在路由之後,而對於outside-inside而言,NAT發生在路由之前。這是目前為止,我們唯一需要記住的。

1.問題

迷惑的原因不在別的,就在inside,outside這個名字不好,實際上如果將inside-outside換成POST-ROUTING,將outside-inside換成PRE-ROUTING的話,就非常好理解了,最重要的是,換了名字之後,NAT看起來不再和裝置的inside/outside網口域相關,而和“路由”發生了關係,雖然本質上沒有任何變化。        後面會介紹,實際上,在理解Cisco的NAT的時候,根本不能將inside和outside單獨拿出來理解,inside和outside僅僅是一個位置限定詞,代表“某地”,而具體的是“到某地去”還是“從某地來”,還需要一個副詞,這就是source和destination。在詳述這個之前,姑且先將inside和outside單獨拿出來使用。        接下來我來說明一下NAT和路由的關係是多麼重要!考慮以下的資料流,我以“路由”這個動作為中心:正向包:-->NAT point1-->路由-->NAT point2-->返回包:<--NAT point1<--路由<--NAT point2<--

我們看一下在NAT point1和NAT point2上要做些什麼動作才合理。首先我們先考慮轉換正向包的源IP地址發生在NAT point2,那麼對於返回包,目標地址轉換就發生在NAT point2,返回包轉換完目標地址後,發生路由查詢,資料包正常返回,沒有任何問題。現在考慮正向包的源IP地址轉換髮生在NAT point1,那麼按照將NAT鉤子操作安裝在資料流同一位置的原則,返回包的目標地址轉換隻能發生在NAT point1,此時已經經過了路由查詢,路由查詢是基於目標地址轉換前的目標地址來的,也就是說這個路由結果並非真正的路由結果,真正要想將返回資料包送到目的地,必須基於轉換後目標地址來查詢路由表才可以,然而即便這個針對轉換前目標的路由查詢結果實際上是個假的結果,你也要必須把它對映成一個真的結果(這就是ip nat outside source中add-route引數要做的事情,下面的例子詳述)。以下給出一個例項:

為了使得返回包能到達1.1.1.2,必須將到達2.2.2.4的路由對映到E0那一側才行,因此必須增加一條:ip route 2.2.2.4 255.255.255.255 1.1.1.2也就是說將到達目標地址轉換前的下一跳設定成目標地址轉換後的下一跳。        這就是NAT和路由的聯動關係。Cisco中如果你用ip nat outside source設定NAT的時候,就會碰到上面的這個問題,你可以手工新增一條反向包的地址轉換前路由對映,也可以通過命令列後面加add-route引數解決,add-route引數會自動為你新增那條路由,實際上它做的就是:a.根據NAT對映表查詢目的地址B轉換後的目的地址A;b.查詢目的地址A查詢到達A的下一跳C;c.將到達B的下一條設定到C。
        我們看到在outside上,即PRE-ROUTING上做源地址轉換確實會有問題,但是可以通過add-route引數進行路由重新對映來解決之。正常合理的情況就是在inside上,即POST-ROUTING上做源地址轉換。事實上,我們雖然在做源地址轉換,但是問題總是出在反向資料包的目標地址轉換上,合理的情況就是要將目標地址轉換放在路由之前進行,反推正向資料,那就是將源地址轉換放在路由之後進行,如果不瞭解Cisco Domain概念的話,一定會認為:不要在outside上作源地址轉換!        目前為止,我來總結一下。Cisco完全割裂了路由和NAT的關係,不像Linux那樣DNAT永遠在路由之前進行,不管是配置的DNAT,還是SNAT的反向DNAT,都在路由前,這樣就可以針對轉換後的目標地址做路由查詢,如果這麼理解是對的,你會驚訝,Cisco竟然可以在PREROUTING(outside)做SNAT(ip nat outside source),導致反向包在POSTROUTING做DNAT...但是事情不是這麼簡單!        Cisco不會由於犯錯而設計出如此不合理又容易把人搞暈的NAT架構的,它這麼設計必然有自己的理由,是什麼理由呢?下面2,3兩小節給出了一些提示。

2.關於policy routing

我們知道,標準的IP路由是基於目標地址的,但是為了增加更多的策略,policy routing可以用源地址來影響路由查詢結果。在這種意義上,源地址轉換在路由之前就是必要的,然而這樣就會導致反向的目標地址地址轉換髮生在路由之後!到底是:a.為了policy routing將SNAT置於路由之前b.為了不必add-route(雖然它確實不是什麼問題,而且是自動的),將DNAT置於路由之前需要一個權衡!然而Cisco不像Linux那樣去對稱設計那5個HOOK點,Cisco的方式就劃分Domain的,即inside和outside

3.關於Domain和NAT domain

Cisco裝置一般連線兩種網路環境,一種是自己內部的,另一種是外部公共的,這就將介面分為了兩個域,一個是內部域,即inside,另外一種為外部域,即outside!這種分法的名字叫得特別好,以至於它被用在了很多的領域,比如nat,然而一旦用在了nat方面,就讓人糊塗了。因此我提議,對Cisco架構不是很理解的,請嘗試用POST/PRE ROUTING來理解inside和outside。但是本小節想做的就是闡明使用inside和outside是合理的。        對於NAT來講,轉換的是IP地址,而IP地址可以分為Global地址以及Local地址,前者是公網可路由的地址,後者是私有地址。從inside域到outside域,需要將所有的Local地址轉換為Global地址,一個首要原則就是,Global的地址在未經允許是不能出現在inside域和DMZ域(路由器可能沒有)的!決策點就是路由!因此outside到inside方向的地址轉換必然要在路由之前完成。這就要求inside到outside方向的地址轉換必然要在路由之後完成。即:ip nat inside source必然發生在路由之後,而:ip nat outside source 必然發生在路由之前。如果你定義了某個介面比如FE0/0為outside,那麼需要在介面上使能ip nat outside,這樣的話,從該介面進入的包就會在路由之前去查詢NAT表,如果找到對應的表項,就會執行NAT。同理如果定義某介面比如FE0/1為inside,那麼需要在介面上使能ip nat inside,這樣的話,從該介面進入的包先執行路由查詢,然後去查詢NAT表,如果找到對應的表項,則執行NAT操作。

3.1.轉換方向以及轉換點-Cisco NAT的設計

這個小節涉及到了對Domain的使用以及如何解讀ip nat inside|outside source|destination命令。本小節總結了Cisco NAT設計的終極理論。為了簡單,我不再引入Cisco定義的那四種地址以及它們和源/目標IP地址,方向的關聯,這些概念都是額外的概念,最容易使人跑偏而最終陷進去。        Cisco沒有像Linux那樣使用“pre/post路由”這麼技術化的術語來定義NAT的行為,而是完全根據Domain來定義,所謂的Domain,即路由器兩邊一邊屬於inside,另一邊屬於outside。那麼所有的NAT無外乎就以下4種類型:1>從inside到outside時轉換源地址2>從inside到outside時轉換目標地址3>從outside到inside時轉換源地址4>從outside到inside時轉換目標地址其中1和4互相隱含,2和3互相隱含。到此為止,我們發現Cisco的NAT並沒有像Linux那麼簡單,Linux實際上就定義了兩種NAT,即:i> SNAT,源地址轉換ii>DNAT,目標地址轉換然後其它的約束都是設計的時候內建的:路由前執行DNAT,路由後執行SNAT,包含隱含規則。這就是Cisco和Linux的NAT設計的終極區別!它們側重點不同,Cisco強調使用者的使用域,Linux強調技術本身的合理性(如何配置就需要發揮想象力了)。我們先看一下Linux的NAT設計基準是什麼。Linux的NAT是全域性生效的,沒有“將NAT應用於介面”的說法,因此介面就成了一個match。因此管理員只需要寫match/target就可以了。

        對於Cisco而言,為了將4種NAT配置介面全部匯出給工程師,需要一個前提操作,那就是定義inside介面和outside介面,即在哪個介面上應用inside nat,在哪個介面上應用outside nat。到此,所有的4種NAT都必須能和任意型別(inside/outside)的介面單獨組合。這就打破了平衡點,變成了馬鞍面,你無法找到一個點,在PRE ROUTING和POST ROUTING中完成一切,舉例,如果介面E1使能inside nat,E0使能outside nat,說明E1是inside介面,E0是outside介面,那麼我們考慮從inside到outside方向的兩種轉換,一種是轉換源地址,另一種是轉換目標地址,我們把它們放在一個位置還是放在一個“虛擬的平衡點”(不一定是routing)兩邊,即兩個位置呢?我們看下面的兩個圖,實際上代表了兩種約束:

這兩個圖展開後是個典型的馬鞍面,原點就是路由,之所以要有個原點,是因為基於Domain的配置中,資料包從inside到outside或者反過來必然要經過一個點,從本文最上面的那幅Cisco網站上的圖,我們看到基於Domain的NAT行為不僅僅是一個NAT,它需要和ACL匹配,加解密等操作聯動,這些操作所依賴的IP地址和NAT發生了關聯,因此基於Domain的NAT行為一定要位於路由行為的兩邊。由於Cisco是按照Domain即inside/outside來進行配置的(在介面上應用特定Domain的規則),因此必然是這種設計方式,而對於Linux,NAT是全域性的,介面只是一個match而已,因此就完全按照路由的約束來設計。        現在,我們可以來總結一下ip nat inside|outside source|destination的含義了。我把這個命令公式化:ip nat P H其中H代表要做源轉換還是要做目標轉換,H還有一個更加隱蔽的含義,那就是它和P指名了資料的方向,也就是說:資料是以P為H的。舉例,ip nat inside destination表示資料是以inside為目標的(來自outside),做目標地址轉換;ip nat outside source表示資料是以outside為源發往inside的,做源地址轉換。

4.Cisco的destination轉換

但是,但是如何對目標地址進行轉換,即將訪問一個公共Global地址時,將其轉到一個內部的Local地址,這就是目的地址轉換,也叫地址對映,Cisco如何來做呢?實際上,很多Cisco裝置的ISO版本不允許你像Linux那樣無限制做DNAT,而僅僅允許對映特定的IP地址+TCP/UDP埠對或者全IP。這肯定是在outside上做目標地址轉換了,在相反的方向就是在inside上做源地址轉換,即:ip nat inside source static tcp $local_ip $local_port $global_ip $global_port注意,必須是static的NAT,這涉及到下一節要說的“如何安裝NAT”        對於TCP負載均衡做的ip nat inside destination這種定製化的NAT則不在本文討論範圍內。

二.如何安裝NAT

0.風格

Linux的NAT是基於5元組的,也就是NAT結果和一個流(conntrack)關聯在一起,這種關聯導致同屬於一個五元組的一個流的所有資料包的NAT策略必須一致,對於這一硬性規定有點太強硬了,因此我在Linux上做了好幾個補丁來彌補Linux的不足,當然也可以用RAWNAT。        對於Cisco,NAT不和一個流關聯,除非是Stateful的。既然不和流關聯,那麼如何做呢?Cisco會在特定的時間將“一條NAT對映策略”安裝到系統的inside NAT表或者outside NAT表中,對於從網口進入的資料包,會根據網口是inside還是outside去匹配inside NAT表或者outside NAT表中的NAT規則,僅此而已。        不管是inside NAT表還是outside NAT表,都各有兩張,一張是SNAT表,另一張是DNAT表,NAT表的拍腦袋想出的資料結構可以是:
NAT table {type:SNAT or DNATdirection:inside or outsidenodes:local/global mapping}
對於每一個數據包,都要用源IP地址去查詢SNAT表,用目標IP地址去查詢DNAT表。然而對於Linux而言,需要的僅僅是查詢conntrack結構,然後取出第一個包查詢時記錄於此的nat結果。

1.靜態NAT

靜態NAT就是一個一對一的NAT對映,也就是一個Local IP地址和一個Global IP地址之間的轉換。在配置生效的時候,NAT轉換規則就會被新增到NAT表中。

1.1.Cisco方式

當在inside方向上添加了一條NAT:ip nat inside source static a b系統會將a->b的源地址轉換加入到inside的SNAT表中,同時將b->a的目標地址轉換加入到outside的DNAT表中。        針對後面的所有資料包,不管是從內部發起的,還是從外部發起的,都會根據介面使能的是inside nat還是outside nat來查表匹配。

1.2.Linux方式

Linux基於conntrack,因此即使你使用iptables -t nat -A POSTROUTING -s a -j SNAT --to-source b也只針對匹配該策略的第一個資料包,Linux的NAT的轉換一方a是作為一個match出現的,因此它嚴格匹配第一個包的源地址,故反方向的資料包不會匹配,因此Linux的NAT都是單向的。

2.動態NAT

動態NAT不在配置的時候規定轉換後的地址,而在第一個有轉換需求(由ACL來判斷)的資料包到來的時候才確定它要轉換成什麼地址。因此,配置生效的時候,沒有任何NAT規則會被加入到NAT表中。

2.1.Cisco方式

當在inside方向添加了一條動態NAT:ip nat pool NAME ...ip nat inside source list $acl pool NAME ...系統不會新增任何NAT規則,只有當某一個包匹配到了acl,要引發NAT的時候,系統會動態(基於pool型別來計算)從pool中選一個要轉換成的IP地址,將其新增入inside的SNAT表中,同時針對反方向的目標地址轉換規則生成並加入outside的DNAT表中。        因此,Cisco動態的NAT是單向的,因此反向的資料包進入時不會匹配到acl,不會引發NAT規則的生成,也就不會匹配到任何NAT規則。

2.2.Linux方式

再次重申,Linux的nat中,待轉換的IP地址是一個match,因此不管是一對一的轉換還是一對多的轉換,原理都是一樣的。Linux並不區分靜態轉換和動態轉換。在核心中,永遠都不會出現所謂的NAT對映表,iptables新增的NAT規則不會生成對映,資料包進入匹配nat成功,也不會生成對映,nat結果僅僅存在於conntrack中作為tuple的一部分體現。

3.查詢方式

3.1.Linux方式

Linux的nat查詢對於第一個包是逐條匹配iptables nat表規則,對於後續的包,則轉化為針對五元組的conntrack雜湊查詢。

3.2.Cisco方式

對於Cisco而言,不像Linux那樣PREROUTING處僅僅發生DNAT,POSTROUTING處僅僅發生SNAT,而是不管在PREROUTING還是在POSTROUTING,均可能發生SNAT和DNAT,因此不管在哪個nat HOOK point,均要用資料包的源IP地址和目標IP地址分別來查詢SNAT表和DNAT表。既然這麼對稱,那麼就可以將這個查詢過程抽象出來,使之成為一個獨立的虛擬介面,是為NVI!

三.使用NVI虛擬介面

0.虛擬介面理念

這個就不說了。OpenVPN的tap,Cisco IPSec VPN的VTI,包括下文描述的NVI,都屬於虛擬介面,並且所有的虛擬介面都可以通過路由的方式將資料包匯入進去,至於在裡面執行什麼操作,這就是虛擬介面“虛擬”的地方,可以將資料包匯出到字元裝置,也可以加解密,當然也可以做NAT...

1.Linux 2.4的nat route

Linux 2.4的nat route依賴於policy routing,超級失敗,還沒有我自己寫的好,《又一個Linux的雙向stateless NAThttp://blog.csdn.net/dog250/article/details/8996666》詳細介紹了實現方法。

2.Cisco的domainless NAT

Domainless就是說不再區分inside和outside,只是單純地做NAT,這個在Cisco中實現得熱別清爽,沒有用所謂的平衡點,進而兩個方向NAT的處理HOOK點也不再基於平衡點對稱,所有的NAT操作全部在PREROUTING上做,然而Cisco並沒有用增加配置,掛接HOOK的方式實現,而是用一個叫做NAT  Virtual Interface的虛擬介面來實現,這樣有什麼好處呢?說實話,從介面上看不出來,但是從其實現角度,就可以通過路由的方式將帶有ip nat enable配置的介面進來的包全部匯入這個虛擬介面NVI0中。然後用資料包的源地址和目標地址分別查詢SNAT表和DNAT表,根據結果進行NAT操作,隨後進入真正的路由查詢,整體流程如下圖所示:

可見,不管方向,不管路由,只要資料包進入了一塊帶有ip nat enable配置的物理網絡卡,就會進行NAT匹配以及匹配成功後的操作,不管是SNAT和DNAT都在這裡進行。這個實現雖然很豪放,但是卻解決了所有問題,平衡點的問題不存在了,資料包在進入真正的路由查詢前,NAT就已經完成了,在路由器看來,NAT操作被藏起來了,就好像資料包本來就是那個樣子一樣。        當然Domainless的NAT也不再和任何其它操作關聯,ACL,VPN感興趣流匹配,policy routing等都和NAT無關。Cisco Domainless的NAT設計和Linux的NAT裝置區別更加明顯,雖然Linux的NAT也是Domainless的,但是在設計上卻和Cisco基於Domain的NAT很像,因為二者的NAT都要和其它的Filter操作聯動,對於Linux,NAT行為需要和大量的Netfilter行為聯動。

四.總結和想法

通過使用Cisco的NAT,我有一些自己的想法,需要暫時記錄下來。

1.和微軟的關係,和自由的關係

我配置Cisco的時候,有種使用微軟系統的感覺。遇到瓶頸的時候,束手無策!在微軟的系統上,我想做單臂NAT,我想配置路由的時候帶上source,折騰了好久都未果,配置Cisco的時候,同樣的感覺!雖然有時候,高一點的IOS版本確實能解決問題(這是和微軟一樣的)。總的來講,使用這些東西讓你感覺不自由,能HACK的地方實在太少,HACK操作門檻太高,使用它們時,你要一直提心吊膽,你要麼一氣呵成,只要是碰到了攔路虎,哪怕多麼小的攔路虎,你可能都過不去。        Linux正好是事情的另一面,UNIX處在中間。在Linux上操作,你的任何行為都不會受到阻礙,即使碰到了問題,系統不會阻礙你去編寫一個程式或者核心模組,甚至重新訂製核心。即使不這麼深入,光看一下iproute2以及iptables就夠了,本身自帶的引數就能讓你完成幾乎所有的操作,在這個意義上,習慣了Linux的人可能也會苛求Cisco的擴充套件訪問控制列表更加優秀一些,實際上,面對Netfilter的幾乎無限的擴充套件能力,Cisco的擴充套件訪問控制列表的“擴充套件”體現在哪裡呢??        我買手機不是來hack的,我也不寫程式碼安裝在手機上,我僅僅是想用它來打電話,發信息,上網,所以我不買Android,由於價值觀相差甚遠,我不買和微軟有任何關聯的手機,不管它有多好,買了iOS是因為它和UNIX有關聯,事實上,蘋果很封閉,但是它的封閉不是技術本身,而是價值取向和設計,事實上,也正是因為它封閉的不是技術,才有了越獄之說,越獄之後,你將面對一個原汁原味的開放的UNIX。開放是金,開放就是一切。

2.功能性技術點和解決方案

如果碰到問題,當然要想辦法去解決問題了,此時就有兩條路,第一條路就是尋找單獨解決該問題的功能性技術點,比如你想在做一個基於ACL的地址對映,如果你用的是Linux BOX,那麼這個功能性技術點就是iptables,但是如果你用的是Cisco低端路由器,那麼就無法做這件事,文件明確說不支援。此時就是第二條路了,那就是徹底否定最初的方案,說一句:“為何要這麼做呢?XX會更好的。我們有整套的解決方案,滿足你的所有需求。”。一般的像Cisco,微軟,IBM都會提供解決方案,它們的系統上的功能性技術點往往都有短板,單獨拿來用很雞肋,只有和其它的配合才可以。因此,想hack的,千萬別基於這些系統,你會煩死的,在這些系統上,這也不行,那也不行,這個不能變通,那個不能變通,用多了,人也就死板了,說話也就不好聽了,“技術上的任何問題都是可以解決的”這句話到了使用這些封閉系統的人那裡就成了“這是不可能的!!!”

3.人員衝突

一週以來,我跟人衝突好幾次,完事後還是證明我的想法是正確的,所以再次讓我感到大多數所謂的高階網管員的水平是多麼的垃圾,水平垃圾個性還一個個都挺狂妄,這實在讓人氣憤和悲哀。這些垃圾人員的觀點基本分為兩類:1>沒有做過的事情就說不可能因為以前從來沒有這麼做過,就說不可能,甚至否定我的技術方案。我是作為研發出現的,這就更讓對方覺得我根本就不懂網路,沒有經驗等,第一他們沒做過,第二我沒網路方面的職稱,這就更讓他們狂妄無比。2>想當然的事情就拍腦袋說可以,實際上根本就不可以我說要把udp服務一對一雙向映射出去:ip nat inside source udp 1.1.1.1 12345 2.2.2.2 12345 在測試的時候,他們非要用ping來測試!這個明明是一個udp的對映,怎麼用ping?!然而他們非要用ping,我告訴他們不能做全對映,因為會把不相干的不該轉換的地址也轉換了,因為他們知道NAT可以通過ACL+pool來做,所以他們就認為這個可以在outside口用ACL匹配地址來做...這幫傻逼!我要做目標地址轉換,還得是雙向的,也就是說不管資料流從那邊發起都是可以的!那幫人連NAT轉換項什麼時候安裝進系統的都不知道!        這些人還好意思說自己是網路工程師啊!看看我們的那些NA/NP/IE/NE/SE們,無一例外的就是靠背題的,好多證書都拿到手了,卻連網路的基本原理都TMD不懂!再看看那些真正的CCIE,比如Petr Lapukhov, 4xCCIE/CCDE in CCIE R&S,IP Services:Petr Lapukhov's career in IT begain in 1988 with a focus on computer programming, and progressed into networking with his first exposure to Novell NetWare in 1991. Initially involved with Kazan State University's campus network support and UNIX system administration, he went through the path of becoming a networking consultant, taking part in many network deployment projects. Petr currently has over 12 years of experience working in the Cisco networking field, and is the only person in the world to have obtained four CCIEs in under two years, passing each on his first attempt. Petr is an exceptional case in that he has been working with all of the technologies covered in his four CCIE tracks (R&S, Security, SP, and Voice) on a daily basis for many years. When not actively teaching classes, developing self-paced products, studying for the CCDE Practical & the CCIE Storage Lab Exam, and completing his PhD in Applied Mathematics.看看人家的經歷,從程式設計開始,逐步深入,從計算機體系結構,TCP/IP棧,到UNIX,最終,Cisco只是大師將這一切附著的一個平臺而已,如果他不是在Cisco上工作,而是加入IBM的陣營,或者去開發Linux核心,你能說人家不是圈子裡的人嗎?記住,別把程式設計的人看作不懂網路的。