Linux 中的虛擬網絡接口

獨立博客閱讀地址: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 <[email protected]>
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,但是感覺不太可能這麼慢,有時間可以再測一波看看。

參考

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