本文首發於我的公眾號 Linux雲計算網絡(id: cloud_dev),專注於干貨分享,號內有 10T 書籍和視頻資源,后台回復「1024」即可領取,歡迎大家關注,二維碼文末可以掃。
前面這篇文章介紹了 tap/tun 設備之后,大家應該對虛擬網絡設備有了一定的了解,本文來看另外一種虛擬網絡設備 veth-pair。
01 veth-pair 是什么
顧名思義,veth-pair 就是一對的虛擬設備接口,和 tap/tun 設備不同的是,它都是成對出現的。一端連着協議棧,一端彼此相連着。如下圖所示:

正因為有這個特性,它常常充當着一個橋梁,連接着各種虛擬網絡設備,典型的例子像“兩個 namespace 之間的連接”,“Bridge、OVS 之間的連接”,“Docker 容器之間的連接” 等等,以此構建出非常復雜的虛擬網絡結構,比如 OpenStack Neutron。
02 veth-pair 的連通性
我們給上圖中的 veth0 和 veth1 分別配上 IP:10.1.1.2 和 10.1.1.3,然后從 veth0 ping 一下 veth1。理論上它們處於同網段,是能 ping 通的,但結果卻是 ping 不通。
抓個包看看,tcpdump -nnt -i veth0
root@ubuntu:~# tcpdump -nnt -i veth0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
ARP, Request who-has 10.1.1.3 tell 10.1.1.2, length 28
ARP, Request who-has 10.1.1.3 tell 10.1.1.2, length 28
可以看到,由於 veth0 和 veth1 處於同一個網段,且是第一次連接,所以會事先發 ARP 包,但 veth1 並沒有響應 ARP 包。
經查閱,這是由於我使用的 Ubuntu 系統內核中一些 ARP 相關的默認配置限制所導致的,需要修改一下配置項:
echo 1 > /proc/sys/net/ipv4/conf/veth1/accept_local
echo 1 > /proc/sys/net/ipv4/conf/veth0/accept_local
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/veth0/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/veth1/rp_filter
完了再 ping 就行了。
root@ubuntu:~# ping -I veth0 10.1.1.3 -c 2
PING 10.1.1.3 (10.1.1.3) from 10.1.1.2 veth0: 56(84) bytes of data.
64 bytes from 10.1.1.3: icmp_seq=1 ttl=64 time=0.047 ms
64 bytes from 10.1.1.3: icmp_seq=2 ttl=64 time=0.064 ms
--- 10.1.1.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 3008ms
rtt min/avg/max/mdev = 0.047/0.072/0.113/0.025 ms
我們對這個通信過程比較感興趣,可以抓包看看。
對於 veth0 口:
root@ubuntu:~# tcpdump -nnt -i veth0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth0, link-type EN10MB (Ethernet), capture size 262144 bytes
ARP, Request who-has 10.1.1.3 tell 10.1.1.2, length 28
ARP, Reply 10.1.1.3 is-at 5a:07:76:8e:fb:cd, length 28
IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 2189, seq 1, length 64
IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 2189, seq 2, length 64
IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 2189, seq 3, length 64
IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 2244, seq 1, length 64
對於 veth1 口:
root@ubuntu:~# tcpdump -nnt -i veth1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on veth1, link-type EN10MB (Ethernet), capture size 262144 bytes
ARP, Request who-has 10.1.1.3 tell 10.1.1.2, length 28
ARP, Reply 10.1.1.3 is-at 5a:07:76:8e:fb:cd, length 28
IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 2189, seq 1, length 64
IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 2189, seq 2, length 64
IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 2189, seq 3, length 64
IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 2244, seq 1, length 64
奇怪,我們並沒有看到 ICMP 的 echo reply 包,那它是怎么 ping 通的?
其實這里 echo reply 走的是 localback 口,不信抓個包看看:
root@ubuntu:~# tcpdump -nnt -i lo
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 2244, seq 1, length 64
IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 2244, seq 2, length 64
IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 2244, seq 3, length 64
IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 2244, seq 4, length 64
為什么?
我們看下整個通信流程就明白了。
- 首先 ping 程序構造 ICMP
echo request,通過 socket 發給協議棧。 - 由於 ping 指定了走 veth0 口,如果是第一次,則需要發 ARP 請求,否則協議棧直接將數據包交給 veth0。
- 由於 veth0 連着 veth1,所以 ICMP request 直接發給 veth1。
- veth1 收到請求后,交給另一端的協議棧。
- 協議棧看本地有 10.1.1.3 這個 IP,於是構造 ICMP reply 包,查看路由表,發現回給 10.1.1.0 網段的數據包應該走 localback 口,於是將 reply 包交給 lo 口(會優先查看路由表的 0 號表,
ip route show table 0查看)。 - lo 收到協議棧的 reply 包后,啥都沒干,轉手又回給協議棧。
- 協議棧收到 reply 包之后,發現有 socket 在等待包,於是將包給 socket。
- 等待在用戶態的 ping 程序發現 socket 返回,於是就收到 ICMP 的 reply 包。
整個過程如下圖所示:

03 兩個 namespace 之間的連通性
namespace 是 Linux 2.6.x 內核版本之后支持的特性,主要用於資源的隔離。有了 namespace,一個 Linux 系統就可以抽象出多個網絡子系統,各子系統間都有自己的網絡設備,協議棧等,彼此之間互不影響。
如果各個 namespace 之間需要通信,怎么辦呢,答案就是用 veth-pair 來做橋梁。
根據連接的方式和規模,可以分為“直接相連”,“通過 Bridge 相連” 和 “通過 OVS 相連”。
3.1 直接相連
直接相連是最簡單的方式,如下圖,一對 veth-pair 直接將兩個 namespace 連接在一起。

給 veth-pair 配置 IP,測試連通性:
# 創建 namespace
ip netns a ns1
ip netns a ns2
# 創建一對 veth-pair veth0 veth1
ip l a veth0 type veth peer name veth1
# 將 veth0 veth1 分別加入兩個 ns
ip l s veth0 netns ns1
ip l s veth1 netns ns2
# 給兩個 veth0 veth1 配上 IP 並啟用
ip netns exec ns1 ip a a 10.1.1.2/24 dev veth0
ip netns exec ns1 ip l s veth0 up
ip netns exec ns2 ip a a 10.1.1.3/24 dev veth1
ip netns exec ns2 ip l s veth1 up
# 從 veth0 ping veth1
[root@localhost ~]# ip netns exec ns1 ping 10.1.1.3
PING 10.1.1.3 (10.1.1.3) 56(84) bytes of data.
64 bytes from 10.1.1.3: icmp_seq=1 ttl=64 time=0.073 ms
64 bytes from 10.1.1.3: icmp_seq=2 ttl=64 time=0.068 ms
--- 10.1.1.3 ping statistics ---
15 packets transmitted, 15 received, 0% packet loss, time 14000ms
rtt min/avg/max/mdev = 0.068/0.084/0.201/0.032 ms
3.2 通過 Bridge 相連
Linux Bridge 相當於一台交換機,可以中轉兩個 namespace 的流量,我們看看 veth-pair 在其中扮演什么角色。
如下圖,兩對 veth-pair 分別將兩個 namespace 連到 Bridge 上。

同樣給 veth-pair 配置 IP,測試其連通性:
# 首先創建 bridge br0
ip l a br0 type bridge
ip l s br0 up
# 然后創建兩對 veth-pair
ip l a veth0 type veth peer name br-veth0
ip l a veth1 type veth peer name br-veth1
# 分別將兩對 veth-pair 加入兩個 ns 和 br0
ip l s veth0 netns ns1
ip l s br-veth0 master br0
ip l s br-veth0 up
ip l s veth1 netns ns2
ip l s br-veth1 master br0
ip l s br-veth1 up
# 給兩個 ns 中的 veth 配置 IP 並啟用
ip netns exec ns1 ip a a 10.1.1.2/24 dev veth0
ip netns exec ns1 ip l s veth0 up
ip netns exec ns2 ip a a 10.1.1.3/24 dev veth1
ip netns exec ns2 ip l s veth1 up
# veth0 ping veth1
[root@localhost ~]# ip netns exec ns1 ping 10.1.1.3
PING 10.1.1.3 (10.1.1.3) 56(84) bytes of data.
64 bytes from 10.1.1.3: icmp_seq=1 ttl=64 time=0.060 ms
64 bytes from 10.1.1.3: icmp_seq=2 ttl=64 time=0.105 ms
--- 10.1.1.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.060/0.082/0.105/0.024 ms
3.3 通過 OVS 相連
OVS 是第三方開源的 Bridge,功能比 Linux Bridge 要更強大,對於同樣的實驗,我們用 OVS 來看看是什么效果。
如下圖所示:

同樣測試兩個 namespace 之間的連通性:
# 用 ovs 提供的命令創建一個 ovs bridge
ovs-vsctl add-br ovs-br
# 創建兩對 veth-pair
ip l a veth0 type veth peer name ovs-veth0
ip l a veth1 type veth peer name ovs-veth1
# 將 veth-pair 兩端分別加入到 ns 和 ovs bridge 中
ip l s veth0 netns ns1
ovs-vsctl add-port ovs-br ovs-veth0
ip l s ovs-veth0 up
ip l s veth1 netns ns2
ovs-vsctl add-port ovs-br ovs-veth1
ip l s ovs-veth1 up
# 給 ns 中的 veth 配置 IP 並啟用
ip netns exec ns1 ip a a 10.1.1.2/24 dev veth0
ip netns exec ns1 ip l s veth0 up
ip netns exec ns2 ip a a 10.1.1.3/24 dev veth1
ip netns exec ns2 ip l s veth1 up
# veth0 ping veth1
[root@localhost ~]# ip netns exec ns1 ping 10.1.1.3
PING 10.1.1.3 (10.1.1.3) 56(84) bytes of data.
64 bytes from 10.1.1.3: icmp_seq=1 ttl=64 time=0.311 ms
64 bytes from 10.1.1.3: icmp_seq=2 ttl=64 time=0.087 ms
^C
--- 10.1.1.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.087/0.199/0.311/0.112 ms
總結
veth-pair 在虛擬網絡中充當着橋梁的角色,連接多種網絡設備構成復雜的網絡。
veth-pair 的三個經典實驗,直接相連、通過 Bridge 相連和通過 OVS 相連。
參考
http://www.opencloudblog.com/?p=66
https://segmentfault.com/a/1190000009251098
我的公眾號 「Linux雲計算網絡」(id: cloud_dev) ,號內有 10T 書籍和視頻資源,后台回復 「1024」 即可領取,分享的內容包括但不限於 Linux、網絡、雲計算虛擬化、容器Docker、OpenStack、Kubernetes、工具、SDN、OVS、DPDK、Go、Python、C/C++編程技術等內容,歡迎大家關注。

