邻居子系统之核心数据结构

整个邻居子系统中核心的数据结构及其关系如下图所示:
邻居子系统之核心数据结构_第1张图片

邻居协议: struct neigh_table

每个具体的邻居协议都需要实例化一个该结构,并将其注册到邻居子系统的全局链表:neigh_tables,后续我们称该结构为邻居协议。邻居协议保存了几乎所有邻居子系统和具体的邻居协议交互需要的所有信息。

/*
 *	neighbour table manipulation
 */
struct neigh_table
{
	// 系统中所有的邻居协议用该字段组织到全局链表neigh_tables中
	struct neigh_table *next;
	// 应用的协议族,必须由具体的邻居协议指定
	int	family;
	// 邻居项的大小,具体的邻居协议可以在struct neighbour的基础上继续扩充
	int	entry_size;
	// L3地址的长度,在相关数据结构中,L3地址也叫key,因为其会参与哈希索引的计算
	int	key_len;
	__u32			(*hash)(const void *pkey, const struct net_device *);
	// 邻居协议提供的初始化函数,邻居子系统在创建邻居项时会回调该函数
	int	(*constructor)(struct neighbour *);
	int			(*pconstructor)(struct pneigh_entry *);
	void			(*pdestructor)(struct pneigh_entry *);
	void			(*proxy_redo)(struct sk_buff *skb);
	// 下面pde文件节点的名字
	char *id;
	// 邻居协议配置,由各个具体的邻居协议提供
	struct neigh_parms parms;
	/* HACK. gc_* shoul follow parms without a gap! */
	int			gc_interval;
	int			gc_thresh1;
	int			gc_thresh2;
	int			gc_thresh3;
	unsigned long		last_flush;
	struct timer_list 	gc_timer;
	struct timer_list 	proxy_timer;
	struct sk_buff_head	proxy_queue;
	atomic_t entries; // 哈希表中邻居项的个数
	// 用于保护邻居项哈希表
	rwlock_t lock;
	unsigned long		last_rand;
	// 每个邻居协议的邻居项大小都不同,用高速缓存池来分配邻居项内存
	struct kmem_cache *kmem_cachep;
	// 邻居协议统计信息
	struct neigh_statistics	*stats;
	// 邻居项哈希表
	struct neighbour **hash_buckets;
	// 邻居项哈希表的掩码,即如果哈希表的桶大小为0x100,那么hash_mask就是0xFF(实际大小减1)
	unsigned int hash_mask;
	// 用于哈希计算的随机值
	__u32 hash_rnd;
	unsigned int		hash_chain_gc;
	struct pneigh_entry	**phash_buckets;
#ifdef CONFIG_PROC_FS
    // /proc/net/stat/xxx_cache(对于IPv4是arp_cache,IPv6是ndisc_cache)文件
	struct proc_dir_entry *pde;
#endif
};

邻居项:struct neighbour

每个邻居项neighbour表示了一个L3地址到L2地址的映射。

struct neighbour
{
	struct neighbour *next; // 邻居项已哈希表方式组织,同一个冲突链的邻居项用next指针连接
	struct neigh_table *tbl; // 指向邻居协议
	struct neigh_parms *parms; // 邻居参数
	struct net_device *dev; // 该邻居项属于哪个设备,邻居项会持有网络设备的引用
	unsigned long used; // 记录最近一次使用该邻居项时的jiffies
	unsigned long confirmed; // 记录最近一次该邻居项进入CONNECTED状态时的jiffies
	unsigned long updated; // 记录最近一次更新该邻居项状态时的jiffies
	__u8			flags;
	__u8 nud_state; // 邻居项状态,非常重要,后面单独介绍
	__u8 type; // 要映射的L3地址类型:广播、多播还是单播等
	__u8			dead;
	// 验证邻居可达性或者解析邻居地址时,都需要发送solicitations请求,该参数指定了当前还可以
	// 发送多少次solicitations请求,初始值来自配置参数中的ucast_probes/mcast_probes
	atomic_t probes;
	// 保护邻居项中各个字段的更新
	rwlock_t lock;
	// L3地址映射的L2地址
	unsigned char ha[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))];
	struct hh_cache		*hh;
	atomic_t refcnt;
	// 邻居协议输出函数指针,该指针的值会随着邻居项的状态变化而不同
	int	(*output)(struct sk_buff *skb);
	// skb缓存队列。当发送时,如果邻居项状态尚未就绪(L2地址还不知道),则邻居子系统先缓存数据包
	struct sk_buff_head	arp_queue;
	// 该定时器用于邻居解析和可达性验证(定时器函数中发送solicitations请求)
	struct timer_list timer;
	struct neigh_ops *ops;
	// 实际上就是要映射的L3地址,叫key是因为其也参与哈希表索引计算
	u8	primary_key[0];
};

有几个关键点需要理解:

  1. 每个邻居项代表的是L3地址到L2地址的映射,这里的L3地址和L2地址都是网络中其它设备的地址,在创建邻居项之前,肯定已经知道本机应该由哪个设备和该L3/L2地址通信了,所以邻居项都是和某个网络设备关联的。

struct neigh_ops

如上,每个邻居项的ops字段都关联一个该结构。

struct neigh_ops
{
	int	family;
	void (*solicit)(struct neighbour *, struct sk_buff*);
	void (*error_report)(struct neighbour *, struct sk_buff*);
	int	(*output)(struct sk_buff*);
	int	(*connected_output)(struct sk_buff*);
	int	(*hh_output)(struct sk_buff*);
	int	(*queue_xmit)(struct sk_buff*);
};

邻居参数: struct neigh_parms

邻居协议(struct neigh_table)在向系统注册时都提供了协议级别的邻居参数配置(字段parms)。

邻居参数最终作用于邻居项,它控制着邻居项方方面面的行为,邻居项的parms指针指向该邻居项使用的邻居协议配置。邻居项的parms是在创建邻居项时在neigh_alloc()中由neigh_parms_clone()生成的,该函数实现非常简单,就是增加邻居协议的parms的引用计数,然后返回其指针,所以说邻居项的parms实际指向的就是邻居协议的配置。(虽然实际是来自网络设备的配置,但是网络设备的配置也是来自邻居协议的配置,所以这么说也合理)

struct neigh_parms
{
	struct net *net;
	struct net_device *dev;
	struct neigh_parms *next;
	int	(*neigh_setup)(struct neighbour *);
	void (*neigh_cleanup)(struct neighbour *);
	struct neigh_table *tbl;

	void *sysctl_table;

	int dead;
	// 比如邻居项会持有其邻居协议的配置的引用计数
	atomic_t refcnt;
	struct rcu_head rcu_head;

	int	base_reachable_time;
	// solicitations请求可能并未得到响应,可以重试几次,该参数指定重试间隔
	int	retrans_time;
	int	gc_staletime;
	int	reachable_time;
	int	delay_probe_time;

	int	queue_len;
	// ucast_probes表示为了验证邻居是否可达,可以发送的单播solicitations请求的数量
	int	ucast_probes;
	// 当可达性由用户态程序(如ARPD)控制时,该参数指定了用户态可以发送的solicitations请求的数量
	int	app_probes;
	// mcast_probes指定了为了解析一个邻居地址,可以发出的多播(或者广播)solicitations请求的数量
	int	mcast_probes;
	int	anycast_delay;
	int	proxy_delay;
	int	proxy_qlen;
	int	locktime;
};

你可能感兴趣的:(邻居子系统)