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_QUEUEING在opt.h头文件中定义,它表示在进行MAC地址解析的时候有多个发送包需要缓存起来,它能够缩短TCP建链的时间。当ARP_QUEUEING设置为1,则用成员q指针指向的队列用来存储发送的数据包。当ARP_QUEUEING设置为0是,用pbuf类型的指针q来缓存一个发送数据包。
ipaddr成员用来存储32bit的IP地址。netif成员表示此网卡结构体指针。ethaddr成员用来存储48bit的MAC地址。
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 ÐARP_FLAG_FIND_ONLY) != 0) ||
/* or no empty entry found and not allowed to recycle? */
((empty== ARP_TABLE_SIZE) &&((flags ÐARP_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_SIZE,在for循环中如果找到第一个的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_EMPTY,ctime成员为0,ethaddr成员为ethzero等。
etharp_find_entry函数最后一部分的工作就是创建一个新的ARP表项,就是对arp_table[i]成员的各种初始化工作。
- Lwip ARP分析(1)
- lwIP ARP协议分析1
- lwIP ARP协议分析1 (转)
- lwIP ARP协议分析
- lwIP ARP协议分析
- lwIP ARP协议分析
- Lwip ARP分析(2)
- lwIP ARP协议分析0
- lwip源码分析2----ARP
- lwip源码分析2----ARP
- LwIP源码分析2-ARP
- lwip ARP相关处理(1)
- 小议LWIP——ARP协议1
- Lwip入门(2)--ARP
- lwip之ARP协议
- LWIP[转]LwIP BUG之ARP缓存
- Lwip--ARP协议(ARP数据包处理)
- lwIP分析
- 使用Http Head方法获取文件长度
- Android Studio打开Android Device Monitor中的DDMS没有内容显示
- Use gprof to visualize code running time
- vlc延迟流和桥接流输出功能等
- Laravel 服务容器实例教程 —— 深入理解控制反转(IoC)和依赖注入(DI)
- Lwip ARP分析(1)
- System.out.println(5.00 - 4.90);结果分析
- 将字符串插入到另一个字符串的指定位置
- 【codevs2822】 爱在心中 tarjan+统计点的个数
- dB的意义
- 关于Java -jar参数的使用心得
- 关于mysql or 的 底层执行
- 实验对象随机分组程序
- CentOS7下解决yum install mysql-server没有可用包的问