【Linux5.4】【TUN】代码学习记录(12)--tun_attach,tun_struct,tun_file

【Linux5.4】【TUN】代码学习记录(12)–tun_attach,tun_struct,tun_file

tun_attach,attach有连接的意思,顾名思义这个函数用来连接。先看下代码

tun_attach

源码有些长,前面都是一些检验和判断功能,最后达成符合条件之后,在末尾处调用rcu_assign_pointer将tun_struct与tun_file结构体中两个指针互指,达成连接目的。

static int tun_attach(struct tun_struct *tun, struct file *file,
		      bool skip_filter, bool napi, bool napi_frags,
		      bool publish_tun)
{
	struct tun_file *tfile = file->private_data;
	struct net_device *dev = tun->dev;
	int err;

	err = security_tun_dev_attach(tfile->socket.sk, tun->security);
	if (err < 0)
		goto out;

	err = -EINVAL;
	if (rtnl_dereference(tfile->tun) && !tfile->detached)
		goto out;

	err = -EBUSY;
	if (!(tun->flags & IFF_MULTI_QUEUE) && tun->numqueues == 1)
		goto out;

	err = -E2BIG;
	if (!tfile->detached &&
	    tun->numqueues + tun->numdisabled == MAX_TAP_QUEUES)
		goto out;

	err = 0;

	/* Re-attach the filter to persist device */
	if (!skip_filter && (tun->filter_attached == true)) {
		lock_sock(tfile->socket.sk);
		err = sk_attach_filter(&tun->fprog, tfile->socket.sk);
		release_sock(tfile->socket.sk);
		if (!err)
			goto out;
	}

	if (!tfile->detached &&
	    ptr_ring_resize(&tfile->tx_ring, dev->tx_queue_len,
			    GFP_KERNEL, tun_ptr_free)) {
		err = -ENOMEM;
		goto out;
	}

	tfile->queue_index = tun->numqueues;
	tfile->socket.sk->sk_shutdown &= ~RCV_SHUTDOWN;

	if (tfile->detached) {
		/* Re-attach detached tfile, updating XDP queue_index */
		WARN_ON(!xdp_rxq_info_is_reg(&tfile->xdp_rxq));

		if (tfile->xdp_rxq.queue_index    != tfile->queue_index)
			tfile->xdp_rxq.queue_index = tfile->queue_index;
	} else {
		/* Setup XDP RX-queue info, for new tfile getting attached */
		err = xdp_rxq_info_reg(&tfile->xdp_rxq,
				       tun->dev, tfile->queue_index);
		if (err < 0)
			goto out;
		err = xdp_rxq_info_reg_mem_model(&tfile->xdp_rxq,
						 MEM_TYPE_PAGE_SHARED, NULL);
		if (err < 0) {
			xdp_rxq_info_unreg(&tfile->xdp_rxq);
			goto out;
		}
		err = 0;
	}

	if (tfile->detached) {
		tun_enable_queue(tfile);
	} else {
		sock_hold(&tfile->sk);
		tun_napi_init(tun, tfile, napi, napi_frags);
	}

	if (rtnl_dereference(tun->xdp_prog))
		sock_set_flag(&tfile->sk, SOCK_XDP);

	/* device is allowed to go away first, so no need to hold extra
	 * refcnt.
	 */

	/* Publish tfile->tun and tun->tfiles only after we've fully
	 * initialized tfile; otherwise we risk using half-initialized
	 * object.
	 */
	if (publish_tun)
		rcu_assign_pointer(tfile->tun, tun);
	rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile);
	tun->numqueues++;
	tun_set_real_num_queues(tun);
out:
	return err;
}

tun_file与tun_struct 的关系

那么为何要将tun与tfile互相连接,tun_file结构体与tun_struct结构体有什么相互关系?
首先TUN模块的设计是为了能让用户空间有一个方便的方式修改我们想控制的网络数据流,Linux网络协议栈是在内核里实现的,而TUN可以通过read函数在用户空间拿到内核协议栈处理的数据,通过write函数将修改的数据在交付给内核网咯协议栈。
为了实现过程,TUN需要两个重要的功能:
①文件操作,支持open、close、write、read等。
②网络功能,确切说是网卡功能,但因为它没有真实的的物理网卡存在,所以是一种模拟功能,也叫虚拟网卡。
TUN创建虚拟网卡是为了能和内核网络协议栈进行交互,例如:网络协议栈接收物理网卡数据,经过处理后发现是要交给虚拟网卡的,此时用户空间调用read就能得到这个数据。而当用户空间write数据时,数据是从虚拟网卡交给网络协议栈的,网络协议栈处理后找到一个出口将数据交付出去。

回想一下tfile的建立,tun_file是在tun_chr_open中创建的,并且tfile定义是file指针的私有数据。

static int tun_chr_open(struct inode *inode, struct file * file)
{
	...
	/* 创建tfile */
	tfile = (struct tun_file *)sk_alloc(net, AF_UNSPEC, GFP_KERNEL,
					    &tun_proto, 0);
	RCU_INIT_POINTER(tfile->tun, NULL);
	tfile->socket.file = file;
	file->private_data = tfile;//tfile成为file的私有数据
	...
}

tun_chr_open里面file与tfile进行了关联,file具备文件属性,同时TUN模块支持读写数据,所以需要一个地方承载数据,那么在tun_file结构体中使用了tx_ring,此外tun_file结构体融合了sock、socket等结构体,具备套接字功能。

tun_file

struct tun_file {
	struct sock sk;
	struct socket socket;
	struct tun_struct __rcu *tun;
	struct fasync_struct *fasync;
	/* only used for fasnyc 仅用于fasnyc */
	unsigned int flags;
	union {
		u16 queue_index;
		unsigned int ifindex;
	};
	struct napi_struct napi;
	bool napi_enabled;
	bool napi_frags_enabled;
	struct mutex napi_mutex;	/* Protects access to the above napi 保护对上述napi的访问 */
	struct list_head next;
	struct tun_struct *detached;
	struct ptr_ring tx_ring;
	struct xdp_rxq_info xdp_rxq;
};

但这还不够,TUN模块需要实现一个虚拟网卡,也就是net_device结构体,回想一下,net_device创建是在tun_set_iff中的,dev创建后,netdev_priv指定tun为dev的“私有数据”,而后tun->dev = dev,实现了dev与tun的互连。

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;
		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;
	...
}

此时,file与tfile,tun与dev完成了互连,再经过tun_attach,将tfile与tun互连,也就完成了file、tfile、tun、dev四个关键结构体互连。

此外在tun_attach中,执行完互连操作后,有一个计数器累加:

	if (publish_tun)
		rcu_assign_pointer(tfile->tun, tun);
	rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile);
	tun->numqueues++;
	tun_set_real_num_queues(tun);

这里要说明的时,tun_struct结构体内tfiles[MAX_TAP_QUEUES]是一个指针数组,那么在互连时,tun可以对应多个tfile,tun与tfile可以是一对多的关系。这就像网卡与应用程序的关系,网卡可以给多个应用程序提供服务。

tun_struct

struct tun_struct {
	struct tun_file __rcu	*tfiles[MAX_TAP_QUEUES];
	unsigned int            numqueues;
	unsigned int 		flags;
	kuid_t			owner;
	kgid_t			group;

	struct net_device	*dev;//网络设备
	netdev_features_t	set_features;
#define TUN_USER_FEATURES (NETIF_F_HW_CSUM|NETIF_F_TSO_ECN|NETIF_F_TSO| \
			  NETIF_F_TSO6)

	int			align;
	int			vnet_hdr_sz;
	int			sndbuf;
	struct tap_filter	txflt;
	struct sock_fprog	fprog;
	/* protected by rtnl lock */
	bool			filter_attached;
#ifdef TUN_DEBUG
	int debug;
#endif
	spinlock_t lock;
	struct hlist_head flows[TUN_NUM_FLOW_ENTRIES];//#define TUN_NUM_FLOW_ENTRIES 1024
	struct timer_list flow_gc_timer;
	unsigned long ageing_time;
	unsigned int numdisabled;
	struct list_head disabled;
	void *security;
	u32 flow_count;
	u32 rx_batched;
	struct tun_pcpu_stats __percpu *pcpu_stats;
	struct bpf_prog __rcu *xdp_prog;
	struct tun_prog __rcu *steering_prog;
	struct tun_prog __rcu *filter_prog;
	struct ethtool_link_ksettings link_ksettings;
};

说的再简单一些,应用程序使用TUN模块时,会创建自己的tfile,然后关联一个虚拟网卡dev的tun,每个应用程序都有自己的tfile,与其它tfile互不干扰,但是它们关联的可以是同一个tun。
通过tfile找到tun很简单,tfile->tun即可,但是反过来就需要一个标记,例如__tun_detach中

u16 index = tfile->queue_index;
ntfile = rtnl_dereference(tun->tfiles[index]);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值