本文共 2704 字,大约阅读时间需要 9 分钟。
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来检测队列是否为nullstatic __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_iterstatic 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; }
转载地址:http://lsnmi.baihongyu.com/