Linux 中的虛擬網路介面

於清樂發表於2021-08-15

獨立部落格閱讀地址:https://ryan4yin.space/posts/linux-virtual-network-interfaces/

本文用到的字元畫工具:vscode-asciiflow2

Linux 具有強大的虛擬網路能力,這也是 openstack 網路、docker 容器網路以及 kubernetes 網路等虛擬網路的基礎。

這裡介紹 Linux 常用的虛擬網路介面型別:TUN/TAP、bridge 以及 veth。

一、tun/tap 虛擬網路介面

tun/tap 是作業系統核心中的虛擬網路裝置,他們為使用者層程式提供資料的接收與傳輸。

普通的物理網路介面如 eth0,它的兩端分別是核心協議棧和外面的物理網路。

而對於 TUN/TAP 虛擬介面如 tun0,它的一端一定是連線的使用者層程式,另一端則視配置方式的不同而變化,可以直連核心協議棧,也可以是某個 bridge(後面會介紹)。
Linux 通過核心模組 TUN 提供 tun/tap 功能,該模組提供了一個裝置介面 /dev/net/tun 供使用者層程式讀寫,使用者層程式通過 /dev/net/tun 讀寫主機核心協議棧的資料。

> modinfo tun
filename:       /lib/modules/5.13.6-1-default/kernel/drivers/net/tun.ko.xz
alias:          devname:net/tun
alias:          char-major-10-200
license:        GPL
author:         (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>
description:    Universal TUN/TAP device driver
...

> ls /dev/net/tun
/dev/net/tun

一個 TUN 裝置的示例圖如下:

             
+----------------------------------------------------------------------+
|                                                                      |
|  +--------------------+      +--------------------+                  |
|  | User Application A |      | User Application B +<-----+           |
|  +------------+-------+      +-------+------------+      |           |
|               | 1                    | 5                 |           |
|...............+......................+...................|...........|
|               ↓                      ↓                   |           |
|         +----------+           +----------+              |           |
|         | socket A |           | socket B |              |           |
|         +-------+--+           +--+-------+              |           |
|                 | 2               | 6                    |           |
|.................+.................+......................|...........|
|                 ↓                 ↓                      |           |
|             +------------------------+          +--------+-------+   |
|             | Network Protocol Stack |          |  /dev/net/tun  |   |
|             +--+-------------------+-+          +--------+-------+   |
|                | 7                 | 3                   ^           |
|................+...................+.....................|...........|
|                ↓                   ↓                     |           |
|        +----------------+    +----------------+        4 |           |
|        |      eth0      |    |      tun0      |          |           |
|        +-------+--------+    +-----+----------+          |           |
|    10.32.0.11  |                   |   192.168.3.11      |           |
|                | 8                 +---------------------+           |
|                |                                                     |
+----------------+-----------------------------------------------------+
                 ↓
         Physical Network

因為 TUN/TAP 裝置的一端是核心協議棧,顯然流入 tun0 的資料包是先經過本地的路由規則匹配的。

路由匹配成功,資料包被髮送到 tun0 後,tun0 發現另一端是通過 /dev/net/tun 連線到應用程式 B,就會將資料丟給應用程式 B。

應用程式對資料包進行處理後,可能會構造新的資料包,通過物理網路卡傳送出去。比如常見的 VPN 程式就是把原來的資料包封裝/加密一遍,再傳送給 VPN 伺服器。

C 語言程式設計測試 TUN 裝置

為了使用 tun/tap 裝置,使用者層程式需要通過系統呼叫開啟 /dev/net/tun 獲得一個讀寫該裝置的檔案描述符(FD),並且呼叫 ioctl() 向核心註冊一個 TUN 或 TAP 型別的虛擬網路卡(例項化一個 tun/tap 裝置),其名稱可能是 tun0/tap0 等。

此後,使用者程式可以通過該 TUN/TAP 虛擬網路卡與主機核心協議棧(或者其他網路裝置)互動。當使用者層程式關閉後,其註冊的 TUN/TAP 虛擬網路卡以及自動生成的路由表相關條目都會被核心釋放。

可以把使用者層程式看做是網路上另一臺主機,他們通過 tun/tap 虛擬網路卡相連。

一個簡單的 C 程式示例如下,它每次收到資料後,都只單純地列印一下收到的位元組數:

#include <linux/if.h>
#include <linux/if_tun.h>

#include <sys/ioctl.h>

#include <fcntl.h>
#include <string.h>

#include <unistd.h>
#include<stdlib.h>
#include<stdio.h>

int tun_alloc(int flags)
{

    struct ifreq ifr;
    int fd, err;
    char *clonedev = "/dev/net/tun";

    // 開啟 tun 檔案,獲得 fd
    if ((fd = open(clonedev, O_RDWR)) < 0) {
        return fd;
    }

    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = flags;

    // 向核心註冊一個 TUN 網路卡,並與前面拿到的 fd 關聯起來
    // 程式關閉時,註冊的 tun 網路卡及自動生成的相關路由策略,會被自動釋放
    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;
}

接下來開啟三個終端視窗來測試上述程式,分別執行上面的 tun 程式、tcpdump 和 iproute2 指令。

首先通過編譯執行上述 c 程式,程式會阻塞住,等待資料到達:

# 編譯,請忽略部分 warning
> gcc mytun.c -o mytun

# 建立並監聽 tun 裝置需要 root 許可權
> sudo mytun 
Open tun/tap device: tun0 for reading...

現在使用 iproute2 檢視下鏈路層裝置:

# 能發現最後面有列出名為 tun0 的介面,但是狀態為 down
❯ ip addr ls
......
3: wlp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether c0:3c:59:36:a4:16 brd ff:ff:ff:ff:ff:ff
    inet 192.168.31.228/24 brd 192.168.31.255 scope global dynamic noprefixroute wlp4s0
       valid_lft 41010sec preferred_lft 41010sec
    inet6 fe80::4ab0:130f:423b:5d37/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
7: tun0: <POINTOPOINT,MULTICAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 500
    link/none 

# 為 tun0 設定 ip 地址,注意不要和其他介面在同一網段,會導致路由衝突
> sudo ip addr add 172.21.22.23/24 dev tun0
# 啟動 tun0 這個介面,這一步會自動向路由表中新增將 172.21.22.23/24 路由到 tun0 的策略
> sudo ip link set tun0 up
#確認上一步新增的路由策略是否存在
❯ ip route ls
default via 192.168.31.1 dev wlp4s0 proto dhcp metric 600 
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown 
172.21.22.0/24 dev tun0 proto kernel scope link src 172.21.22.23 
192.168.31.0/24 dev wlp4s0 proto kernel scope link src 192.168.31.228 metric 600 

# 此時再檢視介面,發現 tun0 狀態為 unknown
> ip addr ls
......
8: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500
    link/none 
    inet 172.21.22.23/24 scope global tun0
       valid_lft forever preferred_lft forever
    inet6 fe80::3d52:49b5:1cf3:38fd/64 scope link stable-privacy 
       valid_lft forever preferred_lft forever

# 使用 tcpdump 嘗試抓下 tun0 的資料,會阻塞在這裡,等待資料到達
> tcpdump -i tun0

現在再啟動第三個視窗發點資料給 tun0,持續觀察前面 tcpdumpmytun 的日誌:

# 直接 ping tun0 的地址,貌似有問題,資料沒進 mytun 程式,而且還有響應
❯ ping -c 4 172.21.22.23
PING 172.21.22.23 (172.21.22.23) 56(84) bytes of data.
64 bytes from 172.21.22.23: icmp_seq=1 ttl=64 time=0.167 ms
64 bytes from 172.21.22.23: icmp_seq=2 ttl=64 time=0.180 ms
64 bytes from 172.21.22.23: icmp_seq=3 ttl=64 time=0.126 ms
64 bytes from 172.21.22.23: icmp_seq=4 ttl=64 time=0.141 ms

--- 172.21.22.23 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3060ms
rtt min/avg/max/mdev = 0.126/0.153/0.180/0.021 ms

# 但是 ping 該網段下的其他地址,流量就會被轉發給 mytun 程式,因為 mytun 啥資料也沒回,自然丟包率 100%
# tcpdump 和 mytun 都會列印出相關日誌
❯ ping -c 4 172.21.22.26
PING 172.21.22.26 (172.21.22.26) 56(84) bytes of data.

--- 172.21.22.26 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3055ms

下面給出 mytun 的輸出:

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

以及 tcpdump 的輸出:

00:22:03.622684 IP (tos 0x0, ttl 64, id 37341, offset 0, flags [DF], proto ICMP (1), length 84)
    172.21.22.23 > 172.21.22.26: ICMP echo request, id 11, seq 1, length 64
00:22:04.633394 IP (tos 0x0, ttl 64, id 37522, offset 0, flags [DF], proto ICMP (1), length 84)
    172.21.22.23 > 172.21.22.26: ICMP echo request, id 11, seq 2, length 64
00:22:05.653356 IP (tos 0x0, ttl 64, id 37637, offset 0, flags [DF], proto ICMP (1), length 84)
    172.21.22.23 > 172.21.22.26: ICMP echo request, id 11, seq 3, length 64
00:22:06.677341 IP (tos 0x0, ttl 64, id 37667, offset 0, flags [DF], proto ICMP (1), length 84)
    172.21.22.23 > 172.21.22.26: ICMP echo request, id 11, seq 4, length 64

更復雜的 tun 程式,可以參考

TUN 與 TAP 的區別

TUN 和 TAP 的區別在於工作的網路層次不同,使用者程式通過 TUN 裝置只能讀寫網路層的 IP 資料包,而 TAP 裝置則支援讀寫鏈路層的資料包(通常是乙太網資料包,帶有 Ethernet headers)。

TUN 與 TAP 的關係,就類似於 socket 和 raw socket.

TUN/TAP 應用最多的場景是 VPN 代理,比如:

  1. clash: 一個支援各種規則的隧道,也支援 TUN 模式
  2. tun2socks: 一個全域性透明代理,和 VPN 的工作模式一樣,它通過建立虛擬網路卡+修改路由表,在第三層網路層代理系統流量。

二、veth

veth 介面總是成對出現,一對 veth 介面就類似一根網線,從一端進來的資料會從另一端出去。

同時 veth 又是一個虛擬網路介面,因此它和 TUN/TAP 或者其他物理網路介面一樣,也都能配置 mac/ip 地址(但是並不是一定得配 mac/ip 地址)。

其主要作用就是連線不同的網路,比如在容器網路中,用於將容器的 namespace 與 root namespace 的網橋 br0 相連。
容器網路中,容器側的 veth 自身設定了 ip/mac 地址並被重新命名為 eth0,作為容器的網路介面使用,而主機側的 veth 則直接連線在 docker0/br0 上面。

使用 veth 的容器網路的詳細示例圖,在下一節有提供。

三、bridge

Linux Bridge 是工作在鏈路層的網路交換機,由 Linux 核心模組 brige 提供,它負責在所有連線到它的介面之間轉發鏈路層資料包。

新增到 Bridge 上的裝置被設定為只接受二層資料幀並且轉發所有收到的資料包到 Bridge 中。
在 Bridge 中會進行一個類似物理交換機的查MAC埠對映表、轉發、更新MAC埠對映表這樣的處理邏輯,從而資料包可以被轉發到另一個介面/丟棄/廣播/發往上層協議棧,由此 Bridge 實現了資料轉發的功能。

如果使用 tcpdump 在 Bridge 介面上抓包,可以抓到網橋上所有介面進出的包,因為這些資料包都要通過網橋進行轉發。

與物理交換機不同的是,Bridge 本身可以設定 IP 地址,可以認為當使用 brctl addbr br0 新建一個 br0 網橋時,系統自動建立了一個同名的隱藏 br0 網路介面。br0 一旦設定 IP 地址,就意味著這個隱藏的 br0 介面可以作為路由介面裝置,參與 IP 層的路由選擇(可以使用 route -n 檢視最後一列 Iface)。因此只有當 br0 設定 IP 地址時,Bridge 才有可能將資料包發往上層協議棧。

但被新增到 Bridge 上的網路卡是不能配置 IP 地址的,他們工作在資料鏈路層,對路由系統不可見。

它常被用於在虛擬機器、主機上不同的 namepsaces 之間轉發資料。

虛擬機器場景(橋接模式)

以 qemu-kvm 為例,在虛擬機器的橋接模式下,qemu-kvm 會為每個虛擬機器建立一個 tun/tap 虛擬網路卡並連線到 br0 網橋。
虛擬機器內部的網路介面 eth0 是 qemu-kvm 軟體模擬的,實際上虛擬機器內網路資料的收發都會被 qemu-kvm 轉換成對 /dev/net/tun 的讀寫。

以傳送資料為例,整個流程如下:

  • 虛擬機器發出去的資料包先到達 qemu-kvm 程式
  • 資料被使用者層程式 qemu-kvm 寫入到 /dev/net/tun,到達 tap 裝置
  • tap 裝置把資料傳送到 br0 網橋
  • br0 把資料交給 eth0 傳送出去

整個流程跑完,資料包都不需要經過宿主機的協議棧,效率高。

+------------------------------------------------+-----------------------------------+-----------------------------------+
|                       Host                     |           VirtualMachine1         |           VirtualMachine2         |
|                                                |                                   |                                   |
|    +--------------------------------------+    |    +-------------------------+    |    +-------------------------+    |
|    |         Network Protocol Stack       |    |    |  Network Protocol Stack |    |    |  Network Protocol Stack |    |
|    +--------------------------------------+    |    +-------------------------+    |    +-------------------------+    |
|                       ↑                        |                ↑                  |                 ↑                 |
|.......................|........................|................|..................|.................|.................|
|                       ↓                        |                ↓                  |                 ↓                 |
|                  +--------+                    |            +-------+              |             +-------+             |
|                  | .3.101 |                    |            | .3.102|              |             | .3.103|             |
|     +------+     +--------+     +-------+      |            +-------+              |             +-------+             |
|     | eth0 |<--->|   br0  |<--->|tun/tap|      |            | eth0  |              |             | eth0  |             |
|     +------+     +--------+     +-------+      |            +-------+              |             +-------+             |
|         ↑             ↑             ↑      +--------+           ↑                  |                 ↑                 |
|         |             |             +------|qemu-kvm|-----------+                  |                 |                 |
|         |             ↓                    +--------+                              |                 |                 |
|         |         +-------+                    |                                   |                 |                 |
|         |         |tun/tap|                    |                                   |                 |                 |
|         |         +-------+                    |                                   |                 |                 |
|         |             ↑                        |            +--------+             |                 |                 |
|         |             +-------------------------------------|qemu-kvm|-------------|-----------------+                 |
|         |                                      |            +--------+             |                                   |
|         |                                      |                                   |                                   |
+---------|--------------------------------------+-----------------------------------+-----------------------------------+
          ↓
    Physical Network  (192.168.3.0/24)

跨 namespace 通訊場景(容器網路,NAT 模式)

由於容器執行在自己單獨的 network namespace 裡面,所以和虛擬機器一樣,它們也都有自己單獨的協議棧。

容器網路的結構和虛擬機器差不多,但是它改用了 NAT 網路,並把 tun/tap 換成了 veth,導致 docker0 過來的資料,要先經過宿主機協議棧,然後才進入 veth 介面。

多了一層 NAT,以及多走了一層宿主機協議棧,都會導致效能下降。

示意圖如下:

+-----------------------------------------------+-----------------------------------+-----------------------------------+
|                      Host                     |           Container 1             |           Container 2             |
|                                               |                                   |                                   |
|   +---------------------------------------+   |    +-------------------------+    |    +-------------------------+    |
|   |       Network Protocol Stack          |   |    |  Network Protocol Stack |    |    |  Network Protocol Stack |    |
|   +----+-------------+--------------------+   |    +-----------+-------------+    |    +------------+------------+    |
|        ^             ^                        |                ^                  |                 ^                 |
|........|.............|........................|................|..................|.................|.................|
|        v             v  ↓                     |                v                  |                 v                 |
|   +----+----+  +-----+------+                 |          +-----+-------+          |           +-----+-------+         |
|   | .31.101 |  | 172.17.0.1 |      +------+   |          | 172.17.0.2  |          |           |  172.17.0.3 |         |
|   +---------+  +-------------<---->+ veth |   |          +-------------+          |           +-------------+         |
|   |  eth0   |  |   docker0  |      +--+---+   |          | eth0(veth)  |          |           | eth0(veth)  |         |
|   +----+----+  +-----+------+         ^       |          +-----+-------+          |           +-----+-------+         |
|        ^             ^                |       |                ^                  |                 ^                 |
|        |             |                +------------------------+                  |                 |                 |
|        |             v                        |                                   |                 |                 |
|        |          +--+---+                    |                                   |                 |                 |
|        |          | veth |                    |                                   |                 |                 |
|        |          +--+---+                    |                                   |                 |                 |
|        |             ^                        |                                   |                 |                 |
|        |             +------------------------------------------------------------------------------+                 |
|        |                                      |                                   |                                   |
|        |                                      |                                   |                                   |
+-----------------------------------------------+-----------------------------------+-----------------------------------+
         v
    Physical Network  (192.168.31.0/24)

每建立一個新容器,都會在容器的 namespace 裡新建一個 veth 介面並命令為 eth0,同時在主 namespace 建立一個 veth,將容器的 eth0 與 docker0 連線。

可以在容器中通過 iproute2 檢視到, eth0 的介面型別為 veth

❯ docker run -it --rm debian:buster bash
root@5facbe4ddc1e:/# ip --details addr ls
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 minmtu 0 maxmtu 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 promiscuity 0 minmtu 68 maxmtu 65535 
    veth numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

同時在宿主機中能看到對應的 veth 裝置是繫結到了 docker0 網橋的:

❯ sudo brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242fce99ef5       no              vethea4171a

四、其他虛擬網路介面的型別

除了上面介紹的這些,Linux 還支援 VLAN、VXLAN 等型別的虛擬網路介面,可通過 ip link help 檢視,因為我接觸的少,這裡就不介紹了。

五、虛擬網路介面的速率

Loopback 和本章講到的其他虛擬網路介面一樣,都是一種軟體模擬的網路裝置。
他們的速率是不是也像物理鏈路一樣,存在鏈路層(比如乙太網)的頻寬限制呢?

比如目前很多老舊的網路裝置,都是隻支援到百兆乙太網,這就決定了它的頻寬上限。
即使是較新的裝置,目前基本也都只支援到千兆,也就是 1GbE 乙太網標準,那本文提到的虛擬網路介面單純在本機內部通訊,是否也存在這樣的制約呢?是否也只能跑到 1GbE?

使用 ethtool 檢查:

# docker 容器的 veth 介面速率
> ethtool vethe899841 | grep Speed
        Speed: 10000Mb/s

# 網橋看起來沒有固定的速率
> ethtool docker0 | grep Speed
        Speed: Unknown!

# tun0 裝置的預設速率貌似是 10Mb/s ?
> ethtool tun0 | grep Speed
        Speed: 10Mb/s

# 此外 ethtool 無法檢查 lo 以及 wifi 的速率

網路效能實測

接下來實際測試一下,先給出機器引數:

❯ cat /etc/os-release 
NAME="openSUSE Tumbleweed"
# VERSION="20210810"
...

❯ uname -a
Linux legion-book 5.13.8-1-default #1 SMP Thu Aug 5 08:56:22 UTC 2021 (967c6a8) x86_64 x86_64 x86_64 GNU/Linux


❯ lscpu
Architecture:                    x86_64
CPU(s):                          16
Model name:                      AMD Ryzen 7 5800H with Radeon Graphics
...

# 記憶體,單位 MB
❯ free -m
               total        used        free      shared  buff/cache   available
Mem:           27929        4482       17324         249        6122       22797
Swap:           2048           0        2048

使用 iperf3 測試:

# 啟動服務端
iperf3 -s

-------------
# 新視窗啟動客戶端,通過 loopback 介面訪問 iperf3-server,大概 49Gb/s
❯ iperf3 -c 127.0.0.1
Connecting to host 127.0.0.1, port 5201
[  5] local 127.0.0.1 port 48656 connected to 127.0.0.1 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  4.46 GBytes  38.3 Gbits/sec    0   1.62 MBytes       
[  5]   1.00-2.00   sec  4.61 GBytes  39.6 Gbits/sec    0   1.62 MBytes       
[  5]   2.00-3.00   sec  5.69 GBytes  48.9 Gbits/sec    0   1.62 MBytes       
[  5]   3.00-4.00   sec  6.11 GBytes  52.5 Gbits/sec    0   1.62 MBytes       
[  5]   4.00-5.00   sec  6.04 GBytes  51.9 Gbits/sec    0   1.62 MBytes       
[  5]   5.00-6.00   sec  6.05 GBytes  52.0 Gbits/sec    0   1.62 MBytes       
[  5]   6.00-7.00   sec  6.01 GBytes  51.6 Gbits/sec    0   1.62 MBytes       
[  5]   7.00-8.00   sec  6.05 GBytes  52.0 Gbits/sec    0   1.62 MBytes       
[  5]   8.00-9.00   sec  6.34 GBytes  54.5 Gbits/sec    0   1.62 MBytes       
[  5]   9.00-10.00  sec  5.91 GBytes  50.8 Gbits/sec    0   1.62 MBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  57.3 GBytes  49.2 Gbits/sec    0             sender
[  5]   0.00-10.00  sec  57.3 GBytes  49.2 Gbits/sec                  receiver

# 客戶端通過 wlp4s0 wifi 網路卡(192.168.31.228)訪問 iperf3-server,實際還是走的本機,但是速度要比 loopback 快一點,可能是預設設定的問題
❯ iperf3 -c 192.168.31.228
Connecting to host 192.168.31.228, port 5201
[  5] local 192.168.31.228 port 43430 connected to 192.168.31.228 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  5.12 GBytes  43.9 Gbits/sec    0   1.25 MBytes       
[  5]   1.00-2.00   sec  5.29 GBytes  45.5 Gbits/sec    0   1.25 MBytes       
[  5]   2.00-3.00   sec  5.92 GBytes  50.9 Gbits/sec    0   1.25 MBytes       
[  5]   3.00-4.00   sec  6.00 GBytes  51.5 Gbits/sec    0   1.25 MBytes       
[  5]   4.00-5.00   sec  5.98 GBytes  51.4 Gbits/sec    0   1.25 MBytes       
[  5]   5.00-6.00   sec  6.05 GBytes  52.0 Gbits/sec    0   1.25 MBytes       
[  5]   6.00-7.00   sec  6.16 GBytes  52.9 Gbits/sec    0   1.25 MBytes       
[  5]   7.00-8.00   sec  6.08 GBytes  52.2 Gbits/sec    0   1.25 MBytes       
[  5]   8.00-9.00   sec  6.00 GBytes  51.6 Gbits/sec    0   1.25 MBytes       
[  5]   9.00-10.00  sec  6.01 GBytes  51.6 Gbits/sec    0   1.25 MBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  58.6 GBytes  50.3 Gbits/sec    0             sender
[  5]   0.00-10.00  sec  58.6 GBytes  50.3 Gbits/sec                  receiver

# 從容器中訪問宿主機的 iperf3-server,速度幾乎沒區別
❯ docker run  -it --rm --name=iperf3-server networkstatic/iperf3 -c 192.168.31.228
Connecting to host 192.168.31.228, port 5201
[  5] local 172.17.0.2 port 43436 connected to 192.168.31.228 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  4.49 GBytes  38.5 Gbits/sec    0    403 KBytes       
[  5]   1.00-2.00   sec  5.31 GBytes  45.6 Gbits/sec    0    544 KBytes       
[  5]   2.00-3.00   sec  6.14 GBytes  52.8 Gbits/sec    0    544 KBytes       
[  5]   3.00-4.00   sec  5.85 GBytes  50.3 Gbits/sec    0    544 KBytes       
[  5]   4.00-5.00   sec  6.14 GBytes  52.7 Gbits/sec    0    544 KBytes       
[  5]   5.00-6.00   sec  5.99 GBytes  51.5 Gbits/sec    0    544 KBytes       
[  5]   6.00-7.00   sec  5.86 GBytes  50.4 Gbits/sec    0    544 KBytes       
[  5]   7.00-8.00   sec  6.05 GBytes  52.0 Gbits/sec    0    544 KBytes       
[  5]   8.00-9.00   sec  5.99 GBytes  51.5 Gbits/sec    0    544 KBytes       
[  5]   9.00-10.00  sec  6.12 GBytes  52.5 Gbits/sec    0    544 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  58.0 GBytes  49.8 Gbits/sec    0             sender
[  5]   0.00-10.00  sec  58.0 GBytes  49.8 Gbits/sec                  receiver

把 iperf3-server 跑在容器裡再測一遍:

# 在容器中啟動 iperf3-server,並對映到宿主機埠 6201
> docker run  -it --rm --name=iperf3-server -p 6201:5201 networkstatic/iperf3 -s
> docker inspect --format "{{ .NetworkSettings.IPAddress }}" iperf3-server
172.17.0.2
-----------------------------
# 測試容器之間互訪的速度,ip 為 iperf3-server 的容器 ip,速度要慢一些。
# 畢竟過了 veth -> veth -> docker0 -> veth -> veth 五層虛擬網路介面
❯ docker run  -it --rm networkstatic/iperf3 -c 172.17.0.2
Connecting to host 172.17.0.2, port 5201
[  5] local 172.17.0.3 port 40776 connected to 172.17.0.2 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  4.74 GBytes  40.7 Gbits/sec    0    600 KBytes       
[  5]   1.00-2.00   sec  4.48 GBytes  38.5 Gbits/sec    0    600 KBytes       
[  5]   2.00-3.00   sec  5.38 GBytes  46.2 Gbits/sec    0    600 KBytes       
[  5]   3.00-4.00   sec  5.39 GBytes  46.3 Gbits/sec    0    600 KBytes       
[  5]   4.00-5.00   sec  5.42 GBytes  46.6 Gbits/sec    0    600 KBytes       
[  5]   5.00-6.00   sec  5.39 GBytes  46.3 Gbits/sec    0    600 KBytes       
[  5]   6.00-7.00   sec  5.38 GBytes  46.2 Gbits/sec    0    635 KBytes       
[  5]   7.00-8.00   sec  5.37 GBytes  46.1 Gbits/sec    0    667 KBytes       
[  5]   8.00-9.00   sec  6.01 GBytes  51.7 Gbits/sec    0    735 KBytes       
[  5]   9.00-10.00  sec  5.74 GBytes  49.3 Gbits/sec    0    735 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  53.3 GBytes  45.8 Gbits/sec    0             sender
[  5]   0.00-10.00  sec  53.3 GBytes  45.8 Gbits/sec                  receiver

# 本機直接訪問容器 ip,走的是 docker0 網橋,居然還挺快
❯ iperf3 -c 172.17.0.2
Connecting to host 172.17.0.2, port 5201
[  5] local 172.17.0.1 port 56486 connected to 172.17.0.2 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  5.01 GBytes  43.0 Gbits/sec    0    632 KBytes       
[  5]   1.00-2.00   sec  5.19 GBytes  44.6 Gbits/sec    0    703 KBytes       
[  5]   2.00-3.00   sec  6.46 GBytes  55.5 Gbits/sec    0    789 KBytes       
[  5]   3.00-4.00   sec  6.80 GBytes  58.4 Gbits/sec    0    789 KBytes       
[  5]   4.00-5.00   sec  6.82 GBytes  58.6 Gbits/sec    0    913 KBytes       
[  5]   5.00-6.00   sec  6.79 GBytes  58.3 Gbits/sec    0   1007 KBytes       
[  5]   6.00-7.00   sec  6.63 GBytes  56.9 Gbits/sec    0   1.04 MBytes       
[  5]   7.00-8.00   sec  6.75 GBytes  58.0 Gbits/sec    0   1.04 MBytes       
[  5]   8.00-9.00   sec  6.19 GBytes  53.2 Gbits/sec    0   1.04 MBytes       
[  5]   9.00-10.00  sec  6.55 GBytes  56.3 Gbits/sec    0   1.04 MBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  63.2 GBytes  54.3 Gbits/sec    0             sender
[  5]   0.00-10.00  sec  63.2 GBytes  54.3 Gbits/sec                  receiver

# 如果走本機 loopback 地址 + 容器埠對映,速度就慢了好多
# 或許是因為用 iptables 做埠對映導致的?
❯ iperf3 -c 127.0.0.1 -p 6201
Connecting to host 127.0.0.1, port 6201
[  5] local 127.0.0.1 port 48862 connected to 127.0.0.1 port 6201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  2.71 GBytes  23.3 Gbits/sec    0   1.37 MBytes       
[  5]   1.00-2.00   sec  3.64 GBytes  31.3 Gbits/sec    0   1.37 MBytes       
[  5]   2.00-3.00   sec  4.08 GBytes  35.0 Gbits/sec    0   1.37 MBytes       
[  5]   3.00-4.00   sec  3.49 GBytes  30.0 Gbits/sec    0   1.37 MBytes       
[  5]   4.00-5.00   sec  5.50 GBytes  47.2 Gbits/sec    2   1.37 MBytes       
[  5]   5.00-6.00   sec  4.06 GBytes  34.9 Gbits/sec    0   1.37 MBytes       
[  5]   6.00-7.00   sec  4.12 GBytes  35.4 Gbits/sec    0   1.37 MBytes       
[  5]   7.00-8.00   sec  3.99 GBytes  34.3 Gbits/sec    0   1.37 MBytes       
[  5]   8.00-9.00   sec  3.49 GBytes  30.0 Gbits/sec    0   1.37 MBytes       
[  5]   9.00-10.00  sec  5.51 GBytes  47.3 Gbits/sec    0   1.37 MBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  40.6 GBytes  34.9 Gbits/sec    2             sender
[  5]   0.00-10.00  sec  40.6 GBytes  34.9 Gbits/sec                  receiver

# 可走 wlp4s0 + 容器埠對映,速度也不慢啊
❯ iperf3 -c 192.168.31.228 -p 6201
Connecting to host 192.168.31.228, port 6201
[  5] local 192.168.31.228 port 54582 connected to 192.168.31.228 port 6201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  4.34 GBytes  37.3 Gbits/sec    0    795 KBytes       
[  5]   1.00-2.00   sec  4.78 GBytes  41.0 Gbits/sec    0    834 KBytes       
[  5]   2.00-3.00   sec  6.26 GBytes  53.7 Gbits/sec    0    834 KBytes       
[  5]   3.00-4.00   sec  6.30 GBytes  54.1 Gbits/sec    0    875 KBytes       
[  5]   4.00-5.00   sec  6.26 GBytes  53.8 Gbits/sec    0    875 KBytes       
[  5]   5.00-6.00   sec  5.75 GBytes  49.4 Gbits/sec    0    875 KBytes       
[  5]   6.00-7.00   sec  5.49 GBytes  47.2 Gbits/sec    0    966 KBytes       
[  5]   7.00-8.00   sec  5.72 GBytes  49.1 Gbits/sec    2    966 KBytes       
[  5]   8.00-9.00   sec  4.81 GBytes  41.3 Gbits/sec    2    966 KBytes       
[  5]   9.00-10.00  sec  5.98 GBytes  51.4 Gbits/sec    0    966 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  55.7 GBytes  47.8 Gbits/sec    4             sender
[  5]   0.00-10.00  sec  55.7 GBytes  47.8 Gbits/sec                  receiver

總的來看,loopback、bridge、veth 這幾個介面基本上是沒被限速的,veth 有查到上限為 10000Mb/s(10Gb/s) 感覺也是個假數字,
實際上測出來的資料基本在 35Gb/s 到 55Gb/s 之間,視情況浮動。

效能的變化和虛擬網路裝置的鏈路和型別有關,或許和預設配置的區別也有關係。

另外 TUN 裝置這裡沒有測,ethtool tun0 查到的值是比較離譜的 10Mb/s,但是感覺不太可能這麼慢,有時間可以再測一波看看。

參考

相關文章