1. 程式人生 > 實用技巧 >【轉】Linux虛擬網路裝置之tun/tap

【轉】Linux虛擬網路裝置之tun/tap

原文:https://segmentfault.com/a/1190000009249039

————————————————————————————————————————————

Linux虛擬網路裝置之tun/tap

網路linux 釋出於 2017-04-30

在現在的雲時代,到處都是虛擬機器和容器,它們背後的網路管理都離不開虛擬網路裝置,所以瞭解虛擬網路裝置有利於我們更好的理解雲時代的網路結構。從本篇開始,將介紹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下更復雜的虛擬網路裝置(比如網橋)做個鋪墊。

參考