linux内核协议栈 邻居协议之通用邻居项的状态机机制【核心】_neigh_add netlink-程序员宅基地

技术标签: 协议栈  领居协议  邻居协议  邻居状态  linux内核协议栈  

目录

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. 对于网络层相关的子层,一般会伴随状态的变化,需要考虑状态机的构建以及完整性分析

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wangquan1992/article/details/109706223

智能推荐

分布式光纤传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告_预计2026年中国分布式传感器市场规模有多大-程序员宅基地

文章浏览阅读3.2k次。本文研究全球与中国市场分布式光纤传感器的发展现状及未来发展趋势,分别从生产和消费的角度分析分布式光纤传感器的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的市场份额。主要生产商包括:FISO TechnologiesBrugg KabelSensor HighwayOmnisensAFL GlobalQinetiQ GroupLockheed MartinOSENSA Innovati_预计2026年中国分布式传感器市场规模有多大

07_08 常用组合逻辑电路结构——为IC设计的延时估计铺垫_基4布斯算法代码-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏12次。常用组合逻辑电路结构——为IC设计的延时估计铺垫学习目的:估计模块间的delay,确保写的代码的timing 综合能给到多少HZ,以满足需求!_基4布斯算法代码

OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版-程序员宅基地

文章浏览阅读3.3k次,点赞3次,收藏5次。OpenAI Manager助手(基于SpringBoot和Vue)_chatgpt网页版

关于美国计算机奥赛USACO,你想知道的都在这_usaco可以多次提交吗-程序员宅基地

文章浏览阅读2.2k次。USACO自1992年举办,到目前为止已经举办了27届,目的是为了帮助美国信息学国家队选拔IOI的队员,目前逐渐发展为全球热门的线上赛事,成为美国大学申请条件下,含金量相当高的官方竞赛。USACO的比赛成绩可以助力计算机专业留学,越来越多的学生进入了康奈尔,麻省理工,普林斯顿,哈佛和耶鲁等大学,这些同学的共同点是他们都参加了美国计算机科学竞赛(USACO),并且取得过非常好的成绩。适合参赛人群USACO适合国内在读学生有意向申请美国大学的或者想锻炼自己编程能力的同学,高三学生也可以参加12月的第_usaco可以多次提交吗

MySQL存储过程和自定义函数_mysql自定义函数和存储过程-程序员宅基地

文章浏览阅读394次。1.1 存储程序1.2 创建存储过程1.3 创建自定义函数1.3.1 示例1.4 自定义函数和存储过程的区别1.5 变量的使用1.6 定义条件和处理程序1.6.1 定义条件1.6.1.1 示例1.6.2 定义处理程序1.6.2.1 示例1.7 光标的使用1.7.1 声明光标1.7.2 打开光标1.7.3 使用光标1.7.4 关闭光标1.8 流程控制的使用1.8.1 IF语句1.8.2 CASE语句1.8.3 LOOP语句1.8.4 LEAVE语句1.8.5 ITERATE语句1.8.6 REPEAT语句。_mysql自定义函数和存储过程

半导体基础知识与PN结_本征半导体电流为0-程序员宅基地

文章浏览阅读188次。半导体二极管——集成电路最小组成单元。_本征半导体电流为0

随便推点

【Unity3d Shader】水面和岩浆效果_unity 岩浆shader-程序员宅基地

文章浏览阅读2.8k次,点赞3次,收藏18次。游戏水面特效实现方式太多。咱们这边介绍的是一最简单的UV动画(无顶点位移),整个mesh由4个顶点构成。实现了水面效果(左图),不动代码稍微修改下参数和贴图可以实现岩浆效果(右图)。有要思路是1,uv按时间去做正弦波移动2,在1的基础上加个凹凸图混合uv3,在1、2的基础上加个水流方向4,加上对雾效的支持,如没必要请自行删除雾效代码(把包含fog的几行代码删除)S..._unity 岩浆shader

广义线性模型——Logistic回归模型(1)_广义线性回归模型-程序员宅基地

文章浏览阅读5k次。广义线性模型是线性模型的扩展,它通过连接函数建立响应变量的数学期望值与线性组合的预测变量之间的关系。广义线性模型拟合的形式为:其中g(μY)是条件均值的函数(称为连接函数)。另外,你可放松Y为正态分布的假设,改为Y 服从指数分布族中的一种分布即可。设定好连接函数和概率分布后,便可以通过最大似然估计的多次迭代推导出各参数值。在大部分情况下,线性模型就可以通过一系列连续型或类别型预测变量来预测正态分布的响应变量的工作。但是,有时候我们要进行非正态因变量的分析,例如:(1)类别型.._广义线性回归模型

HTML+CSS大作业 环境网页设计与实现(垃圾分类) web前端开发技术 web课程设计 网页规划与设计_垃圾分类网页设计目标怎么写-程序员宅基地

文章浏览阅读69次。环境保护、 保护地球、 校园环保、垃圾分类、绿色家园、等网站的设计与制作。 总结了一些学生网页制作的经验:一般的网页需要融入以下知识点:div+css布局、浮动、定位、高级css、表格、表单及验证、js轮播图、音频 视频 Flash的应用、ul li、下拉导航栏、鼠标划过效果等知识点,网页的风格主题也很全面:如爱好、风景、校园、美食、动漫、游戏、咖啡、音乐、家乡、电影、名人、商城以及个人主页等主题,学生、新手可参考下方页面的布局和设计和HTML源码(有用点赞△) 一套A+的网_垃圾分类网页设计目标怎么写

C# .Net 发布后,把dll全部放在一个文件夹中,让软件目录更整洁_.net dll 全局目录-程序员宅基地

文章浏览阅读614次,点赞7次,收藏11次。之前找到一个修改 exe 中 DLL地址 的方法, 不太好使,虽然能正确启动, 但无法改变 exe 的工作目录,这就影响了.Net 中很多获取 exe 执行目录来拼接的地址 ( 相对路径 ),比如 wwwroot 和 代码中相对目录还有一些复制到目录的普通文件 等等,它们的地址都会指向原来 exe 的目录, 而不是自定义的 “lib” 目录,根本原因就是没有修改 exe 的工作目录这次来搞一个启动程序,把 .net 的所有东西都放在一个文件夹,在文件夹同级的目录制作一个 exe._.net dll 全局目录

BRIEF特征点描述算法_breif description calculation 特征点-程序员宅基地

文章浏览阅读1.5k次。本文为转载,原博客地址:http://blog.csdn.net/hujingshuang/article/details/46910259简介 BRIEF是2010年的一篇名为《BRIEF:Binary Robust Independent Elementary Features》的文章中提出,BRIEF是对已检测到的特征点进行描述,它是一种二进制编码的描述子,摈弃了利用区域灰度..._breif description calculation 特征点

房屋租赁管理系统的设计和实现,SpringBoot计算机毕业设计论文_基于spring boot的房屋租赁系统论文-程序员宅基地

文章浏览阅读4.1k次,点赞21次,收藏79次。本文是《基于SpringBoot的房屋租赁管理系统》的配套原创说明文档,可以给应届毕业生提供格式撰写参考,也可以给开发类似系统的朋友们提供功能业务设计思路。_基于spring boot的房屋租赁系统论文

推荐文章

热门文章

相关标签