tun/tap 设备

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tiantao2012/article/details/82939262
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;
	
}

 

阅读更多
换一批

没有更多推荐了,返回首页