(是根据IP地址获取物理地址的一个TCP/IP协议 同时将IP地址和硬件地址存入本机ARP缓存中,下次请求时直接查询ARP缓存。)
对于一个设备用的ARP协议而言,重要的东西包括三方面:
1. 一个本地的IP与MAC地址的缓存表.以有对应的更新和查询操作. 2. 一个发送ARP请求的函数. 3. 一个处理ARP回复的函数.
下面我们来看uIP中是如何实现的(代码见: 首先,定义一个缓存表的数据结构,99行起: struct arp_entry { u16_t ipaddr[2];
struct uip_eth_addr ethaddr; u8_t time; };
只有三个项,很简单
第一项是ip地址,16*2=4*8位的,保存四个八位组. 第二项是MAC地址. 第三项是缓存更新时间.
下来是ARP请求发送函数: L325
/*-----------------------------------------------------------------------------------*/ /**
* Prepend Ethernet header to an outbound IP packet and see if we need * to send out an ARP request.
*为传出的IP包添加以太网头并看是否需要发送ARP请求.
* This function should be called before sending out an IP packet. The * function checks the destination IP address of the IP packet to see * what Ethernet MAC address that should be used as a destination MAC * address on the Ethernet.
*此函数应该在发送IP包时调用,它会检查IP包的目的IP地址,看看以太网应该使用什么目的MAC地址.
* If the destination IP address is in the local network (determined * by logical ANDing of netmask and our IP address), the function * checks the ARP cache to see if an entry for the destination IP * address is found. If so, an Ethernet header is prepended and the * function returns. If no ARP cache entry is found for the
* destination IP address, the packet in the uip_buf[] is replaced by * an ARP request packet for the IP address. The IP packet is dropped * and it is assumed that they higher level protocols ., TCP) * eventually will retransmit the dropped packet.
*如果目的IP地址是在局域网中(由IP地址与子网掩码的与逻辑决定),函数就会从ARP缓存表中查找有
*无对应项.若有,就取对应的MAC地址,加上以太网头,并返回,否则uip_buf[]中的数据包会
被替换成一个
*目的IP在址的ARP请求.原来的IP包会被简单的仍掉,此函数假设高层协议(如TCP)会最终重传扔掉的包.
* If the destination IP address is not on the local network, the IP * address of the default router is used instead.
*如果目标IP地址并非一个局域网IP,则会使用默认路由的IP地址.
* When the function returns, a packet is present in the uip_buf[] * buffer, and the length of the packet is in the global variable * uip_len.函数返回时,uip_buf[]中已经有了一个包,其长度由uip_len指定. */
/*-----------------------------------------------------------------------------------*/ void
uip_arp_out(void) {
struct arp_entry *tabptr;
/* Find the destination IP address in the ARP table and construct the Ethernet header. If the destination IP addres isn't on the local network, we use the default router's IP address instead.
果目的IP地址不在局域网中,则使用默认If not ARP table entry is found, we overwrite the original IP
packet with an ARP request for the IP address. */
/* First check if destination is a local broadcast. 首先检查目标是不是广播*/ if(uip_ipaddr_cmp(IPBUF->destipaddr, broadcast_ipaddr)) { memcpy(IPBUF-> , 6); } else {
/* Check if the destination address is on the local network. 检查目标地址是否在局域网内 */
if(!uip_ipaddr_maskcmp(IPBUF->destipaddr, uip_hostaddr, uip_netmask)) { /* Destination address was not on the local network, so we need to use the default router's IP address instead of the destination
address when determining the MAC address. 目的地址不在局域网内,所以保用默认路由器的地址来确在MAC地址*/
uip_ipaddr_copy(ipaddr, uip_draddr); } else {
/* Else, we use the destination IP address. 否则,使用目标IP地址*/ uip_ipaddr_copy(ipaddr, IPBUF->destipaddr); }
for(i = 0; i < UIP_ARPTAB_SIZE; ++i) {tabptr = &arp_table; if(uip_ipaddr_cmp(ipaddr, tabptr->ipaddr)) { break; } } if(i == UIP_ARPTAB_SIZE) { /* The destination address was not in our ARP table, so we overwrite the IP packet with an ARP request. 如果遍历到头没找到,将原IP包替换为ARP请求并返回*/ memset(BUF-> 0xff, 6); memset(BUF->, 0x00, 6); memcpy(BUF-> , 6); memcpy(BUF->, , 6); uip_ipaddr_copy(BUF->dipaddr, ipaddr); uip_ipaddr_copy(BUF->sipaddr, uip_hostaddr); BUF->opcode = HTONS(ARP_REQUEST); /* ARP request. */ BUF->hwtype = HTONS(ARP_HWTYPE_ETH); BUF->protocol = HTONS(UIP_ETHTYPE_IP); BUF->hwlen = 6; BUF->protolen = 4; BUF-> = HTONS(UIP_ETHTYPE_ARP); uip_appdata = &uip_buf[UIP_TCPIP_HLEN + UIP_LLH_LEN]; uip_len = sizeof(struct arp_hdr); return; } /* Build an ethernet header. 如果是在局域网中,且在ARP缓存中找到了(如果没找到进行不到这一步,在上面就返回了),则构建以太网头*/ memcpy(IPBUF-> tabptr->, 6); } memcpy(IPBUF-> , 6); IPBUF-> = HTONS(UIP_ETHTYPE_IP); uip_len += sizeof(struct uip_eth_hdr); }
以上内容自325行起.下面再总结一下其基本顺序:
用IPBUF->来存储目的IP地址,它有可能是局域网内一主机IP,也可能是路由器IP(如果是发往外网,即原来的目的IP不在局域网内),还有可能是广播专用的IP. 先看是不是在广播:如果是广播,将IPBUF->设为广播IP.
再看是不是在局域网内:如果不是,则将IPBUF->设为路由器IP.
如果在局域网内,查看是否已经存在于ARP缓存表中:如果不在,将要发送的换成ARP请求,返回.
如果已存在,则查找使用查找到的MAC地址为IP包添加以太网头.
这里还要解释一些细节问题,主要是:
1. 如何在IP包上添加以太网头
2. 如果将IP包替换成ARP请求,ARP请求的格式是什么. 3. 广播地址
这些问题将在二楼来说.
将IP包替换成ARP请求部分代码(实际上IP包是放在uip_buf[]里的,这里只是将uip_buf[]填充上ARP请求即可),于的388行:
/* The destination address was not in our ARP table, so we overwrite the IP packet with an ARP request. */
memset(BUF-> 0xff, 6); memset(BUF->, 0x00, 6); memcpy(BUF-> , 6); memcpy(BUF->, , 6);
uip_ipaddr_copy(BUF->dipaddr, ipaddr);
uip_ipaddr_copy(BUF->sipaddr, uip_hostaddr);
BUF->opcode = HTONS(ARP_REQUEST); /* ARP request. */ BUF->hwtype = HTONS(ARP_HWTYPE_ETH); BUF->protocol = HTONS(UIP_ETHTYPE_IP); BUF->hwlen = 6; BUF->protolen = 4;
BUF-> = HTONS(UIP_ETHTYPE_ARP);
uip_appdata = &uip_buf[UIP_TCPIP_HLEN + UIP_LLH_LEN];
uip_len = sizeof(struct arp_hdr); return;
首先解释这里的BUF(于的116行):
#define BUF ((struct arp_hdr *)&uip_buf[0])
可见这里的BUF就是uip_buf[],只不过这里将它取做一个struct arp_hdr的结构体: struct arp_hdr {
struct uip_eth_hdr ethhdr;
u16_t hwtype; struct arp_hdr的第二个成员u16_t hwtype,对应图片中第三项,2字节硬件类型(值为1表示以太网).
struct arp_hdr的第三个成员u16_t protocol,对应图片中第四项,2字节要映射的协议地址类型(ip地址为0x0800).
struct arp_hdr的第四个成员u8_t hwlen,对应图片中第五项,1字节硬件地址长度(对MAC地址来说为6).
struct arp_hdr的第五个成员u8_t protolen,对应图片中第六项,1字节协议地址长度(对IP地址来说为4).
struct arp_hdr的第六个成员u16_t opcode,对应图片中第七项,2字节操作码(1 ARP请求,2 ARP应答,3 RARP请求,4 RARP应答,必须字段).
struct arp_hdr的第七个成员struct uip_eth_addr shwaddr,对应图片中第八项,6字节源以太网地址.
struct arp_hdr的第八个成员u16_t sipaddr[2];,对应图片中第九项,2字节源IP地址. struct arp_hdr的第九个成员struct uip_eth_addr dhwaddr,对应图片中第十项,6字节目标以太网地址.
struct arp_hdr的第十个成员u16_t dipaddr[2];,对应图片中第十一项,2字节目标IP地址.
上面绿色的表示已经详解的,红字的表示要进一步说明的.
这就是一个ARP帧的结构,可以看到,里面的源和目的以太网地址都是重复的. 我们再看函数中的代码: memset(BUF-> 0xff, 6); memset(BUF->, 0x00, 6); memcpy(BUF-> , 6); memcpy(BUF->, , 6);
这里四个memset,重复的源和目的以太网地址一起设置了,四个memset对应图片中的1,10,2,8项.但是:对1和10两项,都是源以太网地址,但置的值是不同的,分别为0xff*6和0x00*6.为什么会这样呢?因为他们的用处不一样,见:【相关资料】ARP分组格式(帧格式,报文格式)
6+6– 以太网的源地址和目的地址,目的地址为全1的为广播地址
注意这里说,目的地址为全为1的广播地址.什么意思?当你发送一个ARP请求是,你是想知道一个IP对应的以太网地址(即MAC地址),但是你现在不知道目的主机的以太网地址,你问谁啊?不知道问谁,这种情况下,只能是广播一下了,0xff*6就是广播地址.
从图片中可以看到,ARP包是分成两部分的,前6+6+2叫做\以太网首部\它的意义就是\分组是从谁(源地址)发给谁(目的地址)的什么类型(帧类型,请求和应答为0x0806)\第二部分则是内容. 来看这个例子:
请求帧如下(为了清晰在每行的前面加了字节计数,每行16个字节): 以太网首部(14字节)
0000: ff ff ff ff ff ff 00 05 5d 61 58 a8 08 06 ARP帧(28字节)
0000:? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? 00 01 0010: 08 00 06 04 00 01 00 05 5d 61 58 a8 c0 a8 00 37 0020: 00 00 00 00 00 00 c0 a8 00 02 填充位(18字节)
0020:? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? 00 77 31 d2 50 10 0030: fd 78 41 d3 00 00 00 00 00 00 00 00
以太网首部: ? ? 目的主机采用广播地址,源主机的MAC地址是00:05:5d:61:58:a8,上层协议类型0x0806表示ARP。 ARP帧: