Linux 虛擬網絡設備之 tun-tap
在現在的雲時代,到處都是虛擬機和容器,它們背後的網絡管理都離不開虛擬網絡設備,所以瞭解虛擬網絡設備有利於我們更好的理解雲時代的網絡結構。
虛擬設備和物理設備的區別
在 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 下更復雜的虛擬網絡設備(比如網橋)做個鋪墊。
原文鏈接:https://segmentfault.com/a/1190000009249039
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/VgC7DiZEyCRamFs_Fix1lw