天天看點

使用netns虛拟網絡進行網絡測試 ********************* 網絡虛拟化技術(一): Linux網絡虛拟化

網絡虛拟化技術(一): Linux網絡虛拟化

http://ju.outofmemory.cn/entry/73667

netns是在linux中提供網絡虛拟化的一個項目,使用netns網絡空間虛拟化可以在本地虛拟化出多個網絡環境,目前netns在lxc容器中被用來為容器提供網絡。

使用netns建立的網絡空間獨立于目前系統的網絡空間,其中的網絡裝置以及iptables規則等都是獨立的,就好像進入了另外一個網絡一樣。

netns虛拟網絡空間的網絡通信依賴于實體接口,光講聽上去很虛,我們來操練點實際的看看:

1.建立虛拟網絡空間:

ip netns add ns1

這樣我們就得到了一個名為ns1的網絡空間,虛拟網絡空間除了網絡是虛的以外,檔案系統完全和目前系統共享,也就是說所有本地可以使用的指令都可以在虛拟網絡中使用,我們進入ns1看看情況:

ip netns exec ns1 bash

ifconfig -a

~# ifconfig -a

lo Link encap:Local Loopback

LOOPBACK MTU:16436 Metric:1

RX packets:0 errors:0 dropped:0 overruns:0 frame:0

TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:0

RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

可以看到ns1中預設添加了一個本地回路裝置,其他神馬也沒有,下面我們退出ns1,給他添加點網卡,之是以要在外部添加網卡是因為我們要為後面聯通網絡做準備,

exit

ip link add name ns1-nic type veth peer name ns1-vnic

這裡我們添加了一對veth裝置,veth裝置是成對出現的,兩個裝置之間的資料是互相貫通的,我們把ns1-vnic加入到ns1網絡空間中:

ip link set ns1-vnic netns ns1

這樣ns1-vnic就進入到ns1空間了,在本地網絡中已經無法檢視到,我們重新進入ns1看看現在的情況:

ip netns exec ns1 bash

# ifconfig -a

lo Link encap:Local Loopback

LOOPBACK MTU:16436 Metric:1

RX packets:0 errors:0 dropped:0 overruns:0 frame:0

TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:0

RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

ns1-vnic Link encap:Ethernet HWaddr 92:69:39:f3:b3:be

BROADCAST MULTICAST MTU:1500 Metric:1

RX packets:0 errors:0 dropped:0 overruns:0 frame:0

TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:1000

RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

可以看到多出來了一塊網卡,雖然網卡名字有點醜,不過網卡名字可以随意修改的,我們改一改:

ip link set ns1-vnic name eth0

# ifconfig -a

eth0 Link encap:Ethernet HWaddr 92:69:39:f3:b3:be

BROADCAST MULTICAST MTU:1500 Metric:1

RX packets:0 errors:0 dropped:0 overruns:0 frame:0

TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:1000

RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

lo Link encap:Local Loopback

LOOPBACK MTU:16436 Metric:1

RX packets:0 errors:0 dropped:0 overruns:0 frame:0

TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:0

RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

剛才把預設的名字修改為了eth0,下面我們給他配置設定一個private ip address:

ip addr add 10.0.0.100/24 dev eth0

然後把它啟動起來:

ip link set eth0 up

# ifconfig

eth0 Link encap:Ethernet HWaddr 92:69:39:f3:b3:be

inet addr:10.0.0.100 Bcast:0.0.0.0 Mask:255.255.255.0

UP BROADCAST MULTICAST MTU:1500 Metric:1

RX packets:0 errors:0 dropped:0 overruns:0 frame:0

TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:1000

RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

然後我們再把回環起起來,ping一下自己感受一下:

# ip link set lo up

[email protected]:~# ping 10.0.0.100

PING 10.0.0.100 (10.0.0.100) 56(84) bytes of data.

64 bytes from 10.0.0.100: icmp_req=1 ttl=64 time=0.072 ms

64 bytes from 10.0.0.100: icmp_req=2 ttl=64 time=0.040 ms

嗷嗷!ping自己通了!那麼我們怎麼樣讓ns1中的eth0和真實server中的網絡進行通信呢?首先讓他能和本地實體機通信,我們在實體機網絡中采用橋接的方式,把剛才添加veth虛拟網卡的時候被拆開的一對的剩下一隻添加的網橋裡面,利用成對veth互相之間資料貫通的特性來實作網絡互通:

先退出ns1,本地添加網橋:

exit

brctl addbr testbr

brctl addif testbr ns1-nic

ip link set ns1-nic up

然後給網橋設定一個ip位址:

ip addr add 10.0.0.1/24 dev testbr

 ip link set testbr up

~# ifconfig testbr

testbr Link encap:Ethernet HWaddr 6a:d1:34:5b:3c:99

inet addr:10.0.0.1 Bcast:0.0.0.0 Mask:255.255.255.0

inet6 addr: fe80::68d1:34ff:fe5b:3c99/64 Scope:Link

UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

RX packets:0 errors:0 dropped:0 overruns:0 frame:0

TX packets:6 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:0

RX bytes:0 (0.0 B) TX bytes:468 (468.0 B)

下面來ping一下ns1中的eth0網卡ip10.0.0.100感受一下:

~# ping 10.0.0.100

PING 10.0.0.100 (10.0.0.100) 56(84) bytes of data.

64 bytes from 10.0.0.100: icmp_req=1 ttl=64 time=0.200 ms

64 bytes from 10.0.0.100: icmp_req=2 ttl=64 time=0.042 ms

嗷嗷!又通了!

我們進入ns1往10.0.0.1來ping一下:

~# ip netns exec ns1 ping 10.0.0.1

PING 10.0.0.1 (10.0.0.1) 56(84) bytes of data.

64 bytes from 10.0.0.1: icmp_req=1 ttl=64 time=0.057 ms

64 bytes from 10.0.0.1: icmp_req=2 ttl=64 time=0.063 ms

嗷嗷!是通的!

但是現在ns1中的網絡還通路不到外網,那麼如何才能讓ns1中的網絡可以通路到外面的世界呢?

常用的namespace的指令:

1. 添加一個namespace

sudo ip netns add [name]

2. 在namespace中啟用一個裝置

sudo ip netns exec   [name]  ip link set lo up

3. 在namespace中新加一個裝置

sudo ip link set  [dev-name]   netns  [name]

啟用:

sudo ip netns exec   [name]  ip link set [dev-name] up

4. 檢視指定namespace中指定裝置的參數資訊

sudo ip netns exec  [name] ip addr show   [dev-name]   permanent scope global

5. 為namespace中指定裝置設定ip

sudo ip netns exec   [name]  ip -4 addr add 192.168.1.2/24 brd 192.168.1.255 scope global dev   [dev-name]  

6.檢視所有network namespace

ip netns list

7.ping虛拟機執行個體

ip netns exec [name] ping 192.168.1.3

上周有廠商到公司測試,拿了一塊據說很猛的網絡處理加速PCIe闆卡,拎在手裡沉甸甸的很有分量,最讓人意淫的是那4個萬兆光口,于是我迫不及待的想要一覽光口轉發時那種看不見的震撼。

可是,僅憑4個光口怎麼測試?起碼你要有個“對端”啊!任何人應該都不想扛着三台機器在客戶們之間跑來跑去測試其轉發性能,當然你也不能指望客戶那裡就一定有你需要的“對端”裝置,比如我們公司就沒有這種和萬兆光口對接的裝置,不過趕巧的是,那天還真有一台裝置帶有萬兆光口,但是隻是碰巧了。最佳的測試方式當然是不依賴任何外部裝置了,顯而易見的方法就是做自環。

RJ45口的雙絞線可以做實體層自環,1/3,2/6短接即可,這樣一台機器的一塊網卡自己就可以既發又收了,但是你能對比頭發略粗的光纖做什麼呢?真實的做法當然是用軟體解決了,在Linux上可以使用netns來解決,即net namespace。

netns是一個很好玩的東西,它可以讓你在一台機器上模拟多個網絡裝置,這樣做的意義是非同一般的:

1.使用netns可以充分利用閑置的處理器資源,特别是你的多塊網卡性能壓不滿CPU的時候;

2.使用netns可以将不同類型的網絡應用隔離,針對每一類實施不同的政策;

3.使用netns有點玩虛拟化的意思,不過比虛拟機更靈活。

一個net namespace有自己獨立的路由表,iptables政策,裝置管理機構,和其它的netns完全隔離,比如你将eth0加入了netns1,那麼netns2中的應用程式就看不到eth0,網卡裝置管理隻是netns中的一個元素,還有很多,比如你在netns1中配置的iptables政策對netns2中的資料包沒有任何影響。總之,如果你懂Linux核心源碼,那麼隻要附着有net結構體字段的那些結構,比如skb,net_device,都和netns有關。

那麼我應該怎麼做自環呢?我的裝置有4個網卡,我希望1和4之間通信,通過2和3轉發,它的邏輯拓撲如下:

PC1/eth0----PC2/eth1(forward)PC2/eth2----PC3/eth3

很簡單,将eth0和eth3設定在兩個不同的netns,然後用線纜連接配接eth0和eth1,同樣連接配接eth2和eth3,最後将eth0和eth1的IP位址設定在一個網段,将eth2和eth3的IP位址設定在另一個不同的網段即可。光說不練假把式,具體應該怎麼做呢?同樣很簡單:

1.添加兩個netns

ip netns add t1

ip netns add t2

2.将eth0加入t1,并且設定IP位址

ip link set eth0 netns t1

此時再ifconfig就看不到eth0了,你甚至執行ls /sys/class/net也看不到eth0了,隻有執行ip netns exec t1 ls /sys/class/net才能看到。

ip netns exec t1 ifconfig eth0 192.168.1.200/24

3.将eth3加入t2,并且設定IP位址

ip link set eth3 netns t2

此時ifconfig就看不到eth3了,你甚至執行ls /sys/class/net也看不到eth3了,隻有執行ip netns exec t2 ls /sys/class/net才能看到。

ip netns exec t1 ifconfig eth3 172.16.1.200/24

4.設定eth1和eth2的位址

ifconfig eth1 192.168.1.1/24

ifconfig eth2 172.16.1.1/24

5.設定兩個netns的預設路由

ip netns exec t1 route add default gw 192.168.1.1

ip netns exec t2 route add default gw 172.16.1.1

6.測試

在netns t1中ping netns t2中的eth3位址

ip netns exec t1 ping 172.16.1.200

上述配置之後,從eth0發出的包會通過網線到達eth1(而不是走local路由表的loopback),然後經過eth1的forward從eth2發出。經由網線到達目的地eth3杯接收。整個過程中就一台機器,展示出的效果好像三台機器的樣子。有了這個機制,是不是再也不用為搭建測試環境而發愁了呢?

除了自環測試之外,netns還可以用于設定政策路由,這種政策路由不需要ip rule。試想一種場景,你同時運作了P1和P2兩個程式,本機所在的區域網路有兩個出口到達外網,你希望P1通過gw1和外界通信,P2通過gw2和外界通信,限制條件是你的機器隻有一張網卡eth0,怎麼辦呢?通過iptables為P1和P2的資料包打上不同的mark,然後通過ip rule設定政策路由無疑可以解決,另外直接在P1和P2應用程式中用setsockopt也是可以設定ipmark的,這就不需要iptables了。然而這一切都過時了,2014年我需要一種不同的方式。

我不知道怎麼表達我思考的過程,但是給出一個操作序列是簡單的事情,因為照着這麼做确實可以滿足需求,然後看到這篇文章的人照着操作步驟倒推回去,就可以得到一個思考過程。首先你要明白的是Linux核心支援一種虛拟網卡類型,即veth,一般而言veth是成對的,從一個veth發出的資料包可以直接到達它的peer veth,感興趣的可以看Linux核心的drivers/net/veth.c,和drivers/net/tun.c沒什麼不同,更簡單些罷了。第一步要做的就是建立一對veth:

ip link add veth1 type veth peer name veth2

此時系統中除了eth0之外又多了兩塊網卡,所有的網卡為lo,eth0,veth1,veth2。中間隐含着一個事實,即veth1和veth2之間有一條虛拟的鍊路将兩塊網卡連接配接起來,就好像一條雙絞線連接配接的兩塊實體網卡一樣。我現在希望P1的資料包通過veth1發出,然後自然而然地就能發到veth2,但是随後怎麼通過eth0發到實體線路呢?太簡單,太簡單,使用bridge吧:

brctl addbr br0

brctl addif br0 eth0 veth2

同時,veth1和br0所在的區域網路設定在一個IP網段中,這下子就全通了,該二層網絡的邏輯拓撲為:

veth1----veth2(bridge)eth0----gw(1,2)

怎麼設定netns我本來不想說了,但是由于小小暫時不跟我玩了,我還是寫完吧。首先将veth1設定到netns1(具體怎麼建立netns,不再贅述)并設定路由:

ip link set veth1 netns netns1

ip netns exec netns1 route add default gw $gw1

route add default gw $gw2

這就完了?是的,完事了。事實上,保留br0的預設netns即可,沒有必要建立netns2了。接下來需要做的就是啟動P1和P2了:

ip netns exec netns1 P1

P2

好了,一切結束。

我始終都覺得,在Linux上一般都是不用修改源碼就能解決問題,可是我還是喜歡修改代碼,原因何在?很簡單,源碼很容易獲得,并且源碼很容易修改,我走火入魔般地寫了大量的Netfilter擴充以及做了大量的nf_conntrack修改,甚至還添加了一些該死的socket filter...雖然這些行為都是自娛自樂型的,并沒有被應用在工作中,但是這些行為說明我不是網絡管理者,而是一名程式員,哈哈,自封的資深軟體工程師(我還是覺得這些成果能被應用)。然而,做一名技術精湛的網絡管理人員的難度卻遠遠超過做程式員的難度。這不,又一次遇到了OpenVPN的多執行個體問題,我覺得,單純的程式員搞不定它,單純的網管也不行。

TAP模式的多執行個體已經被我用Linux Bridge完美蹂躏了,但是TUN模式的多執行個體問題仍然沒有完美的方案,雖然修改tun驅動,使用broadcast mode bonding+tun filter可以解決,但是我還是覺得那是一種走火入魔的方式,是以就算在公司我也沒能将整個調試測試進行下去,結果落了個不了了之,事實上,是我太不喜歡那種方式。tun的IP filter是我改出來的方案,并非标準的,能不能使用标準的方式進行尋址呢?使用netns,答案就是肯定的。

假設在GW上啟動了2個OpenVPN執行個體ovpn1和ovpn2,虛拟網卡分别為tun1和tun2,在client-connect腳本中得知ovpn2負責N1,ovpn2負責N2。現在問題的關鍵是,GW後方出發的資料包如何知道是将資料包發送到tun1還是tun2,這個判斷能不能自動進行?如果使用netns,那就是可以的,我可以将2個tun分别設定在不同的netns,然後每一個netns對應一個同處一個netns的veth虛拟網卡,這些veth的peer們處在另外一個netns中,這樣就可以實作IP層TUN模式虛拟網卡到以太網的TAP模式虛拟網卡的适配。最後将這些peer們Bridge成一個br0,那麼TUN模式的OpenVPN就能和TAP模式的OpenVPN采用同一種方式處理了。

不管怎樣,當你玩弄netns的時候,你要知道你并不是在玩弄冷酷無情的虛拟化作業系統,也不是真的模拟了兩台實體上互相隔離的機器,因為雖然兩個程式的網絡是隔離的,但是檔案系統卻是共享的。你要時刻準備着,使用網絡隔離和使用記憶體,檔案系統共享相結合。将一台機器既可以作為多台機器使用,又可以作為一台機器共享資源!

了解了上述的例子和最後的總結,那麼我來發問,單網卡或者沒有網卡怎麼玩自環?這個需求可能就是為了測試一下協定棧而已。略去思考的過程,很簡單,多加一個層次。比如你有一台機器一塊網卡也沒有,那麼你隻需要下面的指令就可以在你的機器上實作IP轉發或者bridge轉發了:

ip link add v1 type veth peer name vp1

ip link add v2 type veth peer name vp2

brctl addbr br0

brctl addif vp1 vp2

ifconfig vp1 up

ifconfig vp2 up

sysctl -w net.ipv4.ip_forward=1

ip netns add t1

ip netns add t2

ip link set v1 netns t1

ip link set v2 netns t2

ip netns exec t1 ifconfig v1 1.1.1.1/24

ip netns exec t2 ifconfig v2 1.1.1.2/24

ip netns exec t1 ping 1.1.1.2

...