1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Linux 虛擬網(wǎng)絡(luò)設(shè)備之 tun/tap

        共 6997字,需瀏覽 14分鐘

         ·

        2021-08-11 01:21

        在現(xiàn)在的云時(shí)代,到處都是虛擬機(jī)和容器,它們背后的網(wǎng)絡(luò)管理都離不開虛擬網(wǎng)絡(luò)設(shè)備,所以了解虛擬網(wǎng)絡(luò)設(shè)備有利于我們更好的理解云時(shí)代的網(wǎng)絡(luò)結(jié)構(gòu)。

        虛擬設(shè)備和物理設(shè)備的區(qū)別

        在Linux網(wǎng)絡(luò)數(shù)據(jù)包的接收過程和數(shù)據(jù)包的發(fā)送過程這兩篇文章中,介紹了數(shù)據(jù)包的收發(fā)流程,知道了Linux內(nèi)核中有一個(gè)網(wǎng)絡(luò)設(shè)備管理層,處于網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)和協(xié)議棧之間,負(fù)責(zé)銜接它們之間的數(shù)據(jù)交互。驅(qū)動(dòng)不需要了解協(xié)議棧的細(xì)節(jié),協(xié)議棧也不需要了解設(shè)備驅(qū)動(dòng)的細(xì)節(jié)。

        對(duì)于一個(gè)網(wǎng)絡(luò)設(shè)備來說,就像一個(gè)管道(pipe)一樣,有兩端,從其中任意一端收到的數(shù)據(jù)將從另一端發(fā)送出去。

        比如一個(gè)物理網(wǎng)卡eth0,它的兩端分別是內(nèi)核協(xié)議棧(通過內(nèi)核網(wǎng)絡(luò)設(shè)備管理模塊間接的通信)和外面的物理網(wǎng)絡(luò),從物理網(wǎng)絡(luò)收到的數(shù)據(jù),會(huì)轉(zhuǎn)發(fā)給內(nèi)核協(xié)議棧,而應(yīng)用程序從協(xié)議棧發(fā)過來的數(shù)據(jù)將會(huì)通過物理網(wǎng)絡(luò)發(fā)送出去。

        那么對(duì)于一個(gè)虛擬網(wǎng)絡(luò)設(shè)備呢?首先它也歸內(nèi)核的網(wǎng)絡(luò)設(shè)備管理子系統(tǒng)管理,對(duì)于Linux內(nèi)核網(wǎng)絡(luò)設(shè)備管理模塊來說,虛擬設(shè)備和物理設(shè)備沒有區(qū)別,都是網(wǎng)絡(luò)設(shè)備,都能配置IP,從網(wǎng)絡(luò)設(shè)備來的數(shù)據(jù),都會(huì)轉(zhuǎn)發(fā)給協(xié)議棧,協(xié)議棧過來的數(shù)據(jù),也會(huì)交由網(wǎng)絡(luò)設(shè)備發(fā)送出去,至于是怎么發(fā)送出去的,發(fā)到哪里去,那是設(shè)備驅(qū)動(dòng)的事情,跟Linux內(nèi)核就沒關(guān)系了,所以說虛擬網(wǎng)絡(luò)設(shè)備的一端也是協(xié)議棧,而另一端是什么取決于虛擬網(wǎng)絡(luò)設(shè)備的驅(qū)動(dòng)實(shí)現(xiàn)。

        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

        上圖中有兩個(gè)應(yīng)用程序A和B,都在用戶層,而其它的socket、協(xié)議棧(Newwork Protocol Stack)和網(wǎng)絡(luò)設(shè)備(eth0和tun0)部分都在內(nèi)核層,其實(shí)socket是協(xié)議棧的一部分,這里分開來的目的是為了看的更直觀。

        tun0是一個(gè)Tun/Tap虛擬設(shè)備,從上圖中可以看出它和物理設(shè)備eth0的差別,它們的一端雖然都連著協(xié)議棧,但另一端不一樣,eth0的另一端是物理網(wǎng)絡(luò),這個(gè)物理網(wǎng)絡(luò)可能就是一個(gè)交換機(jī),而tun0的另一端是一個(gè)用戶層的程序,協(xié)議棧發(fā)給tun0的數(shù)據(jù)包能被這個(gè)應(yīng)用程序讀取到,并且應(yīng)用程序能直接向tun0寫數(shù)據(jù)。

        這里假設(shè)eth0配置的IP是10.32.0.11,而tun0配置的IP是192.168.3.11.

        這里列舉的是一個(gè)典型的tun/tap設(shè)備的應(yīng)用場(chǎng)景,發(fā)到192.168.3.0/24網(wǎng)絡(luò)的數(shù)據(jù)通過程序B這個(gè)隧道,利用10.32.0.11發(fā)到遠(yuǎn)端網(wǎng)絡(luò)的10.33.0.1,再由10.33.0.1轉(zhuǎn)發(fā)給相應(yīng)的設(shè)備,從而實(shí)現(xiàn)VPN。

        下面來看看數(shù)據(jù)包的流程:

        1. 應(yīng)用程序A是一個(gè)普通的程序,通過socket A發(fā)送了一個(gè)數(shù)據(jù)包,假設(shè)這個(gè)數(shù)據(jù)包的目的IP地址是192.168.3.1
        2. socket將這個(gè)數(shù)據(jù)包丟給協(xié)議棧
        3. 協(xié)議棧根據(jù)數(shù)據(jù)包的目的IP地址,匹配本地路由規(guī)則,知道這個(gè)數(shù)據(jù)包應(yīng)該由tun0出去,于是將數(shù)據(jù)包交給tun0
        4. tun0收到數(shù)據(jù)包之后,發(fā)現(xiàn)另一端被進(jìn)程B打開了,于是將數(shù)據(jù)包丟給了進(jìn)程B
        5. 進(jìn)程B收到數(shù)據(jù)包之后,做一些跟業(yè)務(wù)相關(guān)的處理,然后構(gòu)造一個(gè)新的數(shù)據(jù)包,將原來的數(shù)據(jù)包嵌入在新的數(shù)據(jù)包中,最后通過socket B將數(shù)據(jù)包轉(zhuǎn)發(fā)出去,這時(shí)候新數(shù)據(jù)包的源地址變成了eth0的地址,而目的IP地址變成了一個(gè)其它的地址,比如是10.33.0.1.
        6. socket B將數(shù)據(jù)包丟給協(xié)議棧
        7. 協(xié)議棧根據(jù)本地路由,發(fā)現(xiàn)這個(gè)數(shù)據(jù)包應(yīng)該要通過eth0發(fā)送出去,于是將數(shù)據(jù)包交給eth0
        8. eth0通過物理網(wǎng)絡(luò)將數(shù)據(jù)包發(fā)送出去

        10.33.0.1收到數(shù)據(jù)包之后,會(huì)打開數(shù)據(jù)包,讀取里面的原始數(shù)據(jù)包,并轉(zhuǎn)發(fā)給本地的192.168.3.1,然后等收到192.168.3.1的應(yīng)答后,再構(gòu)造新的應(yīng)答包,并將原始應(yīng)答包封裝在里面,再由原路徑返回給應(yīng)用程序B,應(yīng)用程序B取出里面的原始應(yīng)答包,最后返回給應(yīng)用程序A

        這里不討論Tun/Tap設(shè)備tun0是怎么和用戶層的進(jìn)程B進(jìn)行通信的,對(duì)于Linux內(nèi)核來說,有很多種辦法來讓內(nèi)核空間和用戶空間的進(jìn)程交換數(shù)據(jù)。

        從上面的流程中可以看出,數(shù)據(jù)包選擇走哪個(gè)網(wǎng)絡(luò)設(shè)備完全由路由表控制,所以如果我們想讓某些網(wǎng)絡(luò)流量走應(yīng)用程序B的轉(zhuǎn)發(fā)流程,就需要配置路由表讓這部分?jǐn)?shù)據(jù)走tun0。

        tun/tap設(shè)備有什么用?

        從上面介紹過的流程可以看出來,tun/tap設(shè)備的用處是將協(xié)議棧中的部分?jǐn)?shù)據(jù)包轉(zhuǎn)發(fā)給用戶空間的應(yīng)用程序,給用戶空間的程序一個(gè)處理數(shù)據(jù)包的機(jī)會(huì)。于是比較常用的數(shù)據(jù)壓縮,加密等功能就可以在應(yīng)用程序B里面做進(jìn)去,tun/tap設(shè)備最常用的場(chǎng)景是VPN,包括tunnel以及應(yīng)用層的IPSec等,比較有名的項(xiàng)目是VTun,有興趣可以去了解一下。

        tun和tap的區(qū)別

        用戶層程序通過tun設(shè)備只能讀寫IP數(shù)據(jù)包,而通過tap設(shè)備能讀寫鏈路層數(shù)據(jù)包,類似于普通socket和raw socket的差別一樣,處理數(shù)據(jù)包的格式不一樣。

        示例

        示例程序

        這里寫了一個(gè)程序,它收到tun設(shè)備的數(shù)據(jù)包之后,只打印出收到了多少字節(jié)的數(shù)據(jù)包,其它的什么都不做,如何編程請(qǐng)參考后面的參考鏈接。

        #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;
        }

        演示

        #--------------------------第一個(gè)shell窗口----------------------
        #將上面的程序保存成tun.c,然后編譯
        dev@debian:~$ gcc tun.c -o tun

        #啟動(dòng)tun程序,程序會(huì)創(chuàng)建一個(gè)新的tun設(shè)備,
        #程序會(huì)阻塞在這里,等著數(shù)據(jù)包過來
        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

        #--------------------------第二個(gè)shell窗口----------------------
        #啟動(dòng)抓包程序,抓經(jīng)過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

        #--------------------------第三個(gè)shell窗口----------------------
        #./tun啟動(dòng)之后,通過ip link命令就會(huì)發(fā)現(xiàn)系統(tǒng)多了一個(gè)tun設(shè)備,
        #在我的測(cè)試環(huán)境中,多出來的設(shè)備名稱叫tun1,在你的環(huán)境中可能叫tun0
        #新的設(shè)備沒有ip,我們先給tun1配上IP地址
        dev@debian:~$ sudo ip addr add 192.168.3.11/24 dev tun1

        #默認(rèn)情況下,tun1沒有起來,用下面的命令將tun1啟動(dòng)起來
        dev@debian:~$ sudo ip link set tun1 up

        #嘗試ping一下192.168.3.0/24網(wǎng)段的IP,
        #根據(jù)默認(rèn)路由,該數(shù)據(jù)包會(huì)走tun1設(shè)備,
        #由于我們的程序中收到數(shù)據(jù)包后,啥都沒干,相當(dāng)于把數(shù)據(jù)包丟棄了,
        #所以這里的ping根本收不到返回包,
        #但在前兩個(gè)窗口中可以看到這里發(fā)出去的四個(gè)icmp echo請(qǐng)求包,
        #說明數(shù)據(jù)包正確的發(fā)送到了應(yīng)用程序里面,只是應(yīng)用程序沒有處理該包
        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

        結(jié)束語

        平時(shí)我們用到tun/tap設(shè)備的機(jī)會(huì)不多,不過由于其結(jié)構(gòu)比較簡(jiǎn)單,拿它來了解一下虛擬網(wǎng)絡(luò)設(shè)備還不錯(cuò),為后續(xù)理解Linux下更復(fù)雜的虛擬網(wǎng)絡(luò)設(shè)備(比如網(wǎng)橋)做個(gè)鋪墊。

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


        瀏覽 72
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            女人被爽到呻吟喘息 | 日韩无码第一 | 国产精品扒开腿做爽爽爽A片唱戏 | 三上悠亚免费在线 | 办公室荡乳欲伦交换bd电影 | 91人妻人人澡人人 | 国内精品扒开腿爽爽爽爽爽爽视频 | 从腿摸到内裤里吻戏视频酒吧 | 三级性视频 | 最新版超模裸体asspics |