1. 程式人生 > >Linux 網路名稱空間

Linux 網路名稱空間

虛擬網路基礎

和磁碟裝置類似,Linux 使用者想要使用網路功能,不能通過直接操作硬體完成,而需要直接或間接的操作一個 Linux 為我們抽象出來的裝置,既通用的Linux網路裝置來完成。一個常見的情況是,系統裡裝有一個硬體網絡卡,Linux 會在系統裡為其生成一個網路裝置例項,如eth0,使用者需要對eth0發出命令以配置或使用它了。更多的硬體會帶來更多的裝置例項,虛擬的硬體也會帶來更多的裝置例項。
隨著網路技術,虛擬化技術的發展,更多的高階網路裝置被加入了到了Linux中,使得情況變得更加複雜。
在本節中,將一一分析在虛擬化技術中經常使用的幾種 Linux 網路裝置抽象型別:Bridge、802.1.q VLAN device、VETH、TAP,詳細解釋如何用它們配合 Linux 中的 Route table、IP table簡單的創建出本地虛擬網路。
Linux的網路虛擬化是LXC專案中的一個子專案,LXC包括檔案系統虛擬化,程序空間虛擬化,使用者虛擬化,網路虛擬化,等等。

bridge

Bridge(橋)是 Linux 上用來做 TCP/IP 二層協議交換的裝置,與現實世界中的交換機功能相似。Bridge 裝置例項可以和 Linux 上其他網路裝置例項連線,既attach一個從裝置,類似於在現實世界中的交換機和一個使用者終端之間連線一根網線。當有資料到達時,Bridge會根據報文中的MAC資訊進行廣播、轉發、丟棄處理。
圖 1.Bridge 裝置工作過程
Bridge 裝置工作過程
如圖所示,Bridge 的功能主要在核心裡實現。當一個從裝置被 attach 到 Bridge 上時,相當於現實世界裡交換機的埠被插入了一根連有終端的網線。這時在核心程式裡,netdev_rx_handler_register()被呼叫,註冊一個用於接受資料的回撥函式

。以後每當這個從裝置收到資料時都會呼叫這個函式可以把資料轉發到 Bridge 上。當 Bridge 接收到此資料時,br_handle_frame()被呼叫,進行一個和現實世界中的交換機類似的處理過程:
1. 判斷包的類別(廣播/單點)
1. 查詢內部 MAC 埠對映表
1. 定位目標埠號
1. 將資料轉發到目標埠或丟棄
1. 自動更新內部 MAC 埠對映表以自我學習

Bridge和現實世界中的二層交換機有一個==區別==,圖中左側畫出了這種情況:資料被直接發到Bridge上,而不是從一個埠接受。這種情況可以看做Bridge自己有一個MAC可以主動傳送報文,或者說Bridge自帶了一個隱藏埠和寄主 Linux 系統自動連線,Linux 上的程式可以直接從這個埠向 Bridge 上的其他埠發資料。所以當一個 Bridge 擁有一個網路裝置時,如 bridge0 加入了 eth0 時,實際上 bridge0 擁有兩個有效 MAC 地址,一個是 bridge0 的,一個是 eth0 的,他們之間可以通訊。
由此帶來一個有意思的事情是,Bridge 可以設定 IP 地址。通常來說 IP 地址是三層協議的內容,不應該出現在二層裝置 Bridge 上。但是 Linux 裡 Bridge 是通用網路裝置抽象的一種,只要是網路裝置就能夠設定 IP 地址。當一個 bridge0 擁有 IP 後,Linux 便可以通過路由表或者IP表規則在三層定位bridge0,此時相當於Linux擁有了另外一個隱藏的虛擬網絡卡和 Bridge 的隱藏埠相連,這個網絡卡就是名為bridge0的通用網路裝置,IP可以看成是這個網絡卡的。當有符合此 IP 的資料到達 bridge0 時,核心協議棧認為收到了一包目標為本機的資料,此時應用程式可以通過 Socket接收到它。
一個更好的對比例子是==現實世界中的帶路由的交換機裝置==,它也擁有一個隱藏的 MAC 地址,供裝置中的三層協議處理程式和管理程式使用。裝置裡的三層協議處理程式,對應名為 bridge0 的通用網路裝置的三層協議處理程式,即寄主Linux系統核心協議棧程式。裝置裡的管理程式,對應bridge0寄主Linux系統裡的應用程式。
Bridge 的實現當前有一個==限制==:當一個裝置被 attach 到 Bridge 上時,那個裝置的 IP 會變的無效,Linux 不再使用那個 IP 在三層接受資料。舉例如下:如果 eth0 本來的 IP 是 192.168.1.2,此時如果收到一個目標地址是 192.168.1.2 的資料,Linux 的應用程式能通過 Socket 操作接受到它。而當 eth0 被 attach 到一個 bridge0 時,儘管 eth0 的 IP 還在,但應用程式是無法接受到上述資料的。此時應該把 IP 192.168.1.2 賦予 bridge0。
另外需要注意的是==資料流的方向==。對於一個被attach到Bridge上的裝置來說,只有它收到資料時,此包資料才會被轉發到Bridge上,進而完成查表廣播等後續操作。當請求是傳送型別時,資料是不會被轉發到 Bridge 上的,它會尋找下一個傳送出口。使用者在配置網路時經常忽略這一點從而造成網路故障。

TAP 裝置與 VETH 裝置

TUN/TAP 裝置是一種讓使用者態程式向核心協議棧注入資料的裝置,一個工作在三層,一個工作在二層,使用較多的是 TAP 裝置。VETH裝置出現較早,它的作用是反轉通訊資料的方向,需要傳送的資料會被轉換成需要收到的資料重新送入核心網路層進行處理,從而間接的完成資料的注入。
圖 2 .TAP 裝置和 VETH 裝置工作過程

當一個TAP裝置被建立時,在Linux裝置檔案目錄下將會生成一個對應char裝置,使用者程式可以像開啟普通檔案一樣開啟這個檔案進行讀寫。當執行 write()操作時,資料進入 TAP 裝置,此時對於 Linux 網路層來說,相當於 TAP 裝置收到了一包資料,請求核心接受它,如同普通的物理網絡卡從外界收到一包資料一樣,不同的是其實資料來自Linux上的一個使用者程式。Linux收到此資料後將根據網路配置進行後續處理,從而完成了使用者程式向Linux核心網路層注入資料的功能。當用戶程式執行read()請求時,相當於向核心查詢 TAP 裝置上是否有需要被髮送出去的資料,有的話取出到使用者程式裡,完成 TAP 裝置的傳送資料功能。
針對 TAP 裝置的一個形象的比喻是:使用 TAP 裝置的應用程式相當於另外一臺計算機,TAP 裝置是本機的一個網絡卡,他們之間相互連線。應用程式通過 read()/write()操作,和本機網路核心進行通訊。

VETH 裝置總是成對出現,送到一端請求傳送的資料總是從另一端以請求接受的形式出現。該裝置不能被使用者程式直接操作,但使用起來比較簡單。建立並配置正確後,向其一端輸入資料,VETH 會改變資料的方向並將其送入核心網路核心,完成資料的注入。在另一端能讀到此資料。

netns

netns是在linux中提供網路虛擬化的一個專案,使用netns網路空間虛擬化可以在本地虛擬化出多個網路環境,目前netns在lxc容器中被用來為容器提供網路。
使用netns建立的網路空間獨立於當前系統的網路空間,其中的網路裝置以及iptables規則等都是獨立的,就好像進入了另外一個網路一樣。不同網路名稱空間中的裝置是不同的,之間不能互相直接通訊。

網路名稱空間

在 Linux 中,網路名字空間可以被認為是隔離的擁有單獨網路棧(網絡卡、路由轉發表、iptables)的環境。網路名字空間經常用來隔離網路裝置和服務,只有擁有同樣網路名字空間的裝置,才能看到彼此。

虛擬網路建立

iproute2

iproute2 is usually shipped in a package called iproute or iproute2 and consists of several tools, of which the most important are ip and tc. ip controls IPv4 and IPv6 configuration and tc stands for traffic control. Both tools print detailed usage messages and are accompanied by a set of manpages.

建立新的網路名稱空間

使用命令

$ ip netns add net0

可以建立一個完全隔離的新網路環境,這個環境包括一個獨立的網絡卡空間,路由表,ARP表,ip地址表,iptables,ebtables,等等。總之,與網路有關的元件都是獨立的。

ip命令需要root許可權的,但是由於本文大量使用ip命令,於是筆者給ip命令添加了capability,使普通使用者也能使用ip命令

顯示所有的虛擬網路名稱空間

ip netns list

或者

ls /var/run/netns/

使用命令

$ ip netns list
net0

可以看到我們剛才建立的網路環境

通常情況下,一般我們用 ip netns add 新增新的 netns,然後我們可以用 ip netns list 檢視所有的 netns。但有的時候程序的 netns 卻並沒有顯式匯出,ip netns list 無法列出它,比如 docker 的 container的網路空間。怎麼辦呢?答案是做個符號連結就可以了。

ln -sf /proc/<pid>/ns/net /var/run/netns/$ns

簡要說明一下這個命令:每個程序的網路名稱空間都是通過 proc 檔案系統匯出來了的,位於 /proc//ns/net (這個檔案不可讀,它只是相當於一個訪問點);而 ip netns list 命令是從 /var/run/netns 這個路徑讀取netns列表的,因此直接將程序的名稱空間連結到 /var/run/netns 目錄下就可以了。

進入虛擬網路環境

使用命令

$ ip netns exec net0 `command`

我們可以在 net0 虛擬環境中執行任何命令

$ ip netns exec net0 bash
$ ip ad
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

這樣我們可以在新的網路環境中開啟一個shell,可以看到,新的網路環境裡面只有一個lo裝置,並且這個lo裝置與外面的lo裝置是不同的,之間不能互相通訊。

設定虛擬網路環境net0的veth0裝置處於啟用狀態

ip netns exec net0 ip link set veth0 up

為虛擬網路環境net0的veth0裝置增加IP地址

ip netns exec net0 ip address add 10.0.1.1/24 dev veth0

連線兩個網路環境

新的網路環境裡面沒有任何網路裝置,並且也無法和外部通訊,就是一個孤島,通過下面介紹的這個方法可以把兩個網路環境連起來,簡單的說,就是在兩個網路環境之間拉一根網線

$ ip netns add net1

先建立另一個網路環境net1,我們的目標是把net0與net1連起來

$ ip link add type veth
$ ip ad # address, show protocol (IP or IPv6) address on a device.
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
81: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 12:39:09:81:3a:dd brd ff:ff:ff:ff:ff:ff
82: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 32:4f:fd:cc:79:1b brd ff:ff:ff:ff:ff:ff

這裡建立連一對veth虛擬網絡卡,類似pipe,發給veth0的資料包veth1那邊會收到,發給veth1的資料包veth0會收到。就相當於給機器安裝了兩個網絡卡,並且之間用網線連線起來了

$ ip link set veth0 netns net0
$ ip link set veth1 netns net1

這兩條命令的意思就是把veth0移動到net0環境裡面,把veth1移動到net1環境裡面,我們看看結果

$ ip ad
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
$ ip netns exec net0 ip ad
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
81: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 12:39:09:81:3a:dd brd ff:ff:ff:ff:ff:ff
$ ip netns exec net1 ip ad
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
82: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 32:4f:fd:cc:79:1b brd ff:ff:ff:ff:ff:ff

veth0 veth1已經在我們的環境裡面消失了,並且分別出現在net0與net1裡面。下面我們簡單測試一下net0與net1的聯通性

$ ip netns exec net0 ip link set veth0 up
$ ip netns exec net0 ip address add 10.0.1.1/24 dev veth0
$ ip netns exec net1 ip link set veth1 up
$ ip netns exec net1 ip address add 10.0.1.2/24 dev veth1

分別配置好兩個裝置,然後用ping測試一下聯通性:

$ ip netns exec net0 ping -c 3 10.0.1.2
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
64 bytes from 10.0.1.2: icmp_req=1 ttl=64 time=0.101 ms
64 bytes from 10.0.1.2: icmp_req=2 ttl=64 time=0.057 ms
64 bytes from 10.0.1.2: icmp_req=3 ttl=64 time=0.048 ms

--- 10.0.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.048/0.068/0.101/0.025 ms

實踐:一個稍微複雜的網路環境

graph TD
A[bridge] -->|10.0.1.1| B[net0]
A[bridge] -->|10.0.1.2| C[net1]

建立虛擬網路環境並且連線網線

ip netns add net0
ip netns add net1
ip netns add bridge

ip link add type veth
ip link set dev veth0 name net0-bridge netns net0
ip link set dev veth1 name bridge-net0 netns bridge

ip link add type veth
ip link set dev veth0 name net1-bridge netns net1
ip link set dev veth1 name bridge-net1 netns bridge

在bridge中建立並且設定br裝置

ip netns exec bridge brctl addbr br
ip netns exec bridge ip link set dev br up
ip netns exec bridge ip link set dev bridge-net0 up
ip netns exec bridge ip link set dev bridge-net1 up
ip netns exec bridge brctl addif br bridge-net0
ip netns exec bridge brctl addif br bridge-net1

然後配置兩個虛擬環境的網絡卡

ip netns exec net0 ip link set dev net0-bridge up
ip netns exec net0 ip address add 10.0.1.1/24 dev net0-bridge

ip netns exec net1 ip link set dev net1-bridge up
ip netns exec net1 ip address add 10.0.1.2/24 dev net1-bridge

測試

$ ip netns exec net0 ping -c 3 10.0.1.2
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
64 bytes from 10.0.1.2: icmp_req=1 ttl=64 time=0.121 ms
64 bytes from 10.0.1.2: icmp_req=2 ttl=64 time=0.072 ms
64 bytes from 10.0.1.2: icmp_req=3 ttl=64 time=0.069 ms

--- 10.0.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.069/0.087/0.121/0.025 ms

配置lldpd檢查線路連結情況

隨著虛擬網路環境增加,環境中網絡卡數量也在不斷增加,經常會忘記環境中哪些網絡卡連線到哪裡,通過 Link Layer Discovery Protocol,我們可以清楚看到每個網絡卡連線到了哪些環境中的哪個網絡卡。

github 上有一個 lldp 在 linux 下的開源實現 implementation of IEEE 802.1ab (LLDP),通過在每個環境中起一個 lldp daemon,我們就可以實時檢視每個網絡卡的連線情況。

Bridge 上 lldp 的資料

$ lldpcli show neighbors

LLDP neighbors:

Interface:    bridge-net0, via: LLDP, RID: 2, Time: 0 day, 00:06:53
  Chassis:
    ChassisID:    mac 82:be:2a:ec:70:69
    SysName:      localhost
    SysDescr:     net0
    Capability:   Bridge, off
    Capability:   Router, off
    Capability:   Wlan, off
  Port:
    PortID:       mac 82:be:2a:ec:70:69
    PortDescr:    net0-bridge

Interface:    bridge-net1, via: LLDP, RID: 1, Time: 0 day, 00:06:53
  Chassis:
    ChassisID:    mac b2:34:28:b1:be:49
    SysName:      localhost
    SysDescr:     net1
    Capability:   Bridge, off
    Capability:   Router, off
    Capability:   Wlan, off
  Port:
    PortID:       mac b2:34:28:b1:be:49
    PortDescr:    net1-bridge

參考文獻