ARP 協定
位址解析協定(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)用于代替位址解析協定。
ARP 基本原理
在每台安裝有 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 函數
SendARP 函數通過發送一個位址解析協定(ARP)請求,來獲得與指定的目的地 IPv4 位址相對應的實體位址。
文法格式如下:
DWORD SendARP(
_In_ IPAddr DestIP,
_In_ IPAddr SrcIP,
_Out_ PULONG pMacAddr,
_Inout_ PULONG PhyAddrLen
);
四個參數分别是:目的 IP 位址、源 IP 位址(一般預設為 0)、實體位址緩沖區指針、以及緩沖區長度。
擷取 MAC 位址
擷取與指定的 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;
}
更多參考
- SendARP function
- 位址解析協定