tun/tap是一个虚拟网卡驱动,为用户态程序提供网络数据包的发送和接收能力,tun/tap 包含一个字符设备驱动和一个网卡驱动,利用网卡驱动接收来自tcp/ip协议栈的网络分包并发送或者将接收到的网络分包传给协议栈处理。
用户可以通过ip tuntao add veth1 mode tap的方式来创建tap设备
其驱动是一个ko,源码在drivers/net/tun.c中
从下面的入口函数可以和明显看到tun分为字符设备和模拟物理链路部分
static int __init tun_init(void)
{
int ret = 0;
pr_info("%s, %s\n", DRV_DESCRIPTION, DRV_VERSION);
#模拟物理链路部分用于数据的发送和接收
ret = rtnl_link_register(&tun_link_ops);
if (ret) {
pr_err("Can't register link_ops\n");
goto err_linkops;
}
#注册字符设备,主要用于将从用户空间copy数据
ret = misc_register(&tun_miscdev);
if (ret) {
pr_err("Can't register misc device %d\n", TUN_MINOR);
goto err_misc;
}
#注册通知链
ret = register_netdevice_notifier(&tun_notifier_block);
if (ret) {
pr_err("Can't register netdevice notifier\n");
goto err_notifier;
}
return 0;
}
从字符设备的poll函数中可以看到是通过ptr_ring_empty来检测队列是否为null
static __poll_t tun_chr_poll(struct file *file, poll_table *wait)
{
struct tun_file *tfile = file->private_data;
struct tun_struct *tun = tun_get(tfile);
struct sock *sk;
__poll_t mask = 0;
if (!tun)
return EPOLLERR;
sk = tfile->socket.sk;
tun_debug(KERN_INFO, tun, "tun_chr_poll\n");
poll_wait(file, sk_sleep(sk), wait);
#检测队列是否为null,如果不为空的话,最后会调用tun_chr_read_iter来读,也就是从user space读到kernel space
if (!ptr_ring_empty(&tfile->tx_ring))
mask |= EPOLLIN | EPOLLRDNORM;
/* Make sure SOCKWQ_ASYNC_NOSPACE is set if not writable to
* guarantee EPOLLOUT to be raised by either here or
* tun_sock_write_space(). Then process could get notification
* after it writes to a down device and meets -EIO.
*/
if (tun_sock_writeable(tun, tfile) ||
(!test_and_set_bit(SOCKWQ_ASYNC_NOSPACE, &sk->sk_socket->flags) &&
tun_sock_writeable(tun, tfile)))
mask |= EPOLLOUT | EPOLLWRNORM;
if (tun->dev->reg_state != NETREG_REGISTERED)
mask = EPOLLERR;
tun_put(tun);
return mask;
}
如果需要发送数据的话,则调用tun_chr_write_iter
static ssize_t tun_chr_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct file *file = iocb->ki_filp;
struct tun_file *tfile = file->private_data;
struct tun_struct *tun = tun_get(tfile);
ssize_t result;
if (!tun)
return -EBADFD;
#新建skb 来发送数据
result = tun_get_user(tun, tfile, NULL, from,
file->f_flags & O_NONBLOCK, false);
tun_put(tun);
return result;
}
当调用ioctl中的TUNSETIFF
if (cmd == TUNSETIFF) {
ret = -EEXIST;
if (tun)
goto unlock;
ifr.ifr_name[IFNAMSIZ-1] = '\0';
#创建网络设备
ret = tun_set_iff(net, file, &ifr);
if (ret)
goto unlock;
if (copy_to_user(argp, &ifr, ifreq_len))
ret = -EFAULT;
goto unlock;
}
最终通过alloc_netdev_mqs来注册网络设备,这样就可以通过网口来发送和接收数据
static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
{
dev = alloc_netdev_mqs(sizeof(struct tun_struct), name,
NET_NAME_UNKNOWN, tun_setup, queues,
queues);
if (!dev)
return -ENOMEM;
}