在以太网协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的MAC地址。而在TCP/IP协议中,网络层和传输层只关心目标主机的IP地址。这就导致在以太网中使用IP协议时,数据链路层的以太网协议接到上层IP协议提供的数据中,只包含目的主机的IP地址。于是需要一种方法,根据目的主机的IP地址,获得其MAC地址。这就是ARP协议要做的事情。
所谓地址解析(address resolution)就是主机在发送帧前将目标IP地址转换成目标MAC地址的过程。
理论结构
ARP软件可分为三部分:
- 输出模块
- 将高层协议地址与相应的物理地址进行绑定,返回给网络接口程序
- 输入模块
- 处理来自网络的ARP分组,并通过增加新的绑定来修改ARP高速缓存的内容
- 高速缓存管理程序
- 实现高速缓存替换策略;检测高速缓存中的所有表项,删除已达到规定时限的表项
输出模块
该模块主要是要接收IP数组请求,然后查找物理地址,返回。
主要步骤为:
1. 睡眠,直到IP软件收到IP分组。
2. 检查高速缓存表,寻找对应于这个IP分组的项目。
3. if ( 找到 ){
if ( 状态为 RESOLVED ){
提取硬件物理地址;
将分组连同硬件物理地址一起发送到数据链路层;
return;
}
else if ( 状态为 PENDING ){
将分组放入相应的队列;
return;
}
}
else{
创建一个队列;
将分组加入到队列中;
创建一个高速缓存项目,状态设置为 PENDING ,ATTEMPTS 为 1;
发送ARP请求;
}
ARP高速缓存队列
它是用数组来存储的。
extern struct arpentry arptable[ARP_TSIZE]
搜索ARP高速缓存
/* arpfind.c - arpfind */ #include <conf.h> #include <kernel.h> #include <network.h> /*------------------------------------------------------------------------ * arpfind - find an ARP entry given a protocol address and interface *------------------------------------------------------------------------ */ struct arpentry * arpfind(u_char *pra, u_short prtype, struct netif *pni) { struct arpentry *pae; /* 定义ARP缓存结构体指针 */ int i; for (i=0; i<ARP_TSIZE; ++i) { /* 遍历ARP高速缓存 */ pae = &arptable[i]; /* 高速缓存数组 */ if (pae->ae_state == AS_FREE) /* 缓存为空闲接找下一个 */ continue; if (pae->ae_prtype == prtype && /* 协议类型相同 */ pae->ae_pni == pni && /* 接口和协议地址相同 */ BLKEQU(pae->ae_pra, pra, pae->ae_prlen)) /* BLKEQU的定义 #define BLKEQU(b1, b2, len) (!memcmp((b1), (b2), len))*/ return pae; } return 0; }
ARP请求分组的广播
/* arpsend.c - arpsend */ #include <conf.h> #include <kernel.h> #include <network.h> /*------------------------------------------------------------------------ * arpsend - broadcast an ARP request * N.B. Assumes interrupts disabled *------------------------------------------------------------------------ */ int arpsend(pae) struct arpentry *pae; //指向高速缓存的表项 { struct netif *pni = pae->ae_pni; struct ep *pep; struct arp *parp; int arplen; pep = (struct ep *) getbuf(Net.netpool); //生成ARP请求分组 if ((int)pep == SYSERR) return SYSERR; blkcopy(pep->ep_dst, pni->ni_hwb.ha_addr, pae->ae_hwlen); pep->ep_type = EPT_ARP; pep->ep_order = EPO_NET; parp = (struct arp *) pep->ep_data; parp->ar_hwtype = hs2net(pae->ae_hwtype); parp->ar_prtype = hs2net(pae->ae_prtype); parp->ar_hwlen = pae->ae_hwlen; parp->ar_prlen = pae->ae_prlen; parp->ar_op = hs2net(AR_REQUEST); blkcopy(SHA(parp), pni->ni_hwa.ha_addr, pae->ae_hwlen); blkcopy(SPA(parp), &pni->ni_ip, pae->ae_prlen); bzero(THA(parp), pae->ae_hwlen); blkcopy(TPA(parp), pae->ae_pra, pae->ae_prlen); arplen = ARP_HLEN + 2*(parp->ar_hwlen + parp->ar_prlen); write(pni->ni_dev, pep, EP_HLEN+arplen); //发送请求分组 return OK; }
输入模块
从一个队列中拿走一个分组,并连同解析出的物理地址一起发送给数据报链路层传输。
主要步骤为
1. 睡眠,直到ARP分组到达(请求或回答)。
2. 检查高速缓存表,寻找对应这个ARP分组的项目。
3. if ( 找到 ){
if ( 状态是 RESOLVED ){
更新项目;
return;
}
else if ( 状态是 PENDING ){
更新项目;
如果队列非空的话,将一个分组从队列中取出,将它与硬件地址一起发送给数据链路层;
return;
{
}
else{
创建一个项目;
将此项目添加到表中;
return;
}
4. 如果分组是一个请求, 发送ARP回答。
向表中增加已转换的表项
/* arpadd.c - arpadd */ #include <conf.h> #include <kernel.h> #include <network.h> struct arpentry *arpalloc(void); /*------------------------------------------------------------------------ * arpadd - Add a RESOLVED entry to the ARP cache * N.B. Assumes interrupts disabled *------------------------------------------------------------------------ */ struct arpentry * arpadd(struct netif *pni, struct arp *parp) { struct arpentry *pae; pae = arpalloc(); //在高速缓存中分配一个表项 /* 利用ARP分组信息填写表项 */ pae->ae_hwtype = parp->ar_hwtype; pae->ae_prtype = parp->ar_prtype; pae->ae_hwlen = parp->ar_hwlen; pae->ae_prlen = parp->ar_prlen; pae->ae_pni = pni; pae->ae_queue = EMPTY; memcpy(pae->ae_hwa, SHA(parp), parp->ar_hwlen); memcpy(pae->ae_pra, SPA(parp), parp->ar_prlen); /* 初始化 */ pae->ae_ttl = ARP_TIMEOUT; pae->ae_state = AS_RESOLVED; return pae; }
发送等待发送的分组
/* arpqsend.c - arpqsend */ #include <conf.h> #include <kernel.h> #include <network.h> int netwrite(struct netif *, struct ep *, unsigned); /*------------------------------------------------------------------------ * arpqsend - write packets queued waiting for an ARP resolution *------------------------------------------------------------------------ */ void arpqsend(struct arpentry *pae) { struct ep *pep; struct netif *pni; if (pae->ae_queue == EMPTY) return; pni = pae->ae_pni; /* 遍历等待发送的分组队列,调用netwrite逐个放入网络输出队列中 */ while (pep = (struct ep *)deq(pae->ae_queue)) netwrite(pni, pep, pep->ep_len); freeq(pae->ae_queue); //队列为空后,释放自身 pae->ae_queue = EMPTY; }
高速缓存管理
高速缓存是用来存储IP地址与物理地址的。如果一个IP进行需要发送一个数据报,但其目的地址不在ARP高速缓存中,就会创建一个新的表项,然后广播相应的请求分组,并等待分组置入队列中。
主要步骤:
1. 睡眠,周期性的唤醒。
2. 遍历高速缓存的每一个项目:
if ( 状态为FREE )
continue;
if ( 状态为PENDING ){
尝试次数+1;
if ( 尝试次数达到最大次数 ){
该项目状态->FREE;
撤销相应的队列;
}
else {
发送ARP请求;
}
continue;
}
else if( 状态为RESOLVED ){
将超时字段的值减去已经过去的时间;
若结果小于0,状态->FREE,撤销相应队列。
}
替换策略
/* arpalloc.c - arpalloc */ #include <conf.h> #include <kernel.h> #include <proc.h> #include <network.h> void arpdq(struct arpentry *); /*------------------------------------------------------------------------ * arpalloc - allocate an entry in the ARP table * N.B. Assumes interrupts DISABLED *------------------------------------------------------------------------ */ struct arpentry *arpalloc() { static int aenext = 0; //静态变量,保证循环 struct arpentry *pae; int i; for (i=0; i<ARP_TSIZE; ++i) { //遍历表 if (arptable[aenext].ae_state == AS_FREE) break; aenext = (aenext + 1) % ARP_TSIZE; //循环替换 } pae = & arptable[aenext]; aenext = (aenext + 1) % ARP_TSIZE; if (pae->ae_state == AS_PENDING && pae->ae_queue >= 0) arpdq(pae); pae->ae_state = AS_PENDING; return pae; }
本文转自cococo点点博客园博客,原文链接:http://www.cnblogs.com/coder2012/p/3445092.html,如需转载请自行联系原作者