在現在的雲時代,到處都是虛擬機和容器,它們背后的網絡管理都離不開虛擬網絡設備,所以了解虛擬網絡設備有利於我們更好的理解雲時代的網絡結構。從本篇開始,將介紹Linux下的虛擬網絡設備。
虛擬設備和物理設備的區別
在Linux網絡數據包的接收過程和數據包的發送過程這兩篇文章中,介紹了數據包的收發流程,知道了Linux內核中有一個網絡設備管理層,處於網絡設備驅動和協議棧之間,負責銜接它們之間的數據交互。驅動不需要了解協議棧的細節,協議棧也不需要了解設備驅動的細節。
對於一個網絡設備來說,就像一個管道(pipe)一樣,有兩端,從其中任意一端收到的數據將從另一端發送出去。
比如一個物理網卡eth0,它的兩端分別是內核協議棧(通過內核網絡設備管理模塊間接的通信)和外面的物理網絡,從物理網絡收到的數據,會轉發給內核協議棧,而應用程序從協議棧發過來的數據將會通過物理網絡發送出去。
那么對於一個虛擬網絡設備呢?首先它也歸內核的網絡設備管理子系統管理,對於Linux內核網絡設備管理模塊來說,虛擬設備和物理設備沒有區別,都是網絡設備,都能配置IP,從網絡設備來的數據,都會轉發給協議棧,協議棧過來的數據,也會交由網絡設備發送出去,至於是怎么發送出去的,發到哪里去,那是設備驅動的事情,跟Linux內核就沒關系了,所以說虛擬網絡設備的一端也是協議棧,而另一端是什么取決於虛擬網絡設備的驅動實現。
tun/tap的另一端是什么?
先看圖再說話:
+----------------------------------------------------------------+ | | | +--------------------+ +--------------------+ | | | User Application A | | User Application B |<-----+ | | +--------------------+ +--------------------+ | | | | 1 | 5 | | |...............|......................|...................|.....| | ↓ ↓ | | | +----------+ +----------+ | | | | socket A | | socket B | | | | +----------+ +----------+ | | | | 2 | 6 | | |.................|.................|......................|.....| | ↓ ↓ | | | +------------------------+ 4 | | | | Newwork Protocol Stack | | | | +------------------------+ | | | | 7 | 3 | | |................|...................|.....................|.....| | ↓ ↓ | | | +----------------+ +----------------+ | | | | eth0 | | tun0 | | | | +----------------+ +----------------+ | | | 10.32.0.11 | | 192.168.3.11 | | | | 8 +---------------------+ | | | | +----------------|-----------------------------------------------+ ↓ Physical Network
上圖中有兩個應用程序A和B,都在用戶層,而其它的socket、協議棧(Newwork Protocol Stack)和網絡設備(eth0和tun0)部分都在內核層,其實socket是協議棧的一部分,這里分開來的目的是為了看的更直觀。
tun0是一個Tun/Tap虛擬設備,從上圖中可以看出它和物理設備eth0的差別,它們的一端雖然都連着協議棧,但另一端不一樣,eth0的另一端是物理網絡,這個物理網絡可能就是一個交換機,而tun0的另一端是一個用戶層的程序,協議棧發給tun0的數據包能被這個應用程序讀取到,並且應用程序能直接向tun0寫數據。
這里假設eth0配置的IP是10.32.0.11,而tun0配置的IP是192.168.3.11.
這里列舉的是一個典型的tun/tap設備的應用場景,發到192.168.3.0/24網絡的數據通過程序B這個隧道,利用10.32.0.11發到遠端網絡的10.33.0.1,再由10.33.0.1轉發給相應的設備,從而實現VPN。
下面來看看數據包的流程:
-
應用程序A是一個普通的程序,通過socket A發送了一個數據包,假設這個數據包的目的IP地址是192.168.3.1
-
socket將這個數據包丟給協議棧
-
協議棧根據數據包的目的IP地址,匹配本地路由規則,知道這個數據包應該由tun0出去,於是將數據包交給tun0
-
tun0收到數據包之后,發現另一端被進程B打開了,於是將數據包丟給了進程B
-
進程B收到數據包之后,做一些跟業務相關的處理,然后構造一個新的數據包,將原來的數據包嵌入在新的數據包中,最后通過socket B將數據包轉發出去,這時候新數據包的源地址變成了eth0的地址,而目的IP地址變成了一個其它的地址,比如是10.33.0.1.
-
socket B將數據包丟給協議棧
-
協議棧根據本地路由,發現這個數據包應該要通過eth0發送出去,於是將數據包交給eth0
-
eth0通過物理網絡將數據包發送出去
10.33.0.1收到數據包之后,會打開數據包,讀取里面的原始數據包,並轉發給本地的192.168.3.1,然后等收到192.168.3.1的應答后,再構造新的應答包,並將原始應答包封裝在里面,再由原路徑返回給應用程序B,應用程序B取出里面的原始應答包,最后返回給應用程序A
這里不討論Tun/Tap設備tun0是怎么和用戶層的進程B進行通信的,對於Linux內核來說,有很多種辦法來讓內核空間和用戶空間的進程交換數據。
從上面的流程中可以看出,數據包選擇走哪個網絡設備完全由路由表控制,所以如果我們想讓某些網絡流量走應用程序B的轉發流程,就需要配置路由表讓這部分數據走tun0。
tun/tap設備有什么用?
從上面介紹過的流程可以看出來,tun/tap設備的用處是將協議棧中的部分數據包轉發給用戶空間的應用程序,給用戶空間的程序一個處理數據包的機會。於是比較常用的數據壓縮,加密等功能就可以在應用程序B里面做進去,tun/tap設備最常用的場景是VPN,包括tunnel以及應用層的IPSec等,比較有名的項目是VTun,有興趣可以去了解一下。
tun和tap的區別
用戶層程序通過tun設備只能讀寫IP數據包,而通過tap設備能讀寫鏈路層數據包,類似於普通socket和raw socket的差別一樣,處理數據包的格式不一樣。
示例
示例程序
這里寫了一個程序,它收到tun設備的數據包之后,只打印出收到了多少字節的數據包,其它的什么都不做,如何編程請參考后面的參考鏈接。
#include <net/if.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #include <sys/types.h> #include <linux/if_tun.h> #include<stdlib.h> #include<stdio.h> int tun_alloc(int flags) { struct ifreq ifr; int fd, err; char *clonedev = "/dev/net/tun"; if ((fd = open(clonedev, O_RDWR)) < 0) { return fd; } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = flags; if ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0) { close(fd); return err; } printf("Open tun/tap device: %s for reading...\n", ifr.ifr_name); return fd; } int main() { int tun_fd, nread; char buffer[1500]; /* Flags: IFF_TUN - TUN device (no Ethernet headers) * IFF_TAP - TAP device * IFF_NO_PI - Do not provide packet information */ tun_fd = tun_alloc(IFF_TUN | IFF_NO_PI); if (tun_fd < 0) { perror("Allocating interface"); exit(1); } while (1) { nread = read(tun_fd, buffer, sizeof(buffer)); if (nread < 0) { perror("Reading from interface"); close(tun_fd); exit(1); } printf("Read %d bytes from tun/tap device\n", nread); } return 0; }
演示
#--------------------------第一個shell窗口---------------------- #將上面的程序保存成tun.c,然后編譯 dev@debian:~$ gcc tun.c -o tun #啟動tun程序,程序會創建一個新的tun設備, #程序會阻塞在這里,等着數據包過來 dev@debian:~$ sudo ./tun Open tun/tap device tun1 for reading... Read 84 bytes from tun/tap device Read 84 bytes from tun/tap device Read 84 bytes from tun/tap device Read 84 bytes from tun/tap device #--------------------------第二個shell窗口---------------------- #啟動抓包程序,抓經過tun1的包 # tcpdump -i tun1 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on tun1, link-type RAW (Raw IP), capture size 262144 bytes 19:57:13.473101 IP 192.168.3.11 > 192.168.3.12: ICMP echo request, id 24028, seq 1, length 64 19:57:14.480362 IP 192.168.3.11 > 192.168.3.12: ICMP echo request, id 24028, seq 2, length 64 19:57:15.488246 IP 192.168.3.11 > 192.168.3.12: ICMP echo request, id 24028, seq 3, length 64 19:57:16.496241 IP 192.168.3.11 > 192.168.3.12: ICMP echo request, id 24028, seq 4, length 64 #--------------------------第三個shell窗口---------------------- #./tun啟動之后,通過ip link命令就會發現系統多了一個tun設備, #在我的測試環境中,多出來的設備名稱叫tun1,在你的環境中可能叫tun0 #新的設備沒有ip,我們先給tun1配上IP地址 dev@debian:~$ sudo ip addr add 192.168.3.11/24 dev tun1 #默認情況下,tun1沒有起來,用下面的命令將tun1啟動起來 dev@debian:~$ sudo ip link set tun1 up #嘗試ping一下192.168.3.0/24網段的IP, #根據默認路由,該數據包會走tun1設備, #由於我們的程序中收到數據包后,啥都沒干,相當於把數據包丟棄了, #所以這里的ping根本收不到返回包, #但在前兩個窗口中可以看到這里發出去的四個icmp echo請求包, #說明數據包正確的發送到了應用程序里面,只是應用程序沒有處理該包 dev@debian:~$ ping -c 4 192.168.3.12 PING 192.168.3.12 (192.168.3.12) 56(84) bytes of data. --- 192.168.3.12 ping statistics --- 4 packets transmitted, 0 received, 100% packet loss, time 3023ms
結束語
平時我們用到tun/tap設備的機會不多,不過由於其結構比較簡單,拿它來了解一下虛擬網絡設備還不錯,為后續理解Linux下更復雜的虛擬網絡設備(比如網橋)做個鋪墊。
