linux内核学习笔记------邻居子系统(一)

在学习之前,可以先读读这两篇博客,特别是第一篇:

http://blog.csdn.net/minghe_uestc/article/details/7794852

http://blog.csdn.net/qy532846454/article/details/6806197

邻居子系统的结构由多个数据结构组成:

1、neigh_table结构:该结构主要用来存储与邻居协议相关的参数,功能函数,以及邻居项散列表;一个neigh_table结构实例对应一个邻居协议(例如:arp,arp_tbl),所有的实例都链接在全局链表neigh_tables中。linux内核源码剖析——tcp/ip实现对这个结构体做了很详细的描述,大家可以去看看

2、neighbor结构:邻居项就是使用该结构来描述,该结构存储了邻居的相关信息,包括状态,二层和三层协议地址,提供给三层协议的函数指针,还有定时器和缓存的二层首部等。注:一个邻居并不代表一个主机,而是一个三层协议地址,对于配置了多接口的主机,一个主机将对应多个三层协议地址。

3、neigh_ops结构:该结构相当于是邻居项函数指针表,由在邻居的生存周期中不同时期被调用的多个函数指针组成。其中有多个函数指针是实现三层与dev_queue_xmit之间的调用桥梁,适用于不同状态;

struct neigh_ops
{
	/*
	 * 标识所属的地址族,比如ARP为AF_INET等。
	 */
	int			family;
	/*
	 * 发送请求报文函数。在发送第一个报文时,需要
	 * 新的邻居项,发送报文被缓存到arp_queue队列中,
	 * 然后会调用solicit()发送请求报文。
	 */
	void			(*solicit)(struct neighbour *, struct sk_buff*);
	/*
	 * 当邻居项缓存着未发送的报文,而该邻居项又不可达时,
	 * 被调用来向三层报告错误的函数。ARP中为arp_error_report(),
	 * 最终会给报文发送方发送一个主机不可达的ICMP差错报文。
	 */
	void			(*error_report)(struct neighbour *, struct sk_buff*);
	/*
	 * 最通用的输出函数,可用于所有情况。此输出函数实现了
	 * 完整的输出过程,因此存在较多的校验与操作,以确保
	 * 报文的输出,因此该函数相对较消耗资源。此外,不要
	 * 将neigh_ops->output()与neighbour->output()混淆。
	 */
	int			(*output)(struct sk_buff*);
	/*
	 * 在确定邻居可达时,即状态为NUD_CONNECTED时使用的输出函数。
	 * 由于所有输出所需要的信息都已具备,因此该函数只是简单
	 * 地添加二层首部,也因此比output()快得多。
	 */
	int			(*connected_output)(struct sk_buff*);
	/*
	 * 在已缓存了二层首部的情况下使用的输出函数。
	 */
	int			(*hh_output)(struct sk_buff*);
	/*
	 * 实际上,以上几个输出接口,除了hh_output外,并不真正传输
	 * 数据包,只是在准备好二层首部之后,调用queue_xmit接口。
	 */
	int			(*queue_xmit)(struct sk_buff*);
};
注意solicit,output,connected_output,hh_output以及queue_xmit的区别以及联系,注释中也讲了很明显。

4、neigh_parms结构:该结构是邻居协议参数的配置块,用于存储可调节的邻居协议参数,如重传超时时间,proxy_queue队列长度等。一个邻居协议对应一个参数配置块,而每一个网络设备的ipv4的配置块中也存在一个存放默认值的邻居配置块。

5、pneigh_entry结构:该结构用来保存允许代理的条件,只有和结构中的接收设备以及目标地址想匹配才能代理。所有的pneigh_entry实例都存储在邻居表的phash_buckets散列表中。称之为代理向,可通过ip neigh add proxy命令添加。

6、hh_cache结构:该结构用来缓存二层首部,这样就可以复制而不是逐个域的设置二层首部,从而加速报文的输出,但也并非所有的设备驱动程序都需要缓存二层首部。

邻居表是由neigh_table_init初始化,对于arp_tbl的初始化,在arp模块初始化时由arp_init

void __init arp_init(void)
{
	neigh_table_init(&arp_tbl);

	dev_add_pack(&arp_packet_type);
	arp_proc_init();
#ifdef CONFIG_SYSCTL
	neigh_sysctl_register(NULL, &arp_tbl.parms, NET_IPV4,
			      NET_IPV4_NEIGH, "ipv4", NULL, NULL);
#endif
	register_netdevice_notifier(&arp_netdev_notifier);
}

void neigh_table_init(struct neigh_table *tbl)
{
	struct neigh_table *tmp;

	neigh_table_init_no_netlink(tbl);
	write_lock(&neigh_tbl_lock);
	for (tmp = neigh_tables; tmp; tmp = tmp->next) {
		if (tmp->family == tbl->family)
			break;
	}
	tbl->next	= neigh_tables;
	neigh_tables	= tbl;
	write_unlock(&neigh_tbl_lock);

	if (unlikely(tmp)) {
		printk(KERN_ERR "NEIGH: Registering multiple tables for "
		       "family %d\n", tbl->family);
		dump_stack();
	}
}
从上面的源码看出,arp初始化首先就会调用neigh_table_init对邻居表进行初始化,而neigh_table_init也比较简单,主要就是从neigh_tables中查找,看此邻居表是否已经存在,如果存在会报错,否则就会把该邻居表插入到neigh_tables中;在neigh_table_init_no_netlink这个函数中会做一些初始化的工作,分配缓存,初始化定时器的功能,创建存储邻居项和代理项的hash_buckets,phash_buckets;

邻居项存在一种状态机,邻居项都有一个对于管理和维护邻居表来说非常重要的成员,nud_state,用来表示该邻居项当前所处的状态。具体的状态迁移图在上面提到的那本书中有,并且在第二篇博客中博主结合arp协议进行描绘。下面依依介绍这几个状态:

1、NUD_NONE:邻居项刚建立时处于的状态,在该状态下,还没有硬件地址可以用,所以还不能发送请求报文。一旦有报文要输出到该邻居,便会出发对该邻居硬件地址的请求,进入NUD_INCOMPLETE状态,并缓存发送的报文

2、NUD_INCOMPLETE:该状态是请求报文已发送,但尚未收到应答的状态。该状态下还没解析到硬件地址,因此尚无可用硬件地址,如果有报文要输出到该邻居,会将其缓存起来。这个状态会启动一个定时器,如果在定时器到期时还没有接收到邻居的回应,则会重复发送请求报文,进入NUD_REACHABLE,否则发送请求报文的次数打到上限,便会进入NUD_FAILED

3、NUD_REACHABEL:该状态以及得到并缓存了邻居的硬件地址。进入该状态首先设置邻居项相关的output函数(该状态使用neighbors_ops结构的connectd_outpt),然后查看是否存在要发送给该邻居的报文。如果在该状态下闲置时间达到上限,便会进入NUD_STATLE

4、NUD_STATLE:该状态一旦有报文要输出到该邻居,则会进入NUD_DELAY并将该报文输出。如果在该状态下闲置时间达到上限,且此时的引用计数为1,则通过垃圾回收机制将其删除,在该状态下,报文的输出不收限制,使用慢速发送过程

5、NUD_DELAY:该状态下表示NUD_STATE状态下发送的报文已经发出,需得到邻居的可达性确认的状态。在为接收到邻居的应答或确认时也会定时地重发请求,如果发送请求报文的次数到上限,如果收到邻居的应答,进入NUD_REACHABEL,否则进入NUD_FAILED,在该状态下,报文的输出不收限制,使用慢速发送过程

还有几种状态比较容易理解,也就不特意列出了。



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值