###1. linux 虚拟网络接口
在linux的网络设备驱动框架里面, 使用一个net_device来代表一个网络设备接口, 因此, 一个物理网卡对应着一个net_device结构
虚拟网络接口是指一个网络接口的net_device没有直接对应的物理设备
###2. TUN/TAP
TUN/TAP 会生成虚拟网络接口, 配合“/dev/net/tun”设备文件, 所有通过 tun/tap 网络接口发送的数据包, 都会进入“/dev/net/tun”设备文件的缓冲区, 用户空间可以从“/dev/net/tun”设备文件中读出读出这些数据包, 而写入“/dev/net/tun”设备文件的数据包, 则会进入到linux的网络协议栈进行处理
tun/tap的区别如下:
- tun表示虚拟的是点对点设备,工作在三层网络上,具有处理IP包的能力
- tap表示虚拟的是以太网设备,工作在二层网络上,具有直接处理以太帧的能力
tun/tap的内核代码位于 “driver/net/tun.c”
###3. /dev/net/tun
“/dev/net/tun”是一个misc 设备, minor id固定为200
crw-rw-rwT 1 root root 10, 200 7月 31 09:11 tun
static const struct file_operations tun_fops = {
......
.aio_read = tun_chr_aio_read,
.aio_write = tun_chr_aio_write,
.unlocked_ioctl = tun_chr_ioctl,
.open = tun_chr_open,
.release = tun_chr_close,
......
};
struct miscdevice tun_miscdev = {
.minor = TUN_MINOR,
.name = "tun",
.nodename = "net/tun",
.fops = &tun_fops,
};
对于“/dev/net/tun”设备来说, 最重要的操作就是 read / write / ioctl了, 其中ioctl用于添加/删除 tun/tap 接口
###4. 添加/删除 tun/tap 网络接口
tun/tap 的添加通过使用ioctl 发送 TUNSETIFF 命令来实现
例如,添加一个tun网络接口的示例
int tun_alloc(char *dev)
{
struct ifreq ifr;
int fd, err;
......
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
if (*dev)
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
…… return fd;
}
ioctl的执行流程
/* userspace */
ioctl( TUNSETIFF )
/* kernel */
tun_chr_ioctl()
__tun_chr_ioctl()
tun_set_iff()
tun_net_init()
static void tun_net_init(struct net_device *dev)
{
......
switch (tun->flags & TUN_TYPE_MASK) {
case TUN_TUN_DEV:
dev->netdev_ops = &tun_netdev_ops;
.......
break;
case TUN_TAP_DEV:
dev->netdev_ops = &tap_netdev_ops;
break;
......
}
}
static const struct net_device_ops tun_netdev_ops = {
.ndo_uninit = tun_net_uninit,
.ndo_open = tun_net_open,
.ndo_stop = tun_net_close,
.ndo_start_xmit = tun_net_xmit,
.ndo_change_mtu = tun_net_change_mtu,
.ndo_fix_features = tun_net_fix_features,
.ndo_select_queue = tun_select_queue,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = tun_poll_controller,
#endif
};
static const struct net_device_ops tap_netdev_ops = {
.ndo_uninit = tun_net_uninit,
.ndo_open = tun_net_open,
.ndo_stop = tun_net_close,
.ndo_start_xmit = tun_net_xmit,
.ndo_change_mtu = tun_net_change_mtu,
.ndo_fix_features = tun_net_fix_features,
.ndo_set_rx_mode = tun_net_mclist,
.ndo_set_mac_address = eth_mac_addr,
.ndo_validate_addr = eth_validate_addr,
.ndo_select_queue = tun_select_queue,
#ifdef CONFIG_NET_POLL_CONTROLLER
.ndo_poll_controller = tun_poll_controller,
#endif
};
根据 ioctl 的 TUNSETIFF cmd 附带的IFF_TUN 或者 IFF_TAP flag来添加一个 tun或者tap网络接口, TUNSETIFF 支持如下的flag
- IFF_TUN : 创建一个点对点设备, 和 IFF_TAP互斥
- IFF_TAP : 创建一个以太网设备, 和 IFF_TUN互斥
- IFF_NO_PI : 不包含包信息,默认的每个数据包当传到用户空间时,都将包含一个附加的包头来保存包信息, 可和其它flag组合
- IFF_ONE_QUEUE : 采用单一队列模式,即当数据包队列满的时候,由虚拟网络设备自已丢弃以后的数据包直到数据包队列再有空闲, 可和其它flag组合
对于上述的 IFF_NO_PI, 当其没有开启时, 附加的包头信息为:
struct tun_pi {
unsigned short flags;
unsigned short proto;
};
添加的tun/tap网络接口按照 tun0, tun1 … 或者 tap0 tap1 …的形式命名, 同其它的网络接口一样, 可以使用ifconfig ip等命令来配置ip地址, 路由等
tun/tap 网络接口的删除则是通过释放打开 “/dev/net/tun” 设备文件时获取的fd来实现的, 调用流程为
/* userspace */
close()
/* kernel */
tun_chr_close()
tun_detach()
__tun_detach()
在__tun_detach()中会 unregister tun/tap网络接口对应的net_device
还可以使用tunctl命令管理tun/tap网络接口
/* 创建名为tap0的虚拟网卡, 拥有者为sven */
$ tunctl -t tap0 -u sven
/* 删除名为tap0 的虚拟网卡 */
$ tunctl -d tap0
/* tun/tap 虚拟网卡同其它网卡一样, 使用ifconfig进行设置, 例如 */
$ ifocnfig tap0 192.168.1.165 netmask 255.255.255.0 up
###5. tun/tap 网络接口上的数据发送
数据包经过路由后, 若选择 tun/tap 网络接口作为出口, 最终需要调用 net_device.ndo_start_xmit() 来发送数据, 即调用tun_net_xmit(), 在其中会
- 将skb加入到readq链表中, 然后唤醒等待的read进程,(“/dev/net/tun” 的read进程会在tun_do_read()中阻塞, 等待被数据唤醒)
- “/dev/net/tun” 的read进程在tun_do_read()中被唤醒后, 从skb 数据包队列中, 取出数据, 调用tun_put_user()将数据拷贝到用户空间buffer (对“/dev/net/tun” 调用read()时指定的buffer)
###6. tun/tap 网络接口上的数据接收
向 “/dev/net/tun”写入数据包
/* userspace */
write()
/* kernel */
tun_chr_aio_write()
tun_get_user()
tun_alloc_skb()
skb_copy_datagram_from_iovec()
netif_rx_ni()
可见, tun_get_user()中, 分配skb结构,从用户空间拷贝数据,最后调用netif_rx_ni(), 将数据包发往上层的网络协议栈处理, 最后, 应用程序在用户空间通过socket接口接收数据
###7. tun/tap 网络接口的应用
从上面的tun/tap网络接口的数据包收发流程可以看到:
- 数据包写入“/dev/net/tun”设备文件后, 到达linux网络协议栈, 最后通过socket接口被应用程序接收, 这一过程中, 可以逐层剔除各层的协议头, 得到应用层数据
- 应用层数据通过socket接口进入网络协议栈, 经路由后, 选择tun/tap接口作为出口设备, 到达“/dev/net/tun”设备文件的缓冲区, 可在用户空间读出, 得到以太帧或者ip数据包, 这一过程中,逐层添加协议头
上面的两个相反的过程, 可以应用于实现网络隧道, vtun和openvpn等项目都使用了 tun/tap, 另外, 一些虚拟机的虚拟化网络也使用到了 tun/tap 技术