地址解析协议(ARP)是通过解析网路层地址来找寻数据链路层地址的一个在网络协议包中极其重要的网络传输协议。ARP 最初在 1982 年的 RFC 826 中提出并纳入互联网标准 STD 37. ARP 也可能指是在多数操作系统中管理其相关地址的一个进程。
ARP 是通过网络地址(例:IPv4)来定位 MAC 地址(也称为乙太地址)。ARP 已经在很多网路层和数据链接层之间得以实现,包括:IPv4,Chaosnet,DECnet 和 Xerox PARC Universal Packet (PUP) 使用 IEEE 802 标准,光纤分布式数据接口,X.25,帧中继和异步传输模式(ATM),IEEE 802.3 和 IEEE 802.11 标准上 IPv4 占了多数流量。
在 IPv6 中邻居发现协议(NDP)用于代替地址解析协议。
在每台安装有 TCP/IP 协议的计算机或路由器里都有一个 ARP 缓存表,表里的 IP 地址与 MAC 地址是一对应的,如下表所示:
主机名称 | IP 地址 | MAC 地址 |
---|---|---|
A | 192.168.38.10 | 00-AA-00-62-D2-02 |
B | 192.168.38.11 | 00-BB-00-62-C2-02 |
C | 192.168.38.12 | 00-CC-00-62-C2-02 |
D | 192.168.38.13 | 00-DD-00-62-C2-02 |
E | 192.168.38.14 | 00-EE-00-62-C2-02 |
… | … | … |
以主机 A(192.168.38.10)向主机 B(192.168.38.11)发送数据为例:
当发送数据时,主机 A 会在自己的 ARP 缓存表中寻找是否有目标 IP 地址。如果找到就知道目标 MAC 地址为(00-BB-00-62-C2-02),直接把目标 MAC 地址写入帧里面发送就可。
如果在 ARP 缓存表中没有找到相对应的IP地址,主机 A 就会在网络上发送一个广播(ARP request),目标 MAC 地址是“FF.FF.FF.FF.FF.FF”,这表示向同一网段内的所有主机发出这样的询问:“192.168.38.11 的 MAC 地址是什么?”
网络上其他主机并不响应ARP询问,只有主机 B 接收到这个帧时,才向主机 A 做出这样的回应(ARP response):“192.168.38.11 的 MAC 地址是 00-BB-00-62-C2-02”,此回应以单播方式。这样,主机A就知道主机 B 的 MAC 地址,它就可以向主机B发送信息。同时它还更新自己的 ARP 高速缓存(ARP cache),下次再向主机B发送信息时,直接从 ARP 缓存表里查找就可。
ARP 缓存表采用老化机制,在一段时间内如果表中的某一行没有使用,就会被删除,这样可以大大减少 ARP 缓存表的长度,加快查询速度。
SendARP 函数通过发送一个地址解析协议(ARP)请求,来获得与指定的目的地 IPv4 地址相对应的物理地址。
语法格式如下:
DWORD SendARP( _In_ IPAddr DestIP, _In_ IPAddr SrcIP, _Out_ PULONG pMacAddr, _Inout_ PULONG PhyAddrLen );
四个参数分别是:目的 IP 地址、源 IP 地址(一般默认为 0)、物理地址缓冲区指针、以及缓冲区长度。
获取与指定的 IPv4 地址相关联的硬件或 MAC 地址:
代码如下:
#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include <winsock2.h> #include <iphlpapi.h> #include <stdio.h> #include <stdlib.h> #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "ws2_32.lib") void usage(char *pname) { printf("Usage: %s [options] ip-address\n", pname); printf("\t -h \t\thelp\n"); printf("\t -l length \tMAC physical address length to set\n"); printf("\t -s src-ip \tsource IP address\n"); exit(1); } int __cdecl main(int argc, char **argv) { DWORD dwRetVal; IPAddr DestIp = 0; IPAddr SrcIp = 0; /* default for src ip */ ULONG MacAddr[2]; /* for 6-byte hardware addresses */ ULONG PhysAddrLen = 6; /* default to length of six bytes */ char *DestIpString = NULL; char *SrcIpString = NULL; BYTE *bPhysAddr; unsigned int i; if (argc > 1) { for (i = 1; i < (unsigned int) argc; i++) { if ((argv[i][0] == '-') || (argv[i][0] == '/')) { switch (tolower(argv[i][1])) { case 'l': PhysAddrLen = (ULONG) atol(argv[++i]); break; case 's': SrcIpString = argv[++i]; SrcIp = inet_addr(SrcIpString); break; case 'h': default: usage(argv[0]); break; } /* end switch */ } else DestIpString = argv[i]; } /* end for */ } else usage(argv[0]); if (DestIpString == NULL || DestIpString[0] == '\0') usage(argv[0]); DestIp = inet_addr(DestIpString); memset(&MacAddr, 0xff, sizeof (MacAddr)); printf("Sending ARP request for IP address: %s\n", DestIpString); dwRetVal = SendARP(DestIp, SrcIp, &MacAddr, &PhysAddrLen); if (dwRetVal == NO_ERROR) { bPhysAddr = (BYTE *) & MacAddr; if (PhysAddrLen) { for (i = 0; i < (int) PhysAddrLen; i++) { if (i == (PhysAddrLen - 1)) printf("%.2X\n", (int) bPhysAddr[i]); else printf("%.2X-", (int) bPhysAddr[i]); } } else printf ("Warning: SendArp completed successfully, but returned length=0\n"); } else { printf("Error: SendArp failed with error: %d", dwRetVal); switch (dwRetVal) { case ERROR_GEN_FAILURE: printf(" (ERROR_GEN_FAILURE)\n"); break; case ERROR_INVALID_PARAMETER: printf(" (ERROR_INVALID_PARAMETER)\n"); break; case ERROR_INVALID_USER_BUFFER: printf(" (ERROR_INVALID_USER_BUFFER)\n"); break; case ERROR_BAD_NET_NAME: printf(" (ERROR_GEN_FAILURE)\n"); break; case ERROR_BUFFER_OVERFLOW: printf(" (ERROR_BUFFER_OVERFLOW)\n"); break; case ERROR_NOT_FOUND: printf(" (ERROR_NOT_FOUND)\n"); break; default: printf("\n"); break; } } return 0; }