TUN/TAP 虚拟网络
介绍
TUN/TAP 是 Linux 内核虚拟网络设备。其中 TUN 是点对点设备,TAP 是以太网设备;tun 设备不需要 mac 地址,同一广播域只能有两个设备,并且传输信息无以太网帧头;
用户空间存在一个字符设备 /dev/net/tun
,可以通过对它进行 ioctl 配置生成虚拟网络设备,如 tun0、tap0 等;
准备
内核默认支持,如果没有就按照如下路径打开:
Device Drivers => Network device support => Universal TUN/TAP device driver support
确保支持后,如果没有发现 /dev/net/tun
设备节点,那就手动创建一个:
1
2
# c 示为字符设备,10 200 别是主设备号和次设备号
mknod /dev/net/tun c 10 200
开始编程
编写 c 文件
编写 test_tun.c 如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <assert.h>
#include <fcntl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
int tun_alloc(char *dev, int flags)
{
assert(dev != NULL);
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 (*dev != '\0'){
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
}
if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0){
close(fd);
return err;
}
// 一旦设备开启成功,系统会给设备分配一个名称,对于tun设备,一般为tunX,X为从0开始的编号;
// 对于tap设备,一般为tapX
strcpy(dev, ifr.ifr_name);
return fd;
}
int main()
{
int tun_fd, nread;
char buffer[4096];
char tun_name[IFNAMSIZ] = "mynet";
//tun_name[0] = '\0'; //截断后将自动命名 tun/tap 设备
/* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
* IFF_NO_PI - Do not provide packet information
*/
tun_fd = tun_alloc(tun_name, IFF_TUN | IFF_NO_PI);
if (tun_fd < 0){
perror("Allocating interface");
exit(1);
}
printf("Open tun/tap device: %s\n", tun_name);
while (1){
unsigned char ip[4];
// 收包
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);
// 简单对收到的包调换一下顺序
memcpy(ip, &buffer[12], 4);
memcpy(&buffer[12], &buffer[16], 4);
memcpy(&buffer[16], ip, 4);
buffer[20] = 0;
*((unsigned short *)&buffer[22]) += 8;
// 发包
nread = write(tun_fd, buffer, nread);
printf("Write %d bytes to tun/tap device\n", nread);
}
return 0;
}
进行测试
-
编译生成 test_tun;
1
gcc test_tun.c -o test_tun
-
运行
./test_tun
会打印 TUN name is mynet,创建了虚拟网络设备 mynet;可以使用
ip addr show
查看所有的网络设备; -
设置虚拟网络设备的 IP
1
sudo ip addr add 192.168.2.2/24 dev mynet
默认创建虚拟网络设备后,其状态为 down,需要手动 up:
1
sudo ip link set dev mynet up
-
ping 一下虚拟设备
需要网络状态为 up
1
ping -c3 192.168.2.2 -I mynet
使用软件包管理虚拟网络设备
tunctl
安装:
1
apt-get install uml-utilities
用法:
1
2
3
4
5
6
7
# -p 创建 tap 设备,默认
# -n 创建 tun 设备
# -t 指定创建的 tap/tun 设备名称
# -d 删除指定接口
tunctl -t name
tunctl -n -t name
tunctl -d name
[推荐] ip tuntap
安装:
属于 iproute2 工具箱,一般自带;
创建 tap/tun 设备:
1
2
Copyip tuntap add dev tap0 mod tap # 创建 tap
ip tuntap add dev tun0 mod tun # 创建 tun
删除 tap/tun 设备:
1
2
Copyip tuntap del dev tap0 mod tap # 删除 tap
ip tuntap del dev tun0 mod tun # 删除 tun
PS: user
和 group
参数和 tunctl
的 -u、 -g 参数是一样的;
bridge-utils 网桥
安装
1
sudo apt install bridge-utils
使用
1
2
3
4
5
6
7
brctl 常规用法:
addbr #添加一个虚拟网桥
addif #为虚拟网桥添加物理端口
delbr #删除虚拟网桥,但是,在删除网桥的时候保证网桥是down的
delif #删除虚拟网桥上的一个物理接口
show #查看网桥的相关配置
stp #开启或关闭虚拟网桥的生成树功能
环境
PC 机虚拟网卡 vEthernet(WSL) 的 IP 为 172.20.240.1/20
;
WSL 中有一网卡 eth0 的 IP 为 172.20.245.58/20
;
实验
在 WSL 中虚拟一个网桥 br0,并将 WSL 中的网卡 eth0 加入到网桥:
1
2
3
4
5
6
7
sudo brctl addbr br0
sudo brctl addif br0 eth0
# 展示全部网桥
brctl show
# 展示 br0 网桥
brctl showstp br0
接着给 br0 添加 IP 和网关,注意网关需要填写 vEthernet 的 IP:
1
2
3
4
sudo ip addr add 172.20.240.100/20 brd 172.20.240.1 dev br0
# 别忘了激活网桥设备
sudo ip link set dev br0 up
成功后,在 WSL 中可以 ping 通网关 vEthernet,PC 也可以 ping 通 br0;
扩展
在 WSL 中新建一个虚拟 TAP 网络设备 tap0,把它挂载到 br0 网桥,在 PC 机上访问它;
1
2
3
4
5
6
sudo tunctl -t tap0
sudo brctl addif br0 tap0
# 配置 IP,其中网关 IP 需要为 br0 的 IP
sudo ip addr add 172.20.240.101/20 brd 172.20.240.100 dev tap0
sudo ip link set dev tap0 up