歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux實現基於Loopback的NVI(NAT Virtual Interface)

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

日期:2017/3/3 16:13:40   编辑:關於Linux

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地址:

3: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast state DOWN qlen 1000

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表:

-A POSTROUTING -j SNAT -i eth1 --to-source 192.168.40.249

說明:將所有的發起於內部的數據包源IP都轉換成本機的IP地址。

RAW表:

-A PREROUTING -i lo -j MARK --set-xmark 100

說明:從lo口進入,說明第一次路由查詢導致的NAT已經完成,打上MARK讓後續的路由邏輯將其識別為第二次真正的路由查詢。

策略:

0: from all lookup local

32764: from all fwmark 0x64 lookup loop

32766: from all lookup main

32767: from all lookup default

說明:這是一個策略,匹配一個FWMARK,該MARK由RAW表打上,用於識別是第一次路由查詢還是第二次路由查詢,第一次路由查詢用來實現NAT和一切和conntrack相關的操作,第二次查詢實現真正的IP路由。

主路由:

default dev lo scope link

說明:主路由表中什麼都沒有了,就有一條默認路由通過loopback接口發出。如上所述,這次的路由查詢是“第一次有關NAT”的路由查詢,目的就是要讓數據包經過一次PRE-ROUTING和POST-ROUTING。

策略路由表loop:

192.168.40.249 dev eth2 scope link

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 192.168.2.249 dev eth2 proto kernel scope host src 192.168.2.249

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

總結一下

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

 

Copyright © Linux教程網 All Rights Reserved