linux内核协议栈 邻居协议之通用邻居项的状态机机制【核心】

linux内核协议栈 同时被 3 个专栏收录
78 篇文章 1 订阅
7 篇文章 0 订阅
44 篇文章 0 订阅

目录

1 邻居几个核心状态

2  邻居项创建条件

3 几个邻居状态变迁条件【核心】

4 邻居项的创建流程细化

4.1 首包数据发送 ip_finish_output2()

4.2 收到ARP请求【ipv4】

4.3  本地 netlink 机制

5 邻居状态变更接口

5.1 邻居状态更新 neigh_update()

5.2  数据发送更新 neigh_event_send()

5.3 定时器超时处理 neigh_timer_handler()

6 小结


1 邻居几个核心状态

邻居项的状态机机制是通用邻居层最重要的内容,主要是处理邻居项中状态的改变,其中包括几个邻居状态的定时器机制,以及邻居项的更新,solicit请求的发送等。对于通用邻居项的状态机,主要有如下几个状态:

  • NUD_INCOMPLETE
  • NUD_REACHABLE
  • NUD_STALE
  • NUD_DELAY
  • NUD_PROBE
  • NUD_FAILED
  • NUD_NOARP
  • NUD_PERMANENT
     

其中,处于如下状态的邻居项,都会启动一个定时器:

#define NUD_IN_TIMER	(NUD_INCOMPLETE|NUD_REACHABLE|NUD_DELAY|NUD_PROBE)

而处于如下状态的邻居项,我们认为邻居项是可达的:

#define NUD_CONNECTED	(NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)

对于处于 NUD_PERMANENT、NUD_NOARP 状态的邻居项,是不会进行邻居项的状态的转换的,

  • NUD_PERMANENT :说明邻居项是通过netlink机制添加的邻居项,不会改变;
  • NUD_NOARP :一般是地址为组播的邻居项,其二层地址是可以根据三层地址计算出来的,不需要进行邻居项的学习,也不会进行状态的改变。

从上面2个宏定义我们知道,处于NUD_REACHABLE状态的邻居项, 肯定会进行状态的状态的转变。因为处于该状态的邻居项,要启动一个定时器,那如果定时器超时后,邻居项一直没有被使用,则邻居项的状态就会转变。

2  邻居项创建条件

  1. 有数据要发送出去,我们还不知道目的三层地址对应的二层地址或者下一跳网关对应的二层地址。(在有数据发送时,会先去查找路由表,若查找到路由,且该路由没有在路由缓存中,则会创建路由缓存,并在创建路由缓存时,会创建一个邻居项与该路由缓存绑定)
  2. 接口收到一个solicit的请求报文,且没有在邻居表的邻居项hash 数组中查找到符合条件的邻居项,则创建一个邻居项。
  3. 应用层通过netlink消息创建一个三层地址对应的二层地址的邻居项。

对于上面3种创建邻居项,其初始状态以及状态的变化会有所不同。

  1. 对于因为有数据要发送而创建的邻居项,会将其邻居项状态设置为NUD_NONE。然后邻居项的状态会设置为NUD_INCOMPLETE状态,处于该状态的邻居项会主动发送solicti请求,如果再定时器到期前收到应答,则会将邻居项的状态设置NUD_REACHABLE,否则,在定时器到期且超过最大发包次数的请求下,则会将邻居项的状态设置为NUD_FAILED,处于该状态的邻居项,其占用的缓存将会被释放掉。
  2. 对于接收到一个solicit的请求报文而创建的邻居项,因为既然有远方的solicit请求,则会有数据发送过来,此时创建的邻居项,就没有将邻居项的状态设置为NUD_INCOMPLETE而发送solicit请求,但也不能直接就将状态设置为NUD_CONNECT,此时是将邻居项的状态设置为NUD_STALE,这样如果远方有数据发送过来,而且需要通过该邻居项发送数据到远方,就会将状态设置为NUD_DELAY,如果再收到该远方发来的四层数据的确认等,就间接实现了邻居项的确认,从而将状态设置为NUD_CONNET
  3. 对于通过netlink消息创建的静态邻居项,我们会将邻居项的状态设置为NUD_PERMANENT,且不会再改变该邻居项的状态。

疑问:因为创建的邻居项的状态为NUD_NONE,而NUD_NONE也不处于定时器状态,那么处于NUD_NONE状态的邻居项,是如何将邻居项的状态转变为NUD_INCOMPLETE的呢?对于处于NUD_STALE状态的邻居项,又是如何实现邻居项状态的转变的呢?

       在上面的第 1 点中,我只是说邻居项从NUD_NONE转变为NUD_INCOMPLETE,却没有说明这个转换是如何进行的。其转变过程大致如下(此处以ipv4为例):当有数据包要发送时,首先是查找路由表,确定目的地址可达。在这个查找的过程中,若还没有与该目的地址对应的邻居项,则会创建一个邻居项,并与查找到的路由缓存相关联,此时邻居项的状态还是NUD_NONE。对于ipv4来说,接着就会执行ip_output,然后就会调用到ip_finish_output2,接着就会调用到neighbour->output,而在neighbour->output里就会调用到__neigh_event_send判断数据包是否可以直接发送出去,如果此时邻居项的状态为NUD_NONE,则会将邻居项的状态设置为NUD_INCOMPLETE,并将要发送的数据包缓存到邻居项的队列中。而处于NUD_INCOMPLETE状态的邻居项的状态转变会有定时器处理函数来实现。对于处于NUD_STALE状态的邻居项,有两个条件实现状态的转变:

  1. 在闲置时间没有超过最大值之前,有数据要通过该邻居项进行发送,则会将邻居项的状态设置为NUD_DELAY,接着状态的转变就有定时器超时函数来接管了。
  2. 在超过最大闲置时间后,没有数据通过该邻居项进行发送,则会将邻居项的状态设置为NUD_FAILED,并会被垃圾回收机制进行缓存回收。

3 几个邻居状态变迁条件【核心】

1、对于NUD_INCOMPLETE,当本机发送完arp 请求包后,还未收到应答时,即会进入该状态。 进入该状态,即会启动定时器,如果在定时器到期后,还没有收到应答时:如果没有到达最大发包上限时,即会重新进行发送请求报文;如果超过最大发包上限还没有收到应答,则会将状态设置为NUD_FAILED

2、对于收到可到达性确认后,即会进入NUD_REACHABLE,当进入NUD_REACHABLE状态。当进入NUD_REACHABLE后,即会启动一个定时器,当定时器到时前,该邻居协议没有被使用过,就会将邻居项的状态转换为NUD_STALE

3、对于进入NUD_STALE状态的邻居项,即会启动一个定时器。如果在定时器到时前,有数据需要发送,则直接将数据包发送出去,并将状态设置为NUD_DELAY;如果在定时器到时,没有数据需要发送,且该邻居项的引用计数为1,则会通过垃圾回收机制,释放该邻居项对应的缓存

4、处于NUD_DELAY状态的邻居项,如果在定时器到时后,没有收到可到达性确认,则会进入NUD_PROBE状态;如果在定时器到达之前,收到可到达性确认,则会进入NUD_REACHABLE (在该状态下的邻居项不会发送solicit请求,而只是等待可到达性应答。主要包括对以前的solicit请求的应答或者收到一个对于本设备以前发送的一个数据包的应答)

5、处于NUD_PROBE状态的邻居项,会发送arp solicit请求(ipv4、ipv6则是NS请求),并启动一个定时器。如果在定时器到时前,收到可到达性确认,则进入NUD_REACHABLE;如果在定时器到时后,没有收到可到达性确认:

       a)没有超过最大发包次数时,则继续发送solicit请求,并启动定时器

       b)如果超过最大发包次数,则将邻居项状态设置为NUD_FAILED

下图是邻居项的状态转换逻辑图,通过上面的描述和下面的逻辑图,能够很好的理解邻居项的状态机机制。

       

4 邻居项的创建流程细化

4.1 首包数据发送 ip_finish_output2()

当第一包数据要发送出去,我们还不知道目的三层地址对应的二层地址或者下一跳网关对应的二层地址,对邻居项的创建(我们以ipv4为例)。在ip_finish_output2() 接口中先是获取网关(下一跳)的三层地址,根据取得三层地址(即领居地址)获取领居表项,获取不到则用网关地址创建一个。核心就是通过下一跳网关地址 nexthop 和 net_dev 为关键字查找一个neighbour项:

若查找到,则直接调用 dst_neigh_output() 发送数据。

若没有查找到,则调用neigh_create() 创建一个邻居表项并加入到arp_table的邻居表项链表中。

static inline int ip_finish_output2(struct sk_buff *skb)
{
	struct dst_entry *dst = skb_dst(skb);
	struct rtable *rt = (struct rtable *)dst;
	struct net_device *dev = dst->dev;
	unsigned int hh_len = LL_RESERVED_SPACE(dev);
	struct neighbour *neigh;
	u32 nexthop;
	...
	rcu_read_lock_bh();
	nexthop = (__force u32) rt_nexthop(rt, ip_hdr(skb)->daddr);
	neigh = __ipv4_neigh_lookup_noref(dev, nexthop);
	if (unlikely(!neigh))
		neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);
	if (!IS_ERR(neigh)) {
		int res = dst_neigh_output(dst, neigh, skb);

		rcu_read_unlock_bh();
		return res;
	}
	rcu_read_unlock_bh();

	net_dbg_ratelimited("%s: No header cache and no neighbour!\n",
			    __func__);
	kfree_skb(skb);
	return -EINVAL;
}

static inline void dst_confirm(struct dst_entry *dst)
{
	dst->pending_confirm = 1;
}

static inline int dst_neigh_output(struct dst_entry *dst, struct neighbour *n,
				   struct sk_buff *skb)
{
	const struct hh_cache *hh;
    ...
	hh = &n->hh;
	if ((n->nud_state & NUD_CONNECTED) && hh->hh_len)
		return neigh_hh_output(hh, skb);
	else
		return n->output(n, skb);//调用 neigh_resolve_output()
}

执行完上面邻居项的创建以后,后面neigh_resolve_output会间接调用函数neigh_event_send(),实现邻居项状态从 NUD_NONE 到 NUD_INCOMPLETTE 状态的改变。

4.2 收到ARP请求【ipv4】

在arp的接收处理函数arp_rcv里,在对arp包的头部信息进行检查以及防火墙规则检查以后,对于允许接收的arp包,则会调用arp_process进行后续的处理,而在arp_process中,对于接收到的arp请求报文后,则会调用 neigh_event_ns() 进行邻居项的查找与创建功能,对于新创建的邻居项,则会调用函数 neigh_update() 更新邻居项的状态。

函数neigh_event_ns() 的逻辑流程还是比较简单的,主要是将邻居项的状态设置为 NUD_STALE

struct neighbour *neigh_event_ns(struct neigh_table *tbl,
				 u8 *lladdr, void *saddr,
				 struct net_device *dev)
{
    //找不到就创建一个neigh,状态设置成 NUD_NONE
	struct neighbour *neigh = __neigh_lookup(tbl, saddr, dev,
						 lladdr || !dev->addr_len);
	if (neigh)
		neigh_update(neigh, lladdr, NUD_STALE,
			     NEIGH_UPDATE_F_OVERRIDE);
	return neigh;
}

4.3  本地 netlink 机制

对于第三种创建邻居项的执行流程,是通过netlink消息机制实现的,最终会调用函数neigh_add实现邻居项的创建。对于新创建的邻居项,则会调用函数neigh_update或者neigh_event_send实现邻居项状态的更新。

5 邻居状态变更接口

通过对于第 4 节中三种情况下邻居项的创建流程,我们发现会调用函数neigh_update、neigh_event_send进行邻居项状态的更新,其实对于处于定时器状态的邻居项,会通过定时器超时处理函数实现邻居项状态的转变,下面我们分析一下这3个函数的处理流程。

5.1 邻居状态更新 neigh_update()

该函数的功能:邻居项的更新,主要是更新二层地址与邻居项的状态,并会根据邻居项的状态,选择相对应的输出函数。

  1. 判断输入二层地址,判断是否需要覆盖邻居项的二层地址
  2. 判断邻居项状态的改变是否合法
  3. 根据不同的邻居项状态设置不同的邻居项输出函数,并设置与该邻居项关联的所有二层缓存头部

该函数被调用的情形有:

  1. 当接收到邻居项的应答报文后,则会调用该函数更新二层地址和状态为CONNECT
  2. 当接收到邻居项的请求报文后,则会调用该函数将邻居项的状态设置为STALE
  3. 处理通过ioctl或者netlink执行的邻居项的添加、删除邻居项时,也会调用该函数更新邻居项的状态与二层地址
/* Generic update routine.
   -- lladdr is new lladdr or NULL, if it is not supplied.
   -- new    is new state.
   -- flags
	NEIGH_UPDATE_F_OVERRIDE allows to override existing lladdr,
				if it is different.
	NEIGH_UPDATE_F_WEAK_OVERRIDE will suspect existing "connected"
				lladdr instead of overriding it
				if it is different.
				It also allows to retain current state
				if lladdr is unchanged.
	NEIGH_UPDATE_F_ADMIN	means that the change is administrative.

	NEIGH_UPDATE_F_OVERRIDE_ISROUTER allows to override existing
				NTF_ROUTER flag.
	NEIGH_UPDATE_F_ISROUTER	indicates if the neighbour is known as
				a router.

   Caller MUST hold reference count on the entry.
 */

int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
		 u32 flags)
{
	u8 old;
	int err;
	int notify = 0;
	struct net_device *dev;
	int update_isrouter = 0;

	write_lock_bh(&neigh->lock);

	dev    = neigh->dev;
	old    = neigh->nud_state;
	err    = -EPERM;
	//对于邻居项的原状态为NOARP或者PERMANENT,且不是admin发送的请求,则直接返回
	if (!(flags & NEIGH_UPDATE_F_ADMIN) &&
	    (old & (NUD_NOARP | NUD_PERMANENT)))
		goto out;
	/*
	当邻居项状态不是有效状态时(即是NUD_INCOMPLET、NUD_NONE、NUD_FAILED):
	1、删除该邻居项的定时器。
	2、对于原状态是 CONNECT 而新状态不是有效态时,则调用 neigh_suspect() 将邻居项的
	   输出函数设置为通用输出函数。
	3、如果原状态是 NUD_INCOMPLETE 或者 NUD_PROBE,且新状态为 NUD_FAILED 时,则调用
	   neigh_invalidate() 发送错误报告,并发送通知信息,函数返回。
	*/
	if (!(new & NUD_VALID)) {
		neigh_del_timer(neigh);
		if (old & NUD_CONNECTED)
			neigh_suspect(neigh);
		neigh->nud_state = new;
		err = 0;
		notify = old & NUD_VALID;
		if ((old & (NUD_INCOMPLETE | NUD_PROBE)) &&
		    (new & NUD_FAILED)) {
			neigh_invalidate(neigh);
			notify = 1;
		}
		goto out;
	}
	/*
	1、对于设备二层地址长度为0的情形,则不需要更新二层地址,直接使用neigh->ha
	2、原状态为有效的,且要更改的地址与邻居项存储的地址相同,则无需更改
	3、原状态为无效,且要更改的地址也是无效,则是逻辑错误,函数直接返回
	4、原状态有效,且要更改的地址无效时,则先将地址设置为邻居项的地址. ???
	5、其他情况下不更改传进来的二层地址。
	   即:原状态有效,且修改的地址与原邻居项地址不同
		  原状态无效,且修改的地址有效时
	*/
	/* Compare new lladdr with cached one */
	if (!dev->addr_len) {
		/* First case: device needs no address. */
		lladdr = neigh->ha;
	} else if (lladdr) {
		/* The second case: if something is already cached
		   and a new address is proposed:
		   - compare new & old
		   - if they are different, check override flag
		 */
		if ((old & NUD_VALID) &&
		    !memcmp(lladdr, neigh->ha, dev->addr_len))
			lladdr = neigh->ha;
	} else {
		/* No address is supplied; if we know something,
		   use it, otherwise discard the request.
		 */
		err = -EINVAL;
		if (!(old & NUD_VALID))
			goto out;
		lladdr = neigh->ha;
	}

	//邻居项的新状态是CONNECT时,更新connect时间
	if (new & NUD_CONNECTED)
		neigh->confirmed = jiffies;
	//更新update时间
	neigh->updated = jiffies;

	/* If entry was valid and address is not changed,
	   do not change entry state, if new one is STALE.
	 */
	err = 0;
	update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;
	/*
	1、原状态有效,且不允许覆盖原来值时,且二层地址不同,且原状态为
	   CONNECT时,则不更新邻居项的二层地址,而只是将状态设置为STALE
	   (这是在二层地址不同时,不修改二层地址的最后一个条件)
	2、原状态有效,且二层地址不变,且新状态为STALE时,则不改邻居项的状态
	*/
	if (old & NUD_VALID) {
		if (lladdr != neigh->ha && !(flags & NEIGH_UPDATE_F_OVERRIDE)) {
			update_isrouter = 0;
			if ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) &&
			    (old & NUD_CONNECTED)) {
				lladdr = neigh->ha;
				new = NUD_STALE;
			} else
				goto out;
		} else {
			if (lladdr == neigh->ha && new == NUD_STALE &&
			    ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) ||
			     (old & NUD_CONNECTED))
			    )
				new = old;
		}
	}
	/*
	当邻居项的新旧状态不同时,则会删除定时器。
	若新状态是 NUD_TIMER,则重新添加定时器。(其中对于定时器的超时时间,如果新状态为NUD_REACHABLE,则将超时时间设置为reachable_time,否则将定时器的超时时间设置为当前时间)
	设置邻居项的状态为新状态
	*/
	if (new != old) {
		neigh_del_timer(neigh);
		if (new & NUD_IN_TIMER)
			neigh_add_timer(neigh, (jiffies +
						((new & NUD_REACHABLE) ?
						 neigh->parms->reachable_time :
						 0)));
		neigh->nud_state = new;
	}
	/*
	如果邻居项的二层地址不同,则更新邻居项里的二层地址,并调用neigh_update_hhs,
	更新与该邻居项相关联的所有二层头部缓存。如果新状态不是CONNECT状态,
	则将confirm时间设置为比当前时间早2*base_reachable_time.

	根据邻居项的不同更新邻居项的输出函数:
	当为NUD_CONNECTED,则调用neigh_connect将邻居项的输出函数设置为快速输出函数
	当为非NUD_CONNECTED,则调用neigh_suspect将邻居项的输出函数设置为通用输出函数

	*/
	if (lladdr != neigh->ha) {
		write_seqlock(&neigh->ha_lock);
		memcpy(&neigh->ha, lladdr, dev->addr_len);
		write_sequnlock(&neigh->ha_lock);
		neigh_update_hhs(neigh);
		if (!(new & NUD_CONNECTED))
			neigh->confirmed = jiffies -
				      (neigh->parms->base_reachable_time << 1);
		notify = 1;
	}
	if (new == old)
		goto out;
	if (new & NUD_CONNECTED)
		neigh_connect(neigh);
	else
		neigh_suspect(neigh);
	if (!(old & NUD_VALID)) {
		struct sk_buff *skb;

		/* Again: avoid dead loop if something went wrong */

		while (neigh->nud_state & NUD_VALID &&
		       (skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
			struct dst_entry *dst = skb_dst(skb);
			struct neighbour *n2, *n1 = neigh;
			write_unlock_bh(&neigh->lock);

			rcu_read_lock();

			/* Why not just use 'neigh' as-is?  The problem is that
			 * things such as shaper, eql, and sch_teql can end up
			 * using alternative, different, neigh objects to output
			 * the packet in the output path.  So what we need to do
			 * here is re-lookup the top-level neigh in the path so
			 * we can reinject the packet there.
			 */
			n2 = NULL;
			if (dst) {
				n2 = dst_neigh_lookup_skb(dst, skb);
				if (n2)
					n1 = n2;
			}
			n1->output(n1, skb);
			if (n2)
				neigh_release(n2);
			rcu_read_unlock();

			write_lock_bh(&neigh->lock);
		}
		skb_queue_purge(&neigh->arp_queue);
		neigh->arp_queue_len_bytes = 0;
	}
out:
	if (update_isrouter) {
		neigh->flags = (flags & NEIGH_UPDATE_F_ISROUTER) ?
			(neigh->flags | NTF_ROUTER) :
			(neigh->flags & ~NTF_ROUTER);
	}
	write_unlock_bh(&neigh->lock);

	if (notify)
		neigh_update_notify(neigh);

	return err;
}

5.2  数据发送更新 neigh_event_send()

1、对于connect、delay、probe状态的邻居项,返回0

2、当状态为NUD_NONE时:

  • 如果组播探测最大次数加上应用探测的最大次数不为0,则将状态设置为INCOMPLETE,更新update时间,并修改定时器的超时时间
  • 否则将邻居项的状态设置为failed,更新update时间,直接释放数据包缓存

3、当状态为STALE时:

  • 此时因为有数据包要发送,因此直接将状态设置为DELAY,更新update时间并修改邻居项定时器的超时时间

4、对于处于INCOMPLETE状态的邻居项

  • 此时已经启动了邻居项定时器,因此只需要将要发送的数据包存入邻居项的数据包缓存队列里即可。后续如果邻居项可达时则会有相应的函数发送出去
static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
	unsigned long now = jiffies;
	
	if (neigh->used != now)
		neigh->used = now;
	if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
		return __neigh_event_send(neigh, skb);
	return 0;
}

int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
	int rc;
	bool immediate_probe = false;

	write_lock_bh(&neigh->lock);

	rc = 0;
	if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
		goto out_unlock_bh;

	if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {
		if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
			unsigned long next, now = jiffies;

			atomic_set(&neigh->probes, neigh->parms->ucast_probes);
			neigh->nud_state     = NUD_INCOMPLETE;
			neigh->updated = now;
			next = now + max(neigh->parms->retrans_time, HZ/2);
			neigh_add_timer(neigh, next);
			immediate_probe = true;
		} else {
			neigh->nud_state = NUD_FAILED;
			neigh->updated = jiffies;
			write_unlock_bh(&neigh->lock);

			kfree_skb(skb);
			return 1;
		}
	} else if (neigh->nud_state & NUD_STALE) {
		neigh_dbg(2, "neigh %p is delayed\n", neigh);
		neigh->nud_state = NUD_DELAY;
		neigh->updated = jiffies;
		neigh_add_timer(neigh,
				jiffies + neigh->parms->delay_probe_time);
	}

	if (neigh->nud_state == NUD_INCOMPLETE) {
		if (skb) {
			while (neigh->arp_queue_len_bytes + skb->truesize >
			       neigh->parms->queue_len_bytes) {
				struct sk_buff *buff;

				buff = __skb_dequeue(&neigh->arp_queue);
				if (!buff)
					break;
				neigh->arp_queue_len_bytes -= buff->truesize;
				kfree_skb(buff);
				NEIGH_CACHE_STAT_INC(neigh->tbl, unres_discards);
			}
			skb_dst_force(skb);
			__skb_queue_tail(&neigh->arp_queue, skb);
			neigh->arp_queue_len_bytes += skb->truesize;
		}
		rc = 1;
	}
out_unlock_bh:
	if (immediate_probe)
		neigh_probe(neigh);
	else
		write_unlock(&neigh->lock);
	local_bh_enable();
	return rc;
}
EXPORT_SYMBOL(__neigh_event_send);

5.3 定时器超时处理 neigh_timer_handler()

通过上面的分析,我们知道对于这几个邻居项状态的转变,最主要的就是需要定时器的那几个邻居项的状态,所以我们下面分析邻居项的定时器的处理函数,对于这几个邻居项的状态,内核使用的是同一个定时器,只不过设置的超时时间不同罢了。

该函数的功能:邻居项最主要的定时器超时处理函数,实现了诸多邻居项状态的转换以及邻居项 solicit 请求相关的函数

1、如果邻居项的当前状态不属于NUD_IN_TIMER,则函数返回。

2、对于处于reach状态的邻居项:

  • 如果当前时间距确认时间confirmed,还未到超时时限reachable_time,则将定时器时间设置为邻居项的超时时限reachable_time
  • 当前时间已晚于确认时间加上超时时限,当未超过邻居项使用时间 加上delay_probe_time,则将状态设置为DELAY。这个状态的改变条件,我感觉设置的很巧妙。一般是进入stale状态的邻居项,在超时前有数据时,则进入Delay状态。为什么可以直接从REACH状态进入Delay状态呢?
  • 当前时间晚于used+delay_probe_time,说明在confirmed+reachable_time超时前的短暂时间点内没有数据发送,此时即将状态设置为STALE

3、对于Delay状态的邻居项:

  • 当前时间小于connect_time+delay_time时,说明邻居项可能在定时器超时函数刚执行时即已经更新了connect_time时间,此时即可以在邻居项的状态设置为reach(connect_time会在neigh_update里被更新)
  • 说明该邻居项在delay_time超时后,还没有被外部确认,此时就需要将邻居项的状态设置为probe,准备发送solict请求

4、对于probe与incomplete状态的邻居项:

  • 此时需要将定时器的下一次超时时间设置为retrain,如果在下一次超时前,还没有得到确认,则还会执行该定时器处理函数

5、对于probe与incomplete状态的邻居项:

  • 如果已经超过了最大发包次数,则将邻居项的状态设置FAILED,并调用neigh_invalidate,发送错误报告,并释放缓存的数据包
  • 如果还没有超过最大发包次数,则调用solicit,发送邻居项solicit请求。

/* Called when a timer expires for a neighbour entry. */

static void neigh_timer_handler(unsigned long arg)
{
	unsigned long now, next;
	struct neighbour *neigh = (struct neighbour *)arg;
	unsigned int state;
	int notify = 0;

	write_lock(&neigh->lock);

	state = neigh->nud_state;
	now = jiffies;
	next = now + HZ;

	if (!(state & NUD_IN_TIMER))
		goto out;

	if (state & NUD_REACHABLE) {
		if (time_before_eq(now,
				   neigh->confirmed + neigh->parms->reachable_time)) {
			neigh_dbg(2, "neigh %p is still alive\n", neigh);
			next = neigh->confirmed + neigh->parms->reachable_time;
		} else if (time_before_eq(now,
					  neigh->used + neigh->parms->delay_probe_time)) {
			neigh_dbg(2, "neigh %p is delayed\n", neigh);
			neigh->nud_state = NUD_DELAY;
			neigh->updated = jiffies;
			neigh_suspect(neigh);
			next = now + neigh->parms->delay_probe_time;
		} else {
			neigh_dbg(2, "neigh %p is suspected\n", neigh);
			neigh->nud_state = NUD_STALE;
			neigh->updated = jiffies;
			neigh_suspect(neigh);
			notify = 1;
		}
	} else if (state & NUD_DELAY) {
		if (time_before_eq(now,
				   neigh->confirmed + neigh->parms->delay_probe_time)) {
			neigh_dbg(2, "neigh %p is now reachable\n", neigh);
			neigh->nud_state = NUD_REACHABLE;
			neigh->updated = jiffies;
			neigh_connect(neigh);
			notify = 1;
			next = neigh->confirmed + neigh->parms->reachable_time;
		} else {
			neigh_dbg(2, "neigh %p is probed\n", neigh);
			neigh->nud_state = NUD_PROBE;
			neigh->updated = jiffies;
			atomic_set(&neigh->probes, 0);
			next = now + neigh->parms->retrans_time;
		}
	} else {
		/* NUD_PROBE|NUD_INCOMPLETE */
		next = now + neigh->parms->retrans_time;
	}

	if ((neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) &&
	    atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) {
		neigh->nud_state = NUD_FAILED;
		notify = 1;
		neigh_invalidate(neigh);
	}

	if (neigh->nud_state & NUD_IN_TIMER) {
		if (time_before(next, jiffies + HZ/2))
			next = jiffies + HZ/2;
		if (!mod_timer(&neigh->timer, next))
			neigh_hold(neigh);
	}
	if (neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) {
		neigh_probe(neigh);
	} else {
out:
		write_unlock(&neigh->lock);
	}

	if (notify)
		neigh_update_notify(neigh);

	neigh_release(neigh);
}

在申请邻居项的内存函数neigh_alloc()里,会创建该定时器,并会将定时器的超时处理函数设置为 neigh_timer_handler。

6 小结

至此,大致分析完了通用邻居项的工作流程,通过这次认真的阅读通用邻居层的代码机制,对于以后编程应该会有影响。通过分析该子层,对于一个比较好的子层代码:

  1. 当实现一个功能时,尽量抽象一个通用层,实现通用层功能的处理,也有利于后续增加新的具体的子层功能
  2. 要具有垃圾回收机制,因为内存是有限的,所以就需要有一个机制实现周期性的内存回收,同时最好需要一个同步的回收机制,以便没有内存用于创建新的项时,能够及时的删除很久没被使用的项以创建新的项
  3. 对于网络层相关的子层,一般会伴随状态的变化,需要考虑状态机的构建以及完整性分析

 

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值