☀️苏州程序大白解析Linux 中的虚拟网络接口☀️《❤️记得收藏❤️》

Posted 苏州程序大白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了☀️苏州程序大白解析Linux 中的虚拟网络接口☀️《❤️记得收藏❤️》相关的知识,希望对你有一定的参考价值。

目录

🏳️‍🌈开讲啦!!!!🏳️‍🌈苏州程序大白🏳️‍🌈

🌟博主介绍

💂 个人主页:苏州程序大白

🤟作者介绍:中国DBA联盟(ACDU)成员,CSDN全国各地程序猿(媛)聚集地管理员。目前从事工业自动化软件开发工作。擅长C#、Java、机器视觉、底层算法等语言。2019年成立柒月软件工作室。

💬如果文章对你有帮助,欢迎关注、点赞、收藏(一键三连)和C#、Halcon、python+opencv、VUE、各大公司面试等一些订阅专栏哦

🎗️ 承接各种软件开发项目

💅 有任何问题欢迎私信,看到会及时回复

👤 微信号:stbsl6,微信公众号:苏州程序大白

🎯 想加入技术交流群的可以加我好友,群里会分享学习资料

前言


注意: 本文中使用 ip 命令创建或修改的任何网络配置,都是未持久化的,主机重启即消失。

Linux 具有强大的虚拟网络能力,这也是 openstack 网络、docker 容器网络以及 kubernetes 网络等虚拟网络的基础。

这里介绍 Linux 常用的虚拟网络接口类型:TUN/TAP、bridge、veth、ipvlan/macvlan、vlan 以及 vxlan/geneve。

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 与 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,在下一小节将给出容器网络结构图。

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 发送出去。

整个流程跑完,数据包都不需要经过宿主机的协议栈,效率高。

+------------------------------------------------+--------------------------------

以上是关于☀️苏州程序大白解析Linux 中的虚拟网络接口☀️《❤️记得收藏❤️》的主要内容,如果未能解决你的问题,请参考以下文章

☀️苏州程序大白用万字解析Python网络编程与Web编程☀️《❤️记得收藏❤️》

☀️苏州程序大白一文解析Java多线程☀️《❤️记得收藏❤️》

苏州程序大白用2万字解析数据结构和八大排序算法☀️《❤️记得收藏❤️》

苏州程序大白总结Vue零基础到高阶第一节☀️《❤️记得收藏❤️》

苏州程序大白总结Vue零基础到高阶第二节☀️《❤️记得收藏❤️》

☀️苏州程序大白一文让你学会Java Servlet基础☀️《❤️记得收藏❤️》