雲原生虛擬網絡 tun/tap & veth-pair

雲原生虛擬網絡 tun/tap & veth-pair

轉載請聲明出處哦~,本篇文章發佈於luozhiyun的博客:https://www.luozhiyun.com/archives/684

以前在研究 k8s 網絡的時候,很多東西都看不太懂,只是蜻蜓點水過一下,這段時間打算惡補一下虛擬網絡方面的知識,感興趣的不妨一起探討學習一下。

概述

目前主流的虛擬網卡方案有tun/tapveth兩種。在時間上 tun/tap 出現得更早,在 Linux Kernel 2.4 版之後發佈的內核都會默認編譯 tun/tap 的驅動。並且 tun/tap 應用非常廣泛,其中雲原生虛擬網絡中, flannel 的 UDP 模式中的 flannel0 就是一個 tun 設備,OpenVPN 也利用到了 tun/tap 進行數據的轉發。

veth 是另一種主流的虛擬網卡方案,在 Linux Kernel 2.6 版本,Linux 開始支持網絡名空間隔離的同時,也提供了專門的虛擬以太網(Virtual Ethernet,習慣簡寫做 veth)讓兩個隔離的網絡名稱空間之間可以互相通信。veth 實際上不是一個設備,而是一對設備,因而也常被稱作 Veth-Pair。

Docker 中的 Bridge 模式就是依靠 veth-pair 連接到 docker0 網橋上與宿主機乃至外界的其他機器通信的。

veth

tun/tap

tun 和 tap 是兩個相對獨立的虛擬網絡設備,它們作爲虛擬網卡,除了不具備物理網卡的硬件功能外,它們和物理網卡的功能是一樣的,此外tun/tap負責在內核網絡協議棧和用戶空間之間傳輸數據。

  • tun 設備是一個三層網絡層設備,從 /dev/net/tun 字符設備上讀取的是 IP 數據包,寫入的也只能是 IP 數據包,因此常用於一些點對點IP隧道,例如OpenVPN,IPSec等;
  • tap 設備是二層鏈路層設備,等同於一個以太網設備,從 /dev/tap0 字符設備上讀取 MAC 層數據幀,寫入的也只能是 MAC 層數據幀,因此常用來作爲虛擬機模擬網卡使用;

tap&tun

從上面圖中,我們可以看出物理網卡和 tun/tap 設備模擬的虛擬網卡的區別,雖然它們的一端都是連着網絡協議棧,但是物理網卡另一端連接的是物理網絡,而 tun/tap 設備另一端連接的是一個文件作爲傳輸通道。

根據前面的介紹,我們大約知道虛擬網卡主要有兩個功能,一個是連接其它設備(虛擬網卡或物理網卡)和 Bridge 這是 tap 設備的作用;另一個是提供用戶空間程序去收發虛擬網卡上的數據,這是 tun 設備的作用。

主要區別是因爲它們作用在不同的網絡協議層,換句話說 tap設備是一個二層設備所以通常接入到 Bridge上作爲局域網的一個節點,tun設備是一個三層設備通常用來實現 vpn。

OpenVPN 使用 tun 設備收發數據

OpenVPN 是使用 tun 設備的常見例子,它可以方便的在不同網絡訪問場所之間搭建類似於局域網的專用網絡通道。其核心機制就是在 OpenVPN 服務器和客戶端所在的計算機上都安裝一個 tun 設備,通過其虛擬 IP 實現相互訪問。

例如公網上的兩個主機節點A、B,物理網卡上配置的IP分別是 ipA_eth0 和 ipB_eth0。然後在A、B兩個節點上分別運行 openvpn 的客戶端和服務端,它們會在自己的節點上創建 tun 設備,且都會讀取或寫入這個 tun 設備。

假設這兩個設備對應的虛擬 IP 是 ipA_tun0 和 ipB_tun0,那麼節點 B 上面的應用程序想要通過虛擬 IP 對節點 A 通信,那麼數據包流向就是:

tap&tun2

用戶進程對 ipA_tun0 發起請求,經過路由決策後內核將數據從網絡協議棧寫入 tun0 設備;然後 OpenVPN 從字符設備文件中讀取 tun0 設備數據,將數據請求發出去;內核網絡協議棧根據路由決策將數據從本機的 eth0 接口流出發往 ipA_eth0 。

同樣我們來看看節點 A 是如何接受數據:

tap&tun3

當節點A 通過物理網卡 eth0 接受到數據後會將寫入內核網絡協議棧,因爲目標端口號是OpenVPN程序所監聽的,所以網絡協議棧會將數據交給 OpenVPN ;

OpenVPN 程序得到數據之後,發現目標IP是tun0設備的,於是將數據從用戶空間寫入到字符設備文件中,然後 tun0 設備會將數據寫入到協議棧中,網絡協議棧最終將數據轉發給應用進程。

從上面我們知道使用 tun/tap 設備傳輸數據需要經過兩次協議棧,不可避免地會有一定的性能損耗,如果條件允許,容器對容器的直接通信並不會把 tun/tap 作爲首選方案,一般是基於稍後介紹的 veth 來實現的。但是 tun/tap 沒有 veth 那樣要求設備成對出現、數據要原樣傳輸的限制,數據包到用戶態程序後,程序員就有完全掌控的權力,要進行哪些修改,要發送到什麼地方,都可以編寫代碼去實現,因此 tun/tap 方案比起 veth 方案有更廣泛的適用範圍。

flannel UDP 模式使用 tun 設備收發數據

早期 flannel 利用 tun 設備實現了 UDP 模式下的跨主網絡相互訪問,實際上原理和上面的 OpenVPN 是差不多的。

在 flannel 中 flannel0 是一個三層的 tun 設備,用作在操作系統內核和用戶應用程序之間傳遞 IP 包。當操作系統將一個 IP 包發送給 flannel0 設備之後,flannel0 就會把這個 IP 包,交給創建這個設備的應用程序,也就是 flanneld 進程,flanneld 進程是一個 UDP 進程,負責處理 flannel0 發送過來的數據包:

tap&tun5

flanneld 進程會根據目的 IP 的地址匹配到對應的子網,從 Etcd 中找到這個子網對應的宿主機 Node2 的 IP 地址,然後將這個數據包直接封裝在 UDP 包裏面,然後發送給 Node 2。由於每臺宿主機上的 flanneld 都監聽着一個 8285 端口,所以 Node2 機器上 flanneld 進程會從 8285 端口獲取到傳過來的數據,解析出封裝在裏面的發給 ContainerA 的 IP 地址。

flanneld 會直接把這個 IP 包發送給它所管理的 TUN 設備,即 flannel0 設備。然後網絡棧會將這個數據包根據路由發送到 docker0 網橋,docker0 網橋會扮演二層交換機的角色,將數據包發送給正確的端口,進而通過 veth pair 設備進入到 containerA 的 Network Namespace 裏。

上面所講的 Flannel UDP 模式現在已經廢棄,原因就是因爲它經過三次用戶態與內核態之間的數據拷貝。容器發送數據包經過 docker0 網橋進入內核態一次;數據包由 flannel0 設備進入到 flanneld 進程又一次;第三次是 flanneld 進行 UDP 封包之後重新進入內核態,將 UDP 包通過宿主機的 eth0 發出去。

tap 設備作爲虛擬機網卡

上面我們也說了,tap 設備是一個二層鏈路層設備,通常用作實現虛擬網卡。以 qemu-kvm 爲例,它利用 tap 設備和 Bridge 配合使用擁有極大的靈活性,可以實現各種各樣的網絡拓撲。

veth

在 qume-kvm 開啓 tap 模式之後,在啓動的時候會向內核註冊了一個tap類型虛擬網卡 tapx,x 代表依次遞增的數字; 這個虛擬網卡 tapx 是綁定在 Bridge 上面的,是它上面的一個接口,最終數據會通過 Bridge 來進行轉發。

qume-kvm 會通過其網卡 eth0 向外發送數據,從 Host 角度看就是用戶層程序 qume-kvm 進程將字符設備寫入數據;然後 tapx 設備會收到數據後由 Bridge 決定數據包如何轉發。如果 qume-kvm 要和外界通信,那麼數據包會被髮送到物理網卡,最終實現與外部通信。

從上面的圖也可以看出 qume-kvm 發出的數據包通過 tap 設備先到達 Bridge ,然後到物理網絡中,數據包不需要經過主機的的協議棧,這樣效率也比較高。

veth-pair

veth-pair 就是一對的虛擬設備接口,它是成對出現的,一端連着協議棧,一端彼此相連着,在 veth 設備的其中一端輸入數據,這些數據就會從設備的另外一端原樣不變地流出:

veth2

利用它可以連接各種虛擬設備,兩個 namespace 設備之間的連接就可以通過 veth-pair 來傳輸數據。

下面我們做一下實驗,構造一個 ns1 和 ns2 利用 veth 通信的過程,看看veth是如何收發請求包的。

# 創建兩個namespace
ip netns add ns1
ip netns add ns2

# 通過ip link命令添加vethDemo0和vethDemo1
ip link add vethDemo0 type veth peer name vethDemo1

# 將 vethDemo0 vethDemo1 分別加入兩個 ns
ip link set vethDemo0 netns ns1
ip link set vethDemo1 netns ns2

# 給兩個 vethDemo0 vethDemo1  配上 IP 並啓用
ip netns exec ns1 ip addr add 10.1.1.2/24 dev vethDemo0
ip netns exec ns1 ip link set vethDemo0 up

ip netns exec ns2 ip addr add 10.1.1.3/24 dev vethDemo1
ip netns exec ns2 ip link set vethDemo1 up

然後我們可以看到 namespace 裏面設置好了各自的虛擬網卡以及對應的ip:

~ # ip netns exec ns1 ip addr    

root@VM_243_186_centos
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
7: vethDemo0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether d2:3f:ea:b1:be:57 brd ff:ff:ff:ff:ff:ff
    inet 10.1.1.2/24 scope global vethDemo0
       valid_lft forever preferred_lft forever
    inet6 fe80::d03f:eaff:feb1:be57/64 scope link
       valid_lft forever preferred_lft forever

然後我們 ping vethDemo1 設備的 ip:

ip netns exec ns1 ping 10.1.1.3

對兩個網卡進行抓包:

~ # ip netns exec ns1 tcpdump -n -i vethDemo0                                           root@VM_243_186_centos
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vethDemo0, link-type EN10MB (Ethernet), capture size 262144 bytes
20:19:14.339853 ARP, Request who-has 10.1.1.3 tell 10.1.1.2, length 28
20:19:14.339877 ARP, Reply 10.1.1.3 is-at 0e:2f:e6:ce:4b:36, length 28
20:19:14.339880 IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 27402, seq 1, length 64
20:19:14.339894 IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 27402, seq 1, length 64

~ # ip netns exec ns2 tcpdump -n -i vethDemo1                                           root@VM_243_186_centos
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vethDemo1, link-type EN10MB (Ethernet), capture size 262144 bytes
20:19:14.339862 ARP, Request who-has 10.1.1.3 tell 10.1.1.2, length 28
20:19:14.339877 ARP, Reply 10.1.1.3 is-at 0e:2f:e6:ce:4b:36, length 28
20:19:14.339881 IP 10.1.1.2 > 10.1.1.3: ICMP echo request, id 27402, seq 1, length 64
20:19:14.339893 IP 10.1.1.3 > 10.1.1.2: ICMP echo reply, id 27402, seq 1, length 64

通過上面的抓包,並結合ping相關概念,我們大致可以瞭解到:

  1. ping進程構造ICMP echo請求包,並通過socket發給協議棧;
  2. 協議棧根據目的IP地址和系統路由表,知道去 10.1.1.3 的數據包應該要由 10.1.1.2 口出去;
  3. 由於是第一次訪問 10.1.1.3,剛開始沒有它的 mac 地址,所以協議棧會先發送 ARP 出去,詢問 10.1.1.3 的 mac 地址;
  4. 協議棧將 ARP 包交給 vethDemo0,讓它發出去;
  5. 由於 vethDemo0 的另一端連的是 vethDemo1,所以ARP請求包就轉發給了 vethDemo1;
  6. vethDemo1 收到 ARP 包後,轉交給另一端的協議棧,做出 ARP 應答,迴應告訴 mac 地址 ;
  7. 當拿到10.1.1.3 的 mac 地址之後,再發出 ping 請求會構造一個ICMP request 發送給目的地,然後ping命令回顯成功;

總結

本篇文章只是講了兩種常見的虛擬網絡設備。起因是在看 flannel 的時候,書裏面都會講到 flannel0 是一個 tun 設備,但是什麼是 tun 設備卻不明白,所以導致 flannel 也看的不明白。

經過研究,發現 tun/tap 設備是一個虛擬網絡設備,負責數據轉發,但是它需要通過文件作爲傳輸通道,這樣不可避免的引申出 tun/tap 設備爲什麼要轉發兩次,這也是爲什麼 flannel 設備 UDP 模式下性能不好的原因,導致了後面這種模式被廢棄掉。

因爲 tun/tap 設備作爲虛擬網絡設備性能不好,容器對容器的直接通信並不會把 tun/tap 作爲首選方案,一般是基於稍後介紹的 veth 來實現的。veth 作爲一個二層設備,可以讓兩個隔離的網絡名稱空間之間可以互相通信,不需要反覆多次經過網絡協議棧, veth pair 是一端連着協議棧,另一端彼此相連的,數據之間的傳輸變得十分簡單,這也讓 veth 比起 tap/tun 具有更好的性能。

Reference

https://zhuanlan.zhihu.com/p/293659939

https://segmentfault.com/a/1190000009249039

https://segmentfault.com/a/1190000009251098

https://www.junmajinlong.com/virtual/network/all_about_tun_tap/

https://www.junmajinlong.com/virtual/network/data_flow_about_openvpn/

https://www.zhaohuabing.com/post/2020-02-24-linux-taptun/

https://zhuanlan.zhihu.com/p/462501573

http://icyfenix.cn/immutable-infrastructure/network/linux-vnet.html

https://opengers.github.io/openstack/openstack-base-virtual-network-devices-tuntap-veth/

https://tomwei7.com/2021/10/09/qemu-network-config/

https://time.geekbang.org/column/article/65287

https://blog.csdn.net/qq_41586875/article/details/119943074

https://man7.org/linux/man-pages/man4/veth.4.html

掃碼_搜索聯合傳播樣式-白色版 1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章