和磁盤設備類似,Linux 用戶想要使用網絡功能,不能通過直接操作硬件完成,而需要直接或間接的操作一個 Linux 為我們抽象出來的設備,既通用的 Linux 網絡設備來完成。一個常見的情況是,系統裡裝有一個硬件網卡,Linux 會在系統裡為其生成一個網絡設備實例,如 eth0,用戶需要對 eth0 發出命令以配置或使用它了。更多的硬件會帶來更多的設備實例,虛擬的硬件也會帶來更多的設備實例。隨著網絡技術,虛擬化技術的發展,更多的高級網絡設備被加入了到了 Linux 中,使得情況變得更加復雜。在以下章節中,將一一分析在虛擬化技術中經常使用的幾種 Linux 網絡設備抽象類型:Bridge、802.1.q VLAN device、VETH、TAP,詳細解釋如何用它們配合 Linux 中的 Route table、IP table 簡單的創建出本地虛擬網絡。
Bridge(橋)是 Linux 上用來做 TCP/IP 二層協議交換的設備,與現實世界中的交換機功能相似。Bridge 設備實例可以和 Linux 上其他網絡設備實例連接,既 attach 一個從設備,類似於在現實世界中的交換機和一個用戶終端之間連接一根網線。當有數據到達時,Bridge 會根據報文中的 MAC 信息進行廣播、轉發、丟棄處理。
如圖所示,Bridge 的功能主要在內核裡實現。當一個從設備被 attach 到 Bridge 上時,相當於現實世界裡交換機的端口被插入了一根連有終端的網線。這時在內核程序裡,netdev_rx_handler_register()被調用,一個用於接受數據的回調函數被注冊。以後每當這個從設備收到數據時都會調用這個函數可以把數據轉發到 Bridge 上。當 Bridge 接收到此數據時,br_handle_frame()被調用,進行一個和現實世界中的交換機類似的處理過程:判斷包的類別(廣播/單點),查找內部 MAC 端口映射表,定位目標端口號,將數據轉發到目標端口或丟棄,自動更新內部 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 上的,它會尋找下一個發送出口。用戶在配置網絡時經常忽略這一點從而造成網絡故障。
VLAN 又稱虛擬網絡,是一個被廣泛使用的概念,有些應用程序把自己的內部網絡也稱為 VLAN。此處主要說的是在物理世界中存在的,需要協議支持的 VLAN。它的種類很多,按照協議原理一般分為:MACVLAN、802.1.q VLAN、802.1.qbg VLAN、802.1.qbh VLAN。其中出現較早,應用廣泛並且比較成熟的是 802.1.q VLAN,其基本原理是在二層協議裡插入額外的 VLAN 協議數據(稱為 802.1.q VLAN Tag),同時保持和傳統二層設備的兼容性。Linux 裡的 VLAN 設備是對 802.1.q 協議的一種內部軟件實現,模擬現實世界中的 802.1.q 交換機。
如圖所示,Linux 裡 802.1.q VLAN 設備是以母子關系成對出現的,母設備相當於現實世界中的交換機 TRUNK 口,用於連接上級網絡,子設備相當於普通接口用於連接下級網絡。當數據在母子設備間傳遞時,內核將會根據 802.1.q VLAN Tag 進行對應操作。母子設備之間是一對多的關系,一個母設備可以有多個子設備,一個子設備只有一個母設備。當一個子設備有一包數據需要發送時,數據將被加入 VLAN Tag 然後從母設備發送出去。當母設備收到一包數據時,它將會分析其中的 VLAN Tag,如果有對應的子設備存在,則把數據轉發到那個子設備上並根據設置移除 VLAN Tag,否則丟棄該數據。在某些設置下,VLAN Tag 可以不被移除以滿足某些監聽程序的需要,如 DHCP 服務程序。舉例說明如下:eth0 作為母設備創建一個 ID 為 100 的子設備 eth0.100。此時如果有程序要求從 eth0.100 發送一包數據,數據將被打上 VLAN 100 的 Tag 從 eth0 發送出去。如果 eth0 收到一包數據,VLAN Tag 是 100,數據將被轉發到 eth0.100 上,並根據設置決定是否移除 VLAN Tag。如果 eth0 收到一包包含 VLAN Tag 101 的數據,其將被丟棄。上述過程隱含以下事實:對於寄主 Linux 系統來說,母設備只能用來收數據,子設備只能用來發送數據。和 Bridge 一樣,母子設備的數據也是有方向的,子設備收到的數據不會進入母設備,同樣母設備上請求發送的數據不會被轉到子設備上。可以把 VLAN 母子設備作為一個整體想象為現實世界中的 802.1.q 交換機,下級接口通過子設備連接到寄主 Linux 系統網絡裡,上級接口同過主設備連接到上級網絡,當母設備是物理網卡時上級網絡是外界真實網絡,當母設備是另外一個 Linux 虛擬網絡設備時上級網絡仍然是寄主 Linux 系統網絡。
需要注意的是母子 VLAN 設備擁有相同的 MAC 地址,可以把它當成現實世界中 802.1.q 交換機的 MAC,因此多個 VLAN 設備會共享一個 MAC。當一個母設備擁有多個 VLAN 子設備時,子設備之間是隔離的,不存在 Bridge 那樣的交換轉發關系,原因如下:802.1.q VLAN 協議的主要目的是從邏輯上隔離子網。現實世界中的 802.1.q 交換機存在多個 VLAN,每個 VLAN 擁有多個端口,同一 VLAN 端口之間可以交換轉發,不同 VLAN 端口之間隔離,所以其包含兩層功能:交換與隔離。Linux VLAN device 實現的是隔離功能,沒有交換功能。一個 VLAN 母設備不可能擁有兩個相同 ID 的 VLAN 子設備,因此也就不可能出現數據交換情況。如果想讓一個 VLAN 裡接多個設備,就需要交換功能。在 Linux 裡 Bridge 專門實現交換功能,因此將 VLAN 子設備 attach 到一個 Bridge 上就能完成後續的交換功能。總結起來,Bridge 加 VLAN device 能在功能層面完整模擬現實世界裡的 802.1.q 交換機。
Linux 支持 VLAN 硬件加速,在安裝有特定硬件情況下,圖中所述內核處理過程可以被放到物理設備上完成。
TUN/TAP 設備是一種讓用戶態程序向內核協議棧注入數據的設備,一個工作在三層,一個工作在二層,使用較多的是 TAP 設備。VETH 設備出現較早,它的作用是反轉通訊數據的方向,需要發送的數據會被轉換成需要收到的數據重新送入內核網絡層進行處理,從而間接的完成數據的注入。
如圖所示,當一個 TAP 設備被創建時,在 Linux 設備文件目錄下將會生成一個對應 char 設備,用戶程序可以像打開普通文件一樣打開這個文件進行讀寫。當執行 write()操作時,數據進入 TAP 設備,此時對於 Linux 網絡層來說,相當於 TAP 設備收到了一包數據,請求內核接受它,如同普通的物理網卡從外界收到一包數據一樣,不同的是其實數據來自 Linux 上的一個用戶程序。Linux 收到此數據後將根據網絡配置進行後續處理,從而完成了用戶程序向 Linux 內核網絡層注入數據的功能。當用戶程序執行 read()請求時,相當於向內核查詢 TAP 設備上是否有需要被發送出去的數據,有的話取出到用戶程序裡,完成 TAP 設備的發送數據功能。針對 TAP 設備的一個形象的比喻是:使用 TAP 設備的應用程序相當於另外一台計算機,TAP 設備是本機的一個網卡,他們之間相互連接。應用程序通過 read()/write()操作,和本機網絡核心進行通訊。
VETH 設備總是成對出現,送到一端請求發送的數據總是從另一端以請求接受的形式出現。該設備不能被用戶程序直接操作,但使用起來比較簡單。創建並配置正確後,向其一端輸入數據,VETH 會改變數據的方向並將其送入內核網絡核心,完成數據的注入。在另一端能讀到此數據。
為了更好的說明 Linux 網絡設備的用法,下面將用一系列的例子,說明在一個復雜的 Linux 網絡元素組合出的虛擬網絡裡,數據的流向。網絡設置簡介如下:一個中心 Bridge:bridge0 下 attach 了 4 個網絡設備,包括 2 個 VETH 設備,1 個 TAP 設備 tap0,1 個物理網卡 eth0。在 VETH 的另外一端又創建了 VLAN 子設備。Linux 上共存在 2 個 VLAN 網絡,既 vlan100 與 vlan200。物理網卡和外部網絡相連,並且在它之下創建了一個 VLAN ID 為 200 的 VLAN 子設備。
如圖所示,當用戶嘗試 ping 192.168.100.3 時,Linux 將會根據路由表,從 vlan100 子設備發出 ARP 報文,具體過程如下:
1) 用戶 ping 192.168.100.3
2) Linux 向 vlan100 子設備發送 ARP 信息。
3) ARP 報文被打上 VLAN ID 100 的 Tag 成為 ARP@vlan100,轉發到母設備上。
4) VETH 設備將這一發送請求轉變方向,成為一個需要接受處理的報文送入內核網絡模塊。
5) 由於對端的 VETH 設備被加入到了 bridge0 上,並且內核發現它收到一個報文,於是報文被轉發到 bridge0 上。
6) bridge0 處理此 ARP@vlan100 信息,根據 TCP/IP 二層協議發現是一個廣播請求,於是向它所知道的所有端口廣播此報文,其中一路進入另一對 VETH 設備的一端,一路進入 TAP 設備 tap0,一路進入物理網卡設備 eth0。此時在 tap0 上,用戶程序可以通過 read()操作讀到 ARP@vlan100,eth0 將會向外界發送 ARP@vlan100,但 eth0 的 VLAN 子設備不會收到它,因為此數據方向為請求發送而不是請求接收。
7) VETH 將請求方向轉換,此時在另一端得到請求接受的 ARP@vlan100 報文。
8) 對端 VETH 設備發現有數據需要接受,並且自己有兩個 VLAN 子設備,於是執行 VLAN 處理邏輯。其中一個子設備是 vlan100,與 ARP@vlan100 吻合,於是去除 VLAN ID 100 的 Tag 轉發到這個子設備上,重新成為標准的以太網 ARP 報文。另一個子設備由於 ID 不吻合,不會得到此報文。
9) 此 VLAN 子設備又被 attach 到另一個橋 bridge1 上,於是轉發自己收到的 ARP 報文。
10) bridge1 廣播 ARP 報文。
11) 最終另外一個 TAP 設備 tap1 收到此請求發送報文,用戶程序通過 read()可以得到它。
和前面情況類似,區別是 VLAN ID 是 200,對端的 vlan200 子設備設置為 reorder_hdr = 0,表示此設備被要求保留收到的報文中的 VLAN Tag。此時子設備會收到 ARP 報文,但是帶了 VLAN ID 200 的 Tag,既 ARP@vlan200。
當 bridge0 擁有 IP 時,通過 Linux 路由表用戶程序可以直接將 ARP 報文發向 bridge0。這時 tap0 和外部網絡都能收到 ARP,但 VLAN 子設備由於 VLAN ID 過濾的原因,將收不到 ARP 信息。
當外部網絡連接在一個支持 VLAN 並且對應端口為 vlan200 時,此情況會發生。此時所有的 VLAN ID 為 200 的 VLAN 子設備都將接受到報文,如果設置 reorder_hdr=0 則會收到帶 Tag 的 ARP@vlan200。
 
給 tap0 賦予 IP 並加入路由,此時再 Ping 其對應網段的未知 IP 會產生 ARP 發送請求。需要注意的是此時由於 tap0 上存在的是發送而不是接收請求,因此 ARP 報文不會被轉發到橋上,從而什麼也不會發生。圖中右邊畫了一個類似情況:從 vlan200 子設備發送 ARP 請求。由於缺少 VETH 設備反轉請求方向,因此報文也不會被轉發到橋上,而是直接通過物理網卡發往外部網絡。
用戶程序指定 tap0 設備發送報文有兩種方式:socket 和 file operation。當用 socket_raw 標志新建 socket 並指定設備編號時,可以要求內核將報文從 tap0 發送。但和前面的 ping from tap0 情況類似,由於報文方向問題,消息並不會被轉發到 bridge0 上。當用 open()方式打開 tap 設備文件時,情況有所不同。當執行 write()操作時,內核認為 tap0 收到了報文,從而會觸發轉發動作,bridge0 將收到它。如果發送的報文如圖所示,是一個以 A 為目的地的攜帶 VLAN ID 100 Tag 的單點報文,bridge0 將會找到對應的設備進行轉發,對應的 VLAN 子設備將收到沒有 VLAN ID 100 Tag 的報文。
以 Redhat6.2 紅帽 Linux 發行版為例,如果已安裝 VLAN 內核模塊和管理工具 vconfig,TAP/TUN 設備管理工具 tunctl,那麼可以用以下命令設置前述網絡設備:
創建 Bridge:brctl addbr [BRIDGE NAME]
刪除 Bridge:brctl delbr [BRIDGE NAME]
attach 設備到 Bridge:brctl addif [BRIDGE NAME] [DEVICE NAME]
從 Bridge detach 設備:brctl delif [BRIDGE NAME] [DEVICE NAME]
查詢 Bridge 情況:brctl show
創建 VLAN 設備:vconfig add [PARENT DEVICE NAME] [VLAN ID]
刪除 VLAN 設備:vconfig rem [VLAN DEVICE NAME]
設置 VLAN 設備 flag:vconfig set_flag [VLAN DEVICE NAME] [FLAG] [VALUE]
設置 VLAN 設備 qos:
vconfig set_egress_map [VLAN DEVICE NAME] [SKB_PRIORITY] [VLAN_QOS]
vconfig set_ingress_map [VLAN DEVICE NAME] [SKB_PRIORITY] [VLAN_QOS]
查詢 VLAN 設備情況:cat /proc/net/vlan/[VLAN DEVICE NAME]
創建 VETH 設備:ip link add link [DEVICE NAME] type veth
創建 TAP 設備:tunctl -p [TAP DEVICE NAME]
刪除 TAP 設備:tunctl -d [TAP DEVICE NAME]
查詢系統裡所有二層設備,包括 VETH/TAP 設備:ip link show
刪除普通二層設備:ip link delete [DEVICE NAME] type [TYPE]
綜上所述,Linux 已經提供一套基本工具供用戶創建出各種內部網絡,利用這些工具可以方便的創建出特定網絡給應用程序使用,包括雲計算中的初級內部虛擬網絡。