独立博客地址: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 读写主机内核协议栈的数据。
一个 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 指令。