輕松理解 Docker 網(wǎng)絡(luò)虛擬化基礎(chǔ)之 veth 設(shè)備!
最近對網(wǎng)絡(luò)虛擬化技術(shù)產(chǎn)生了濃厚的興趣。迫切想搞明白在 Docker 等虛擬技術(shù)下,網(wǎng)絡(luò)底層是如何運行的。
不得不說,網(wǎng)絡(luò)虛擬化技術(shù)是我給自己拋的又一個大坑。雖然我自認(rèn)為把原生 Linux 網(wǎng)絡(luò)實現(xiàn)過程理解的還算不錯了。但在看網(wǎng)絡(luò)虛擬化相關(guān)的技術(shù)的時候,還是覺得不是很容易。
今天我給大家?guī)淼木褪?Docker 網(wǎng)絡(luò)虛擬化中的一個比較好理解的技術(shù) - veth。
回想下在物理機(jī)組成的網(wǎng)絡(luò)里,最基礎(chǔ),最簡單的網(wǎng)絡(luò)連接方式是什么?沒錯,那就是直接用一根交叉網(wǎng)線把兩臺電腦的網(wǎng)卡連起來。這樣,一臺機(jī)器發(fā)送數(shù)據(jù),另外一臺就能收到了。

那么,網(wǎng)絡(luò)虛擬化實現(xiàn)的第一步,就是用軟件來模擬這個簡單的網(wǎng)絡(luò)連接實現(xiàn)過程。實現(xiàn)的技術(shù)就是我們今天的主角 veth,它模擬了在物理世界里的兩塊網(wǎng)卡,以及一條網(wǎng)線。通過它可以將兩個虛擬的設(shè)備連接起來,讓他們之間相互通信。平時工作中在 Docker 鏡像里我們看到的 eth0 設(shè)備,其實就是 veth。

事實上,這種軟件模擬硬件方式我們一點兒也不陌生,我們本機(jī)網(wǎng)絡(luò) IO 里的 lo 回環(huán)設(shè)備也是這樣一個用軟件虛擬出來設(shè)備。Veth 和 lo 的一點區(qū)別就是 veth 總是成雙成對地出現(xiàn)。
我們今天就來深入地看看 veth 這個東東是咋工作的。
一、veth 如何使用
不像回環(huán)設(shè)備,絕大多數(shù)同學(xué)在日常工作中可能都沒接觸過 veth。所以本文咱們專門拉一小節(jié)出來介紹 veth 是如何使用的。
在 Linux 下,我們可以通過使用 ip 命令創(chuàng)建一對兒 veth。其中 link 表示 link layer的意思,即鏈路層。這個命令可以用于管理和查看網(wǎng)絡(luò)接口,包括物理網(wǎng)絡(luò)接口,也包括虛擬接口。
使用 ip link show 來進(jìn)行查看。
和 eth0、lo 等網(wǎng)絡(luò)設(shè)備一樣,veth 也需要為其配置上 ip 后才能夠正常工作。我們?yōu)檫@對兒 veth 分別來配置上 IP。
接下來,我們把這兩個設(shè)備啟動起來。
當(dāng)設(shè)備啟動起來以后,我們通過我們熟悉的 ifconfig 就可以查看到它們了。
現(xiàn)在,一對兒虛擬設(shè)備已經(jīng)建立起來了。不過我們需要做一點準(zhǔn)備工作,它們之間才可以進(jìn)行互相通信。首先要關(guān)閉反向過濾 rp_filter,該模塊會檢查 IP 包是否符合要求,否則可能會過濾掉。然后再打開 accept_local,接收本機(jī) IP 數(shù)據(jù)包。詳細(xì)準(zhǔn)備過程如下:
好了,我們在 veth0 上來 ping 一下 veth1。這兩個 veth 之間可以通信了,歐耶!
我在另外一個控制臺上,還啟動了 tcpdump 抓包,抓到的結(jié)果如下。
由于兩個設(shè)備之間是首次通信的,所以 veth0 首先先發(fā)出了一個 arp request,veth1 收到后回復(fù)了一個 arp reply。然后接下來就是正常的 ping 命令下的 IP 包了。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ?


二、veth 底層創(chuàng)建過程
在上一小節(jié)中,我們親手創(chuàng)建了一對兒 veth 設(shè)備,并通過簡單的配置就可以讓他們之間互相進(jìn)行通信了。那么在本小節(jié)中,我們看看在內(nèi)核里,veth 到底是如何創(chuàng)建的。
Veth 相關(guān)源碼位于 drivers/net/veth.c,其中初始化入口是 veth_init。
在 veth_init 中注冊了 veth_link_ops(veth 設(shè)備的操作方法),它包含了 veth 設(shè)備的創(chuàng)建、啟動和刪除等回調(diào)函數(shù)。
我們先來看下 veth 設(shè)備的創(chuàng)建函數(shù) veth_newlink,這是理解 veth 的關(guān)鍵之處。
在 veth_newlink 中,我們看到它通過 register_netdevice 創(chuàng)建了 peer 和 dev 兩個網(wǎng)絡(luò)虛擬設(shè)備。接下來的 netdev_priv 函數(shù)返回的是網(wǎng)絡(luò)設(shè)備的 private 數(shù)據(jù),priv->peer 就是一個指針而已。
兩個新創(chuàng)建出來的設(shè)備 dev 和 peer 通過 priv->peer 指針來完成結(jié)對。其中 dev 設(shè)備里的 priv->peer 指針指向 peer 設(shè)備, peer 設(shè)備里的 priv->peer 指向 dev。
接著我們再看下 veth 設(shè)備的啟動過程。
其中 dev->netdev_ops = &veth_netdev_ops 這行也比較關(guān)鍵。veth_netdev_ops 是 veth 設(shè)備的操作函數(shù)。例如發(fā)送過程中調(diào)用的函數(shù)指針 ndo_start_xmit,對于 veth 設(shè)備來說就會調(diào)用到 veth_xmit。這個在下一個小節(jié)里我們會用到。
三、veth 網(wǎng)絡(luò)通信過程
我們回顧一下基于回環(huán)設(shè)備 lo 的本機(jī)網(wǎng)絡(luò)過程。在發(fā)送階段里,流程分別是 send 系統(tǒng)調(diào)用 => 協(xié)議棧 => 鄰居子系統(tǒng) => 網(wǎng)絡(luò)設(shè)備層 => 驅(qū)動。在接收階段里,流程分別是軟中斷 => 驅(qū)動 => 網(wǎng)絡(luò)設(shè)備層 => 協(xié)議棧 => 系統(tǒng)調(diào)用返回。過程圖示如下:

基于 veth 的網(wǎng)絡(luò) IO 過程和上面這個過程圖幾乎完全一樣。和 lo 設(shè)備所不同的就是使用的驅(qū)動程序不一樣,馬上我們就能看到。
網(wǎng)絡(luò)設(shè)備層最后會通過 ops->ndo_start_xmit 來調(diào)用驅(qū)動進(jìn)行真正的發(fā)送。
我們提到過對于回環(huán)設(shè)備 lo 來說 netdev_ops 是 loopback_ops。那么 ops->ndo_start_xmit 對應(yīng)的就是 loopback_xmit。
回顧本文上一小節(jié)中,對于 veth 設(shè)備來說,它在啟動的時候?qū)?netdev_ops 設(shè)置成了 veth_netdev_ops。那 ops->ndo_start_xmit 對應(yīng)的具體發(fā)送函數(shù)就是 veth_xmit。這就是在整個發(fā)送的過程中,唯一和 lo 設(shè)備不同的地方所在。我們來簡單看一下這個發(fā)送函數(shù)的代碼。
在 veth_xmit 中主要就是獲取一下當(dāng)前 veth 設(shè)備,然后向?qū)Χ税褦?shù)據(jù)發(fā)送過去就行了。發(fā)送到對端設(shè)備的工作是由 dev_forward_skb 函數(shù)來處理的。
先調(diào)用了 eth_type_trans 將 skb 的所屬設(shè)備改為了剛剛?cè)〉降?veth 的對端設(shè)備 rcv。
接著調(diào)用 netif_rx,這塊又和 lo 設(shè)備的操作一樣了。在該方法中最終會執(zhí)行到 enqueue_to_backlog 中(netif_rx -> netif_rx_internal -> enqueue_to_backlog)。在這里將要發(fā)送的 skb 插入 softnet_data->input_pkt_queue 隊列中并調(diào)用 ____napi_schedule 來觸發(fā)軟中斷,見下面的代碼。
當(dāng)數(shù)據(jù)發(fā)送完喚起軟中斷后,veth 對端的設(shè)備開始接收。和發(fā)送過程不同的是,所有的虛擬設(shè)備的收包 poll 函數(shù)都是一樣的,都是在設(shè)備層被初始化成了 process_backlog。
所以 veth 設(shè)備的接收過程和 lo 設(shè)備完全一樣。大致流程是 net_rx_action 執(zhí)行到 deliver_skb,然后送到協(xié)議棧中。
總結(jié)
由于大部分的同學(xué)在日常工作中一般不會接觸到 veth,所以在看到 Docker 相關(guān)的技術(shù)文中提到這個技術(shù)時總會以為它是多么的高深。
其實從實現(xiàn)上來看,虛擬設(shè)備 veth 和我們?nèi)粘=佑|的 lo 設(shè)備非常非常的像。連基于 veth 的本機(jī)網(wǎng)絡(luò) IO 通信圖其實都是我直接從 127.0.0.1 的那篇文章里復(fù)制過來的。理解起來 veth 簡直不要太容易。

只不過和 lo 設(shè)備相比,veth 是為了虛擬化技術(shù)而生的,所以它多了個結(jié)對的概念。在創(chuàng)建函數(shù) veth_newlink 中,一次性就創(chuàng)建了兩個網(wǎng)絡(luò)設(shè)備出來,并把對方分別設(shè)置成了各自的 peer。在發(fā)送數(shù)據(jù)的過程中,找到發(fā)送設(shè)備的 peer,然后發(fā)起軟中斷讓對方收取就算完事了。
怎么樣,是不是 So easy!
原文作者:開發(fā)內(nèi)功修煉
