Linux實現基於Loopback的NVI(NAT Virtual Interface)

來源:互聯網
上載者:User

Loopback實際上是個hole

但是如果它不是一個hole,它確實可以做一些事,類似Cisco的NVI那樣。既然前期是“如果它不是一個hole”,那就需要對代碼進行一些修改。在修改之前,你必須明白的是,Linux的loopback介面為什麼是一個hole。

        標準規定,所有試圖經過loopback介面去往其它地方(非本機)的資料包要全部丟棄。Linux使用loop hole做到了這一點。Linux的限制loopback流量在本機範圍的方式是,所有的loopback流量肯定經由本機發送,那麼在ip_output的時候就會將其設定為loopback_dst,然後進行入IP接收常式的時候,它已經有關聯的路由項了,進而就不會再去查詢路由表,因此凡是進入ip_input邏輯的資料包都不是本機發出的,於是在其內部就可以做比較狠的判斷的,凡是源地址是本機地址的,一律丟棄!這樣本機發出的包就不會先經由loopback口然後去往外部,下面我們看一下外部進入的包是否能經由loopback口去往外部。答案無疑是否定的,看下面的流程:資料包從物理網卡進入->被路由到lo口->將loopback_dst這個路由項關聯給資料包->loopback介面xmit資料包->類比loopback介面接收資料包->進入ip_input路由判斷->由於已經有了路由項故按照路由項轉寄。路由項的轉寄方式有兩種,對於外部進入的資料包,將不斷調用ip_forward,直到TTL變為0。因此只要進入了loopback,要麼直接丟棄,要麼瘋狂loop,是絕對出不去的。
        下面我就來說一下如何來破除這些約束。首先說一下本機發出的資料包如何先經由loopback再出去,然後說明外部進入的資料包如何先經由loopback再出去,最後說明,當做NAT的時候會碰到什麼問題以及如何結合上述針對本機發包以及外部發包兩種情境的措施來解決NAT問題。
1.本機發包經由loopback發出
修改代碼是不必可少的了,因為我這是在破壞原則。幸運的是,代碼只是修改一點點而已。修改的部分就是將這種“經由loopback發往別處”的包識別出來,然後刪除其關聯的路由項。這個用Netfilter在PREROUTING上做比較簡單。另外就是將表示該本機地址的Local路由從Local表刪除,然後作為unicast路由加入main表中,這樣在做反向路由查詢的時候,就不會匹配到Local表的路由了(Linux要求反向路由的類型必須是unicast的),到此即OK!
2.外部發包經由loopback轉寄
對於這種情況,只要是刪除了資料包的loopback路由項關聯,即可被順利轉寄。因為資料包的源IP地址不可能是原生IP,因此也就不可能是Local,如果資料流想原路返回的話,它就一定有反向的unicast路由。
3.NAT的問題
在配置了SNAT的情況下,要看SNAT成了什麼地址,如果是SANT成了本機地址,那就面臨上述第1節的問題,解決方案就是將該地址從Local表中刪除,但是刪除了之後會導致其它機器arp該地址的時候,本機不再回複,因此刪除了之後還要顯式arping一下該地址的arp更新;如果SNAT成了別的地址,就涉及到了反向可達性的問題,因為下一跳不一定知道該地址的可達性。
4.NAT問題的解決
NAT的問題僅僅是在SNAT成了別的地址時才會存在。這裡又分為兩種情況,第一種情況就是SNAT成了一個不相關的其它網段的地址,這樣僅僅要求下一跳配置到該地址的路由就可以保證資料流的反向包能返回到此BOX,這個路由配置在簡單環境下可以手工配置,複雜環境下可以用動態路由的方式進行SNAT地址的宣告;第二種情況就是SNAT的地址是和下一跳同一網段的情況,這會導致資料流反向包返回到下一跳的的時候,該SNAT的地址此時成了目標地址,由於處於同一網段,所以會被直接ARP,因此需要添加一條ARP轉換規則:
arptables -t mangle -A OUTPUT -d  下一跳網關地址  -j mangle --mangle-ip-s SNAT成的地址
        知道了問題所在以及解決方案,現在就可以動手了。本文的目標是實現一個類似Cisco NVI的東西,也就是一個虛擬網卡,在虛擬網卡的發送流程中實現NAT。鑒於有loopback這麼好的現成的東西,我也就不再寫虛擬網卡了,直接用loopback類比一個也好。大體流程如下:
資料包從物理網卡進入-> 執行DNAT->路由到loopback->執行SNAT->loopback口發出-> 策略路由->物理網卡發出
可以看到,路由執行了兩次,第一次是為了NAT,第二次是真正的路由。
        除了使用loopback,編寫一個類似veth的虛擬網卡是一個更不錯的選擇:
Veth stands for Virtual ETHernet. It is a simple tunnel driver that works at the link layer and looks like a pair of ethernet devices interconnected with each other.
比loopback好的是,這基本可以不修改代碼實現NVI,並且可以很容易取到資料包原始的進入介面。該驅動的邏輯非常簡單,即一個pair中包含一個主介面和一個輔助介面,資料包從主介面進入被路由到該主介面的輔助介面,注意,不改變skb的接收介面,這個所謂的路由只是為了搞一次“從物理網卡接收到發送到某另一個網卡的動作”,此時PREROUTING/POSTROUTING都已經完成了,真正的路由之後就可以從另一個主介面發出了。
        這次先不急著自己寫虛擬網卡,先折騰完loopback再說,那麼現在動手吧!
1.對代碼的修改:
重新封裝RAW表的NF_INET_PRE_ROUTING鉤子函數,在ipt_hook的調用前調用下面的邏輯:
//判斷有點太魯莽,正常應該可以設計成一個匹配演算法的if (skb->dev->flags & IFF_LOOPBACK && skb->nfct) {    skb->nfct = &nf_conntrack_untracked.ct_general;    skb->nfctinfo = IP_CT_NEW;    nf_conntrack_get(skb->nfct);    skb_dst_drop(skb);    return NF_ACCEPT;} 
這段代碼的意思是說,如果是資料包從物理網卡進入,顯然是需要匹配和應用規則(比如NAT)的,如果這件事做完了,資料也就是要通過路由匯入loopback介面了,此時就不要再使用conntrack了,然而此時skb的nfct可能已經被設定了,於是將其NOTRACK,並且將skb的路由緩衝丟棄。Linux的IP路由是這麼對待loopback的,如果路由查詢的結果出口是loopback介面,就是直接設定dst,loopback的xmit將資料包發出,調用一個netif_rx重新接收,到達ip_rcv_finish的時候,由於已經有了dst,就不必再查詢路由了。但是如果這樣的話,我們的第二次路由查詢-實際上是策略路由的查詢將無法實現,因此必須drop掉原有的dst。
2.配置IP地址和Netfilter策略
IP地址:
    link/ether 00:0c:29:90:66:c5 brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.249/24 brd 192.168.2.255 scope global eth2
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:90:66:cf brd ff:ff:ff:ff:ff:ff
    inet 192.168.40.249/24 scope global eth2
說明:暫用兩塊網卡,eth1串連內部,eth2串連外部

NAT表:
說明:將所有的發起於內部的資料包源IP都轉換成原生IP地址。
RAW表:
-A PREROUTING -i lo -j MARK --set-xmark 100
說明:從lo口進入,說明第一次路由查詢導致的NAT已經完成,打上MARK讓後續的路由邏輯將其識別為第二次真正的路由查詢。


策略:

32764:  from all fwmark 0x64 lookup loop
32766:  from all lookup main
32767:  from all lookup default
說明:這是一個策略,匹配一個FWMARK,該MARK由RAW表打上,用於識別是第一次路由查詢還是第二次路由查詢,第一次路由查詢用來實現NAT和一切和conntrack相關的操作,第二次查詢實現真正的IP路由。

主路由:
說明:主路由表中什麼都沒有了,就有一條預設路由通過loopback介面發出。如上所述,這次的路由查詢是“第一次有關NAT”的路由查詢,目的就是要讓資料包經過一次PRE-ROUTING和POST-ROUTING。
 
策略路由表loop:
192.168.2.0/24 dev eth1  scope link  src 192.168.2.249
192.168.40.0/24 dev eth2  scope link
default via 192.168.40.254 dev eth2
說明:我是將所有的main路由表中內容全部搬到策略路由表了,包括直連的鏈路層路由。因為我希望我的這套地址僅僅作為IP路由+NAT的輔助地址存在,就連直連的主機也不能通過直連路由發出了,因為main表中不再有直連路由了,所以直連流量也會進入loopback,完成故作的第一次路由之旅。凡是資料包打上了100標籤,就會去查詢loop路由表,如上所述,這次路由查詢是“第二次真正的路由查詢”。此次查詢就可以用source做policy routing了,因為源地址轉換已經完成了。

Local路由表:
local 127.0.0.1 dev lo  proto kernel  scope host  src 127.0.0.1
local 127.0.0.0/8 dev lo  proto kernel  scope host  src 127.0.0.1
說明:參與SNAT的地址要從Local表中刪除。因為IP路由不允許從本機發起的包經過loopback介面發往其它地方,如果SNAT將過路資料包的源地址轉換成了一個原生IP地址,那麼在反向路由驗證的時候,就會失敗。詳細點說還是由於Linux對待loopback流量的方式導致,由於Linux協議棧在output的時候就設定input的路由項,所以根本就不會到達ip_route_input,因此不允許loopback流量被forwarding。 但是,將物理網卡上的IP地址移出Local表有個副作用,那就是ARP邏輯不會再回複針對這些IP的ARP請求,因為將地址移出Local表相當於放棄了該地址的所有權。但是針對這個“下一跳解析協議”的問題有很多解決方案,比如靜態設定,比如專門配置一個代理,比如arping。

缺陷:本實現雖然大體實現了NVI的功能,但是還缺點什麼。比如資料包經過loopback介面這麼一繞,原始的入介面資訊就會丟失,需要複雜的conntrack規則才能將其映射到IP-FWMARK。

本例中,內網口和外網口上配置的IP地址完全用於路由,不再屬於本機,你不能指望通過這些IP地址來訪問BOX本身,因為為了讓反向路由檢查可以通過,這些地址標示的Local路由已經從Local表中被刪除了,所以它們不再標示主機。另外值得注意的是,不單單如此,該BOX連針對這些配置在物理網卡上IP地址的ARP請求都不會回複,因為Linux是否回複ARP請求是基於可以在Local表中查到該IP表示的路由這個前提的。

總結一下再次可以看到,你可以多麼自由地對Linux進行修改和定製,甚至完全可以破壞一些公認的原則,但是前提是你必須知道這些原則被破壞掉以後的後果是什麼,你有充分的這麼做的理由並且必須這麼做,也許,你還沒有發現更好的方法,現在只能這麼做。不管怎麼說,做點自由的事情,總會有很多收穫!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.