Lwip ARP分析(1)

来源:互联网 发布:jd抢购软件 编辑:程序博客网 时间:2024/05/22 17:16

ARP全称Address Resolution Protocol,地址解析协议。它是将32bit的IP地址解析成48bit的MAC地址。一台机器向另外一台机器发生数据时,需要知道对端网卡的硬件地址(48 bit的MAC地址)才能将数据硬件之间进行交互。


ARP核心数据结构

ARP缓存保证ARP高效运行的关键,ARP缓存表由一个个的缓存表项组成的。ARP表项数据结构etharp_entry如下

struct etharp_entry { 

#if ARP_QUEUEING 

    /** Pointer to queue of pending outgoing packets on this ARP entry. */                                                                                   

    struct etharp_q_entry*q;

#else /* ARP_QUEUEING */   

    /** Pointer to a single pending outgoing packet on this ARP entry. */   

    struct pbuf *q; 

#endif /* ARP_QUEUEING */   

    ip4_addr_t ipaddr; 

    struct netif *netif; 

    struct eth_addrethaddr; 

    u16_t ctime; 

    u8_tstate; 

};  

ARP_QUEUEINGopt.h头文件中定义它表示在进行MAC地址解析的时候有多个发送包需要缓存起来,它能够缩短TCP建链的时间。当ARP_QUEUEING设置1则用成员q指针指向的队列用来存储发送的数据包。当ARP_QUEUEING设置0,用pbuf类型的指针q缓存一个发送数据包。

ipaddr成员用来存储32bitIP地址。netif成员表示此网卡结构体指针。ethaddr成员用来存储48bitMAC地址。

ARP缓存表中每个表项都是有生存周期的,超过生存周期后,就会从ARP缓存表中删除。ctime成员就是用来记录此缓存表项加入此缓存表时长。

state成员表示ARP表项的状态如下

成员

说明

ETHARP_STATE_EMPTY

缓存表中各表项初始化时处于此状态,没有记录任何信息。

ETHARP_STATE_PENDING

当对于一个给定的IP地址发送ARP请求包时,会设置新增的缓存表项为此状态。

ETHARP_STATE_STABLE

当收到ARP应答,更新ARP表项后,设置表项为此状态。

ETHARP_STATE_STABLE_REREQUESTING_1                       

在ARP缓存表中某个表项快到老化时间时发送一个ARP请求,并将此ARP表项从ETHARP_STATE_STABLE状态改为此状态。

ETHARP_STATE_STABLE_REREQUESTING_2

 

ETHARP_STATE_STATIC

静态ARP表项。

Lwip代码定义了一个全局数组static struct etharp_entryarp_table[ARP_TABLE_SIZE]用来表示ARP缓存表,缓存表的表项个数ARP_TABLE_SIZE,此宏在opt.h头文件中定义为10


ARP数据包格式

ARP数据包结构由两部分组成:以太网首部+ARP数据包首部。


Lwip以太网首部由eth_hdr结构体表示:

/** Ethernet header */   

struct eth_hdr {  

    PACK_STRUCT_FLD_S(struct eth_addrdest);                                                                                                      

    PACK_STRUCT_FLD_S(struct eth_addrsrc); 

    PACK_STRUCT_FIELD(u16_ttype); 

} PACK_STRUCT_STRUCT; 

dest字段表示以太网目的地址,6字节长度;src字段表示以太网源地址,也是6字节长度。type字段表示对应类型

含义

0x0800                         

IPV4协议,后面跟的是IPV4数据包

0x0806

ARP协议,后面跟的是ARP请求/应答数据包                                                                           


创建ARP缓存

Lwip中创建ARP缓存表项的代码在etharp_find_entry函数中实现,函数声明如下:

static s8_t  

 etharp_find_entry(constip4_addr_t *ipaddr,u8_t flags,struct netif*netif)                                                                    

参数ipaddr:如果ipaddr不为NULL,则从ARP缓存表中查找一个与此IP地址匹配的ARP表项,如果找到有处于ETHARP_STATE_PENDING或者ETHARP_STATE_STABLE状态IP匹配的表项,返回表项索引值;如果没有找到匹配表项,则选择一个ETHARP_STATE_EMPTY的表项,并将此表项的IP地址设置为ipaddr。

参数flag情况ETHARP_FLAG_TRY_HARD和ETHARP_FLAG_FIND_ONLY。这两种情况在后面分析代码时说明。

参数netif就是此IP地址对应的网卡。


etharp_find_entry函数代码主要分为三部分,第一部分是遍历整个ARP缓存表:

for (i= 0;i <ARP_TABLE_SIZE;++i) { 

    u8_t state =arp_table[i].state; 

    if ((empty== ARP_TABLE_SIZE)&& (state== ETHARP_STATE_EMPTY)) {                                                                   

        empty =i;        /*记录第一个empty表项*/

    } else if (state!=  ETHARP_STATE_EMPTY) { 

        if (ipaddr &&ip4_addr_cmp(ipaddr,&arp_table[i].ipaddr)) { 

            return i;     /*找到一个IP地址匹配的表项*/

        }  

 

        if (state== ETHARP_STATE_PENDING) { 

            /* pending with queued packets? */   

            if (arp_table[i].q !=  NULL) { 

                if (arp_table[i].ctime >= age_queue) { 

                    old_queue =i; 

                    age_queue =arp_table[i].ctime; 

                }  

            } else  

            /* pending without queued packets? */   

            {  

                if (arp_table[i].ctime >= age_pending) { 

                    old_pending =i; 

                    age_pending =arp_table[i].ctime; 

                }  

            }  

            /* stable entry? */   

        } else if (state>= ETHARP_STATE_STABLE) { 

if ETHARP_SUPPORT_STATIC_ENTRIES 

            /* don't record old_stable for static entries since they never expire */   

            if (state< ETHARP_STATE_STATIC) 

#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */   

            {  

                /* remember entry with oldest stable entry in oldest, its age in maxtime */   

                if (arp_table[i].ctime >= age_stable) { 

                    old_stable =i; 

                    age_stable =arp_table[i].ctime; 

                }  

            }  

        }  

    }   

}   

for循环会遍历ARP缓存arp_table记录下第一个ETHARP_STATE_EMPTY表项,保存在变量empty中empty初始化值为ARP_TABLE_SIZE,即ARP缓存表大小。如果表项的状态不为ETHARP_STATE_EMPTY,就判断此表项的ipaddr成员是否与参数ipaddr匹配,如果匹配则返回此缓存表项在ARP缓存表中的索引值


在IP地址不匹配的情况下,接着如下处理:

1) 表项状态为ETHARP_STATE_PENDING的情况下,如果arp_table[i].q为NULL,即缓存表项没有挂载未发送的缓存数据记录下此种类型的缓存表的最长存在时间ctime,保存到变量age_pending,此表项的索引值保存到局部变量old_pending。如果arp_table[i].q不为NULL,即此缓存表项有挂载未发送的缓存数据,记录下此种类型的缓存表项长存在时间ctime保存到变量age_queue中,表项索引值保存在变量old_queue。

2) 表项状态state大于等于ETHARP_STATE_STABLE的情况下,此时state可能为枚举etharp_state中的后四种情况。如果ETHARP_SUPPORT_STATIC_ENTRIES值1则还需要判断state不为ETHARP_STATE_STATIC,因为ARP缓存表中静态表项是不会过期的,统计处于stable状态缓存表项的最存在时间时,不统计此种情况的ARP缓存表项。最长存在时间保存到变量age_stable,对应的索引值保存到变量old_stable。


上面for循环结束后,在参数ipaddr不为NULL的情况,有可能找到匹配的缓存表项直接返回。其它情况则会接着执行下面的代码:

if (((flags &ETHARP_FLAG_FIND_ONLY) !=  0) ||  

      /* or no empty entry found and not allowed to recycle? */   

      ((empty== ARP_TABLE_SIZE) &&((flags &ETHARP_FLAG_TRY_HARD)== 0))) {                                              

    return (s8_t)ERR_MEM; 

}  

如果flags标志设置为ETHARP_FLAG_FIND_ONLY,说明调用此函数仅仅是为了查找一个ARP缓存表项,运行到这来就是前面for循环遍历的时候没有找到那么久直接返回错误ERR_MEM。

如果flags标志设置为ETHARP_FLAG_TRY_HARD,说明算没有一个empty缓存表项,也会选择一个合适的表项索引返回。这里如果没有设置ETHARP_FLAG_TRY_HARD标志empty等于ARP_TABLE_SIZE(没有找到空闲表项),那么也直接返回错误ERR_MEM。


接着分析etharp_find_entry函数第二部分代码,此部分代码选择一个合适的ARP表项返回。运行这里来了,说明ETHARP_FLAG_TRY_HARD标志一定设置了。

if (empty< ARP_TABLE_SIZE) { /*找到empty表项*/

    i =empty; 

} else {            /*没有找打empty表项,需要回收已存在表项*/                                                                                                              

    if (old_stable< ARP_TABLE_SIZE) { 

        i =old_stable;   

    } else if (old_pending< ARP_TABLE_SIZE) {   

        i =old_pending;  

    } else if (old_queue< ARP_TABLE_SIZE) {    

        i =old_queue;  

    } else { 

        return (s8_t)ERR_MEM; 

    }  

 

    etharp_free_entry(i); 

}

变量empty初始化ARP_TABLE_SIZEfor循环中如果找到第一个ETHARP_STATE_EMPTY状态的表项,就将其索引值赋给empty这里如果empty小于ARP_TABLE_SIZE说明到了empty表项,并就此表项索引值empty赋给i;否则说明没有找empty表项,那么需要从ARP表中回收ARP表项


前面for循环中我们有记录如下信息:

a) 存在时间最长的ETHARP_STATE_STABLE状态ARP表项索引old_stable

b) 存在时间最长的ETHARP_STATE_PENDING状态且没有挂载未发送缓存数据的ARP表项的索引old_pending

c) 存在时间最长的ETHARP_STATE_PENDING状态且有挂载未发送的缓存数据的ARP表项的索引old_queue

回收的时候安装上面aàbàc的顺序,如果ARP表项中有ETHARP_STATE_STABLE状态表项,回收索引old_stable对应的ARP表项;如果没有找到ETHARP_STATE_STABLE状态的表项,就接着查找ETHARP_STATE_PENDING状态且没有挂载未发送缓存数据的ARP表项找到的话就回收索引old_pending对应的ARP表项;最后的选择就是回收索引值old_queue对应ARP表项。


找到合适的回收ARP表项后,调用etharp_free_entry清除ARP表项包括设置state成员ETHARP_STATE_EMPTYctime成员0ethaddr成员ethzero等

etharp_find_entry函数最后一部分的工作就是创建一个新的ARP表项,就是对arp_table[i]成员的各种初始化工作。


0 0
原创粉丝点击