linux虛擬網絡設備之tun/tap

在現在的雲時代,到處都是虛擬機和容器,它們背後的網絡管理都離不開虛擬網絡設備,所以瞭解虛擬網絡設備有利於我們更好的理解雲時代的網絡結構。從本篇開始,將介紹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。)

下面來看看數據包的流程:

  1. 應用程序A是一個普通的程序,通過socket A發送了一個數據包,假設這個數據包的目的IP地址是192.168.3.1
  2. socket將這個數據包丟給協議棧
  3. 協議棧根據數據包的目的IP地址,匹配本地路由規則,知道這個數據包應該由tun0出去,於是將數據包交給tun0
  4. tun0收到數據包之後,發現另一端被進程B打開了,於是將數據包丟給了進程B
  5. 進程B收到數據包之後,做一些跟業務相關的處理,然後構造一個新的數據包,將原來的數據包嵌入在新的數據包中,最後通過socket B將數據包轉發出去,這時候新數據包的源地址變成了eth0的地址,而目的IP地址變成了一個其它的地址,比如是10.33.0.1.
  6. socket B將數據包丟給協議棧
  7. 協議棧根據本地路由,發現這個數據包應該要通過eth0發送出去,於是將數據包交給eth0
  8. 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

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