【Linux5.4】【TUN】代码学习记录(11)–tun_set_iff
tun_set_iff
tun_set_iff仅在__tun_chr_ioctl中当标识符为TUNSETIFF时调用过。
tun_set_iff主要功能就是按需创建一个网络设备struct net_device *dev。
函数代码过长,分前后两部分看
上半部分
dev = __dev_get_by_name(net, ifr->ifr_name)是查找是否已经存在设备名为ifr->ifr_name的虚拟网卡。
如果已存在,就要区分情况,如果虚拟网卡设备支持IFF_MULTI_QUEUE,而且当前调用tun_set_iff的应用程序也设置了IFF_MULTI_QUEUE,那么应用程序可以直接使用以创建的虚拟网卡(也就是tfile与tun互联),调用tun_attach关联当前应用程序的struct tun_file *tfile与虚拟网卡struct tun_struct *tun两个指针,tun_attach最后参数是true,表明*tun指针已经初始化完成,可以和*tfile进行互连。关于tun_attach后续再写。
static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
{
...
/* 按照设备名称找到设备指针dev */
dev = __dev_get_by_name(net, ifr->ifr_name);
/* 找到说明设备已存在 */
if (dev) {
if (ifr->ifr_flags & IFF_TUN_EXCL)//IFF_TUN_EXCL表示打开 TUN/TAP 接口时以排他模式进行,即只有一个进程可以使用该接口。
return -EBUSY;
if ((ifr->ifr_flags & IFF_TUN) && dev->netdev_ops == &tun_netdev_ops)
tun = netdev_priv(dev);
else if ((ifr->ifr_flags & IFF_TAP) && dev->netdev_ops == &tap_netdev_ops)
tun = netdev_priv(dev);
else
return -EINVAL;
if (!!(ifr->ifr_flags & IFF_MULTI_QUEUE) !=
!!(tun->flags & IFF_MULTI_QUEUE))
return -EINVAL;
if (tun_not_capable(tun))
return -EPERM;
err = security_tun_dev_open(tun->security);
if (err < 0)
return err;
err = tun_attach(tun, file, ifr->ifr_flags & IFF_NOFILTER,
ifr->ifr_flags & IFF_NAPI,
ifr->ifr_flags & IFF_NAPI_FRAGS, true);
if (err < 0)
return err;
if (tun->flags & IFF_MULTI_QUEUE &&
(tun->numqueues + tun->numdisabled > 1)) {
/* One or more queue has already been attached, no need
* to initialize the device again.
*/
netdev_state_change(dev);
return 0;
}
/* 这里代码可能是应对一种特殊情况,即设备开启了持续模式但是不支持IFF_MULTI_QUEUE,此时设备已创建
* 但是创建的应用结束了,设备依然存在,而且新的应用程序调用tun_set_iff并设置了相同的ifr.ifr_name,
* 那么相应的tun设备flags就需要与新的应用设置的flags一致。
*/
tun->flags = (tun->flags & ~TUN_FEATURES) |
(ifr->ifr_flags & TUN_FEATURES);
netdev_state_change(dev);
}
...
}
下半部分
如果__dev_get_by_name(net, ifr->ifr_name)未能找到设备名为ifr->ifr_name的虚拟网卡,就需要创建一个新的虚拟网卡。
首先alloc_netdev_mqs申请新的内存用来创建dev,之后初始化dev。
接着netdev_priv(dev)获取*tun的位置,后续代码对tun和dev进行初始化。
调用tun_attach,此时tun还未完全准备好,因此最后参数是false,tun与tfile只能进行半连接,将tun->tfiles[tun->numqueues]指向当前的tfile。
当完成虚拟网卡设备注册register_netdevice(tun->dev),tun准备完毕,rcu_assign_pointer(tfile->tun, tun)将tfile->tun指向当前的tun,完成互连。
static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
{
...
else { /* 设备不存在,需要创建设备 */
char *name;
unsigned long flags = 0;
int queues = ifr->ifr_flags & IFF_MULTI_QUEUE ?
MAX_TAP_QUEUES : 1;
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
return -EPERM;
err = security_tun_dev_create();
if (err < 0)
return err;
/* Set dev type */
if (ifr->ifr_flags & IFF_TUN) {
/* TUN device */
flags |= IFF_TUN;
name = "tun%d";
} else if (ifr->ifr_flags & IFF_TAP) {
/* TAP device */
flags |= IFF_TAP;
name = "tap%d";
} else
return -EINVAL;
if (*ifr->ifr_name)
name = ifr->ifr_name;
/* 申请内存创建网络设备 */
dev = alloc_netdev_mqs(sizeof(struct tun_struct), name,
NET_NAME_UNKNOWN, tun_setup, queues,
queues);
if (!dev)
return -ENOMEM;
err = dev_get_valid_name(net, dev, name);
if (err < 0)
goto err_free_dev;
dev_net_set(dev, net);
dev->rtnl_link_ops = &tun_link_ops;
dev->ifindex = tfile->ifindex;
dev->sysfs_groups[0] = &tun_attr_group;
tun = netdev_priv(dev);
tun->dev = dev;
tun->flags = flags;
tun->txflt.count = 0;
tun->vnet_hdr_sz = sizeof(struct virtio_net_hdr);
tun->align = NET_SKB_PAD;
tun->filter_attached = false;
tun->sndbuf = tfile->socket.sk->sk_sndbuf;
tun->rx_batched = 0;
RCU_INIT_POINTER(tun->steering_prog, NULL);
tun->pcpu_stats = netdev_alloc_pcpu_stats(struct tun_pcpu_stats);
if (!tun->pcpu_stats) {
err = -ENOMEM;
goto err_free_dev;
}
spin_lock_init(&tun->lock);
err = security_tun_dev_alloc_security(&tun->security);
if (err < 0)
goto err_free_stat;
tun_net_init(dev);
tun_flow_init(tun);
dev->hw_features = NETIF_F_SG | NETIF_F_FRAGLIST |
TUN_USER_FEATURES | NETIF_F_HW_VLAN_CTAG_TX |
NETIF_F_HW_VLAN_STAG_TX;
dev->features = dev->hw_features | NETIF_F_LLTX;
dev->vlan_features = dev->features &
~(NETIF_F_HW_VLAN_CTAG_TX |
NETIF_F_HW_VLAN_STAG_TX);
/* 将tun->flags设置为用户输入的ifr->ifr_flags */
tun->flags = (tun->flags & ~TUN_FEATURES) |
(ifr->ifr_flags & TUN_FEATURES);
INIT_LIST_HEAD(&tun->disabled);
err = tun_attach(tun, file, false, ifr->ifr_flags & IFF_NAPI,
ifr->ifr_flags & IFF_NAPI_FRAGS, false);
if (err < 0)
goto err_free_flow;
err = register_netdevice(tun->dev);
if (err < 0)
goto err_detach;
/* free_netdev() won't check refcnt, to aovid race
* with dev_put() we need publish tun after registration.
*/
rcu_assign_pointer(tfile->tun, tun);
}
netif_carrier_on(tun->dev);
tun_debug(KERN_INFO, tun, "tun_set_iff\n");
/* Make sure persistent devices do not get stuck in
* xoff state.
* 确保持久化设备不会卡在xoff状态。
*/
if (netif_running(tun->dev))
netif_tx_wake_all_queues(tun->dev);
strcpy(ifr->ifr_name, tun->dev->name);
return 0;
err_detach:
tun_detach_all(dev);
/* register_netdevice() already called tun_free_netdev() */
goto err_free_dev;
err_free_flow:
tun_flow_uninit(tun);
security_tun_dev_free_security(tun->security);
err_free_stat:
free_percpu(tun->pcpu_stats);
err_free_dev:
free_netdev(dev);
return err;
}