• Linux邻居协议 学习笔记 之五 通用邻居项的状态机机制



    http://blog.csdn.net/lickylin/article/details/22228047


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

    对于通用邻居项的状态机,主要有如下几个状态:

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

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

    #defineNUD_IN_TIMER (NUD_INCOMPLETE|NUD_REACHABLE|NUD_DELAY|NUD_PROBE)

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

    #defineNUD_CONNECTED       (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)

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

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

    我们知道,邻居项的创建原因有两个:

    1、 有数据要发送出去,我们还不知道目的三层地址对应的二层地址或者下一跳网关对

    应的二层地址。

    (在有数据发送时,会先去查找路由表,若查找到路由,且该路由没有在路由缓存中,则会创建路由缓存,并在创建路由缓存时,会创建一个邻居项与该路由缓存绑定)

    2、 接口收到一个solicit的请求报文,且没有在邻居表的邻居项hash 数组中查找到

    符合条件的邻居项,则创建一个邻居项。

    3、应用层通过netlink消息创建一个三层地址对应的二层地址的邻居项

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

    a)      对于因为有数据要发送而创建的邻居项,会将其邻居项状态设置为NUD_NONE。

    然后邻居项的状态会设置为NUD_INCOMPLETE状态,处于该状态的邻居项会主动发送solicti请求,如果再定时器到期前收到应答,则会将邻居项的状态设置NUD_REACHABLE,否则,在定时器到期且超过最大发包次数的请求下,则会将邻居项的状态设置为NUD_FAILED,处于该状态的邻居项,其占用的缓存将会被释放掉。

    b)     对于接收到一个solicit的请求报文而创建的邻居项,因为既然有远方的solicit请求,则会有数据发送过来,此时创建的邻居项,就没有将邻居项的状态设置为NUD_INCOMPLETE而发送solicit请求,但也不能直接就将状态设置为NUD_CONNECT,此时是将邻居项的状态设置为NUD_STALE,这样如果远方有数据发送过来,而且需要通过该邻居项发送数据到远方,就会将状态设置为NUD_DELAY,如果再收到该远方发来的四层数据的确认等,就间接实现了邻居项的确认,从而将状态设置为NUD_CONNET

    c)      对于通过netlink消息创建的静态邻居项,我们会将邻居项的状态设置为NUD_PERMANENT,且不会再改变该邻居项的状态。

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

           在上面的a)中,我只是说邻居项从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,并会被垃圾回收机制进行缓存回收。

    1、对于NUD_INCOMPLETE,当本机发送完arp 请求包后,还未收到应答时,即会进入该状态。 进入该状态,即会启动定时器,如果在定时器到期后,还没有收到应答时:如果没有到达最大发包上限时,即会重新进行发送请求报文;如果超过最大发包上限还没有收到应答,则会将状态设置为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请求,并启动一个定时器。如果在定时器到时前,收到可到达性确认,则进入NUD_REACHABLE;如果在定时器到时后,没有收到可到达性确认:

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

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

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

          


    邻居项的创建:

    我们知道邻居项的创建有三种方式,下面分析第一种方式的处理流程,即有数据要发送出去,我们还不知道目的三层地址对应的二层地址或者下一跳网关对应的二层地址,对邻居项的创建(我们以ipv4为例)。通过上面的分析,我们知道,当查找到路由并创建路由缓存时,则会调用arp_bind_neighbour进行路由缓存与邻居项的绑定,该函数主要实现了邻居项的查找与创建,下面是该函数的分析:

    该函数实现arp协议中neighbour项与路由缓存中的dst_entry表项的绑定

    通过下一跳网关地址和net_dev为关键字查找一个neighbour

    1、若查找到,则将dst->neighbour指向该neighbour

    2、若没有查找到,则调用neigh_create创建一个邻居表项并加入到arp_table的邻居表项链表中,并将dst->neighbour指向该neighbour

    int arp_bind_neighbour(struct dst_entry *dst)

    {

    struct net_device *dev = dst->dev;

    struct neighbour *n = dst->neighbour;

    if (dev == NULL)

    return -EINVAL;

    if (n == NULL) {

    __be32 nexthop = ((struct rtable *)dst)->rt_gateway;

    if (dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))

    nexthop = 0;

    n = __neigh_lookup_errno(

    #if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)

        dev->type == ARPHRD_ATM ? clip_tbl_hook :

    #endif

        &arp_tbl, &nexthop, dev);

    if (IS_ERR(n))

    return PTR_ERR(n);

    dst->neighbour = n;

    }

    return 0;

    }

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

    我们接着分析第二种创建邻居项的执行流程,仍以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)

    {

    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;

    }

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

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

    邻居项状态的更新函数1

    下面我们分析一下函数neigh_update:

    该函数的功能:邻居项的更新,主要是更新二层地址与邻居项的状态,并会

                 根据邻居项的状态,选择相对应的输出函数

    1、判断输入二层地址,判断是否需要覆盖邻居项的二层地址

    2、判断邻居项状态的改变是否合法

    3、根据不同的邻居项状态设置不同的邻居项输出函数,并设置与该

           邻居项关联的所有二层缓存头部

    该函数被调用的情形有:

     1、当接收到邻居项的应答报文后,则会调用该函数更新二层地址和状态为CONNECT

     2、当接收到邻居项的请求报文后,则会调用该函数将邻居项的状态设置为STALE

    3、处理通过ioctl或者netlink执行的邻居项的添加、删除邻居项时,也会调用该函数

           更新邻居项的状态与二层地址

    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_INCOMPLETNUD_NONENUD_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;

    }

    /*

    1、邻居项的新状态是CONNECT时,更新connect时间

    2、更新update时间

    */

    if (new & NUD_CONNECTED)

    neigh->confirmed = jiffies;

    neigh->updated = jiffies;

    /* If entry was valid and address is not changed,

       do not change entry state, if new one is STALE.

     */

    err = 0;

    /*

    1、原状态有效,且不允许覆盖原来值时,且二层地址不同,且原状态为

           CONNECT时,则不更新邻居项的二层地址,而只是将状态设置为STALE

           (这是在二层地址不同时,不修改二层地址的最后一个条件)

    2、原状态有效,且二层地址不变,且新状态为STALE时,则不改邻居项的

           状态

    */

    update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;

    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) {

    memcpy(&neigh->ha, lladdr, dev->addr_len);

    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 neighbour *n1 = neigh;

    write_unlock_bh(&neigh->lock);

    /* On shaper/eql skb->dst->neighbour != neigh :( */

    if (skb_dst(skb) && skb_dst(skb)->neighbour)

    n1 = skb_dst(skb)->neighbour;

    n1->output(skb);

    write_lock_bh(&neigh->lock);

    }

    skb_queue_purge(&neigh->arp_queue);

    }

    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;

    }

    邻居项状态的更新函数2

    使用定时器实现邻居项状态转变的处理:

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

    该函数的功能:邻居项最主要的定时器超时处理函数,

    实现了诸多邻居项状态的转换以及邻居项solicit请求相关的函数

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

    */

    static void neigh_timer_handler(unsigned long arg)

    {

    unsigned long now, next;

    struct neighbour *neigh = (struct neighbour *)arg;

    unsigned state;

    int notify = 0;

    write_lock(&neigh->lock);

    state = neigh->nud_state;

    now = jiffies;

    next = now + HZ;

    if (!(state & NUD_IN_TIMER)) {

    #ifndef CONFIG_SMP

    printk(KERN_WARNING "neigh: timer & !nud_in_timer ");

    #endif

    goto out;

    }

    /*

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

    1、如果当前时间距确认时间confirmed,还未到超时时限reachable_time,则将定时器时间设置为邻居项的超时

           时限reachable_time

    2、当前时间已晚于确认时间加上超时时限,当未超过邻居项使用时间

           加上delay_probe_time,则将状态设置为DELAY

           这个状态的改变条件,我感觉设置的很巧妙。

           一般是进入stale状态的邻居项,在超时前有数据时,则进入Delay状态。

           为什么可以直接从REACH状态进入Delay状态呢?

    3、当前时间晚于used+delay_probe_time,说明在confirmed+reachable_time超时前的短暂时间点

           内没有数据发送,此时即将状态设置为STALE

    对于Delay状态的邻居项:

    1、当前时间小于connect_time+delay_time时,说明邻居项可能在定时器超时函数刚执行时

           即已经更新了connect_time时间,此时即可以在邻居项的状态设置为reach

           (connect_time会在neigh_update里被更新)

    2、说明该邻居项在delay_time超时后,还没有被外部确认,此时就需要将邻居项

            的状态设置为probe,准备发送solict请求

    对于probeincomplete状态的邻居项,此时需要将定时器的下一次超时时间设置为

    retrain,如果在下一次超时前,还没有得到确认,则还会执行该定时器处理函数

    对于probeincomplete状态的邻居项:

    1、如果已经超过了最大发包次数,则将邻居项的状态设置FAILED,并调用

           neigh_invalidate,发送错误报告,并释放缓存的数据包

    2、如果还没有超过最大发包次数,则调用solicit,发送邻居项solicit请求。

    */

    if (state & NUD_REACHABLE) {

    if (time_before_eq(now,

       neigh->confirmed + neigh->parms->reachable_time)) {

    NEIGH_PRINTK2("neigh %p is still alive. ", neigh);

    next = neigh->confirmed + neigh->parms->reachable_time;

    } else if (time_before_eq(now,

      neigh->used + neigh->parms->delay_probe_time)) {

    NEIGH_PRINTK2("neigh %p is delayed. ", neigh);

    neigh->nud_state = NUD_DELAY;

    neigh->updated = jiffies;

    neigh_suspect(neigh);

    next = now + neigh->parms->delay_probe_time;

    } else {

    NEIGH_PRINTK2("neigh %p is suspected. ", 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_PRINTK2("neigh %p is now reachable. ", neigh);

    neigh->nud_state = NUD_REACHABLE;

    neigh->updated = jiffies;

    neigh_connect(neigh);

    notify = 1;

    next = neigh->confirmed + neigh->parms->reachable_time;

    } else {

    NEIGH_PRINTK2("neigh %p is probed. ", 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)) {

    struct sk_buff *skb = skb_peek(&neigh->arp_queue);

    /* keep skb alive even if arp_queue overflows */

    if (skb)

    skb = skb_copy(skb, GFP_ATOMIC);

    write_unlock(&neigh->lock);

    neigh->ops->solicit(neigh, skb);

    atomic_inc(&neigh->probes);

    kfree_skb(skb);

    } else {

    out:

    write_unlock(&neigh->lock);

    }

    if (notify)

    neigh_update_notify(neigh);

    neigh_release(neigh);

    }

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

    邻居项状态的更新函数3

    第三个邻居项状态的更新函数,通过

    /*

    1、对于connectdelayprobe状态的邻居项,返回0

    2

    */

    int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)

    {

    int rc;

    unsigned long now;

    write_lock_bh(&neigh->lock);

    rc = 0;

    if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))

    goto out_unlock_bh;

    now = jiffies;

    /*

    当状态为NUD_NONE时,

    1、如果组播探测最大次数加上应用探测的最大次数

    不为0,则将状态设置为INCOMPLETE,更新update时间,并修改定时器的超时时间

    2、否则将邻居项的状态设置为failed,更新update时间,直接释放数据包缓存

    当状态为STALE时,当有数据包要发送时,则将状态设置为DELAY,更新update时间

    并修改邻居项定时器的超时时间

    */

    if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {

    if (neigh->parms->mcast_probes + neigh->parms->app_probes) {

    atomic_set(&neigh->probes, neigh->parms->ucast_probes);

    neigh->nud_state     = NUD_INCOMPLETE;

    neigh->updated = jiffies;

    neigh_add_timer(neigh, now + 1);

    } 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_PRINTK2("neigh %p is delayed. ", neigh);

    neigh->nud_state = NUD_DELAY;

    neigh->updated = jiffies;

    neigh_add_timer(neigh,

    jiffies + neigh->parms->delay_probe_time);

    }

    /*

    对于处于INCOMPLETE状态的邻居项,已经启动了邻居项定时器,此时只需要

    将要发送的数据包存入邻居项的数据包缓存队列里即可。后续如果

    邻居项可达时则会有相应的函数发送出去

    */

    if (neigh->nud_state == NUD_INCOMPLETE) {

    if (skb) {

    if (skb_queue_len(&neigh->arp_queue) >=

        neigh->parms->queue_len) {

    struct sk_buff *buff;

    buff = __skb_dequeue(&neigh->arp_queue);

    kfree_skb(buff);

    NEIGH_CACHE_STAT_INC(neigh->tbl, unres_discards);

    }

    __skb_queue_tail(&neigh->arp_queue, skb);

    }

    rc = 1;

    }

    out_unlock_bh:

    write_unlock_bh(&neigh->lock);

    return rc;

    }

    总结:通过上面的分析,用于邻居项状态改变的函数有neigh_update、neigh_timer_handler、__neigh_event_send。而对于每个函数被使用的情景,我们已经在上面分析完了。

    下面分析一下几个通用邻居项常用的函数,它们或者被上面的函数间接调用或者被直接调用,它们的逻辑结构也比较简单:

    /*

    遍历该邻居项所关联的所有二层缓存结构,将其函数指针hh_output指向

    该邻居项的通用输出函数

    */

    static void neigh_suspect(struct neighbour *neigh)

    {

    struct hh_cache *hh;

    NEIGH_PRINTK2("neigh %p is suspected. ", neigh);

    neigh->output = neigh->ops->output;

    for (hh = neigh->hh; hh; hh = hh->hh_next)

    hh->hh_output = neigh->ops->output;

    }

    /* Neighbour state is OK;

       enable fast path.

       Called with write_locked neigh.

     */

    /*

    遍历该邻居项所关联的所有二层缓存结构,将其函数指针hh_output指向

    该邻居项的快速输出函数hh_output

    */

    static void neigh_connect(struct neighbour *neigh)

    {

    struct hh_cache *hh;

    NEIGH_PRINTK2("neigh %p is connected. ", neigh);

    neigh->output = neigh->ops->connected_output;

    for (hh = neigh->hh; hh; hh = hh->hh_next)

    hh->hh_output = neigh->ops->hh_output;

    }

    /*

    返回邻居项能发送solicit的最大次数

    */

    static __inline__ int neigh_max_probes(struct neighbour *n)

    {

    struct neigh_parms *p = n->parms;

    return (n->nud_state & NUD_PROBE ?

    p->ucast_probes :

    p->ucast_probes + p->app_probes + p->mcast_probes);

    }

    /*

    功能:发送错误报告

    1、更新邻居项的update时间

    2、对于邻居状态为failed,且邻居项的队列里有数据包等待发送时,则调用

           error_report函数,发送错误报告

    */

    static void neigh_invalidate(struct neighbour *neigh)

    __releases(neigh->lock)

    __acquires(neigh->lock)

    {

    struct sk_buff *skb;

    NEIGH_CACHE_STAT_INC(neigh->tbl, res_failed);

    NEIGH_PRINTK2("neigh %p is failed. ", neigh);

    neigh->updated = jiffies;

    /* It is very thin place. report_unreachable is very complicated

       routine. Particularly, it can hit the same neighbour entry!

       So that, we try to be accurate and avoid dead loop. --ANK

     */

    while (neigh->nud_state == NUD_FAILED &&

           (skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {

    write_unlock(&neigh->lock);

    neigh->ops->error_report(neigh, skb);

    write_lock(&neigh->lock);

    }

    skb_queue_purge(&neigh->arp_queue);

    }

    /*

    功能:对于一个给定的邻居项,更新与该邻居项有关联的所有二层缓存头部结构成员

    */

    static void neigh_update_hhs(struct neighbour *neigh)

    {

    struct hh_cache *hh;

    void (*update)(struct hh_cache*, const struct net_device*, const unsigned char *)

    = neigh->dev->header_ops->cache_update;

    if (update) {

    for (hh = neigh->hh; hh; hh = hh->hh_next) {

    write_seqlock_bh(&hh->hh_lock);

    update(hh, neigh->dev, neigh->ha);

    write_sequnlock_bh(&hh->hh_lock);

    }

    }

    }

    /*

    功能:邻居项关联的二层缓存头部的初始化

    */

    static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst,

      __be16 protocol)

    {

    struct hh_cache *hh;

    struct net_device *dev = dst->dev;

    for (hh = n->hh; hh; hh = hh->hh_next)

    if (hh->hh_type == protocol)

    break;

    if (!hh && (hh = kzalloc(sizeof(*hh), GFP_ATOMIC)) != NULL) {

    seqlock_init(&hh->hh_lock);

    hh->hh_type = protocol;

    atomic_set(&hh->hh_refcnt, 0);

    hh->hh_next = NULL;

    if (dev->header_ops->cache(n, hh)) {

    kfree(hh);

    hh = NULL;

    } else {

    atomic_inc(&hh->hh_refcnt);

    hh->hh_next = n->hh;

    n->hh     = hh;

    if (n->nud_state & NUD_CONNECTED)

    hh->hh_output = n->ops->hh_output;

    else

    hh->hh_output = n->ops->output;

    }

    }

    if (hh) {

    atomic_inc(&hh->hh_refcnt);

    dst->hh = hh;

    }

    }

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

    1、 当实现一个功能时,尽量抽象一个通用层,实现通用层功能的处理,也有利于后续增加新的具体的子层功能

    2、 要具有垃圾回收机制,因为内存是有限的,所以就需要有一个机制实现周期性的内

    存回收,同时最好需要一个同步的回收机制,以便没有内存用于创建新的项时,能够及时的删除很久没被使用的项以创建新的项

    3、 对于网络层相关的子层,一般会伴随状态的变化,需要考虑状态机的构建以及完整

    性分析

    目前也就总结出这3项,通过这次总结,后续分析路由子层的代码时,应该会比较容易。


  • 相关阅读:
    流程控制
    数据类型和运算符
    抽奖
    蓝桥杯 第五届
    python下编译py成pyc和pyo
    Django中提示TemplateDoesNotExist?
    Ubuntu16.04 apache2 wsgi 部署django
    Ubuntu 14.04下Django+MySQL安装部署全过程
    LVM原理及PV、VG、LV、PE、LE关系图
    SQLServer2008-镜像数据库实施手册(双机)SQL-Server2014同样适用
  • 原文地址:https://www.cnblogs.com/ztguang/p/12644604.html
Copyright © 2020-2023  润新知