arp协议
定义
Address Resolution Protocol,地址解析协议,是根据IP地址获取物理地址的一个TCP/IP 协议。
作用
在以太网环境中,数据的传输所依赖的是MAC地址而非IP地址,而将已知IP地址转换为MAC地址的工作由ARP协议来完成。
工作过程
假设主机A和主机B在同一网段,主机A要向主机B发送信息,过程如下
- ARP缓存表中有目标MAC地址
如果在ARP缓存表中,找到了对应的MAC地址,则使用MAC地址对IP数据包进行帧封装,并将数据包发送给主机B。
- ARP缓存表中没有目标MAC地址
主机A如果没找到对应的MAC地址,则将缓存改数据报文,然后以广播方式发送一个ARP请求报文,报文中,包含发送端IP地址和MAC地址,目标IP地址和全为0的目标MAC地址。
ARP请求报文以广播方式返送,该网段上的所有主机,都可以接收到该请求,但只有被请求的主机,会对该请求进行处理。
主机B比较自己的IP地址和ARP请求报文中的目标IP地址,当两者相同时,将ARP请求报文中的发送端的IP地址和MAC地址存入自己的ARP表中,之后以单播方式发送ARP相应报文给主机A,其中包含了自己的MAC地址。
写入ARP缓存表,主机A收到ARP响应报文后,将主机B的MAC地址加入到自己的ARP表中,同时将IP数据包进行封装后发送出去
arp请求包查看章节《1、OSI基础概念 》,4.3 ARP数据报格式
IP冲突
以上,我们知道,如果局域网内,有该目标IP的设备接收到ARP请求帧,就会回复ARP应答帧,我们只需要统计应答帧的个数,就知道是否有IP冲突。
DEMO
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <time.h>
void send_arp_request(int sockfd, const char *iface, const char *target_ip)
{
struct ifreq ifr;
struct sockaddr_ll sa;
unsigned char buffer[ETH_FRAME_LEN];
struct ether_header *eth_header = (struct ether_header *) buffer;
struct ether_arp *arp_packet = (struct ether_arp *) (buffer + sizeof(struct ether_header));
// 获取接口的 MAC 地址
strncpy(ifr.ifr_name, iface, IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) < 0) {
perror("SIOCGIFHWADDR failed");
exit(1);
}
// 设置以太网头部
memset(eth_header->ether_dhost, 0xff, ETH_ALEN); // 目标 MAC 地址为广播地址
memcpy(eth_header->ether_shost, ifr.ifr_hwaddr.sa_data, ETH_ALEN); // 源 MAC 地址
eth_header->ether_type = htons(ETHERTYPE_ARP);
// 设置 ARP 包
arp_packet->ea_hdr.ar_hrd = htons(ARPHRD_ETHER);
arp_packet->ea_hdr.ar_pro = htons(ETHERTYPE_IP);
arp_packet->ea_hdr.ar_hln = ETH_ALEN;
arp_packet->ea_hdr.ar_pln = 4;
arp_packet->ea_hdr.ar_op = htons(ARPOP_REQUEST);
memcpy(arp_packet->arp_sha, ifr.ifr_hwaddr.sa_data, ETH_ALEN); // 源 MAC 地址
memset(arp_packet->arp_tha, 0x00, ETH_ALEN); // 目标 MAC 地址为空
inet_pton(AF_INET, "0.0.0.0", arp_packet->arp_spa); // 源 IP 地址
inet_pton(AF_INET, target_ip, arp_packet->arp_tpa); // 目标 IP 地址
// 获取接口的索引
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) {
perror("SIOCGIFINDEX failed");
exit(1);
}
// 设置套接字地址
memset(&sa, 0, sizeof(sa));
sa.sll_ifindex = ifr.ifr_ifindex;
sa.sll_halen = ETH_ALEN;
memset(sa.sll_addr, 0xff, ETH_ALEN); // 目标 MAC 地址为广播地址
// 发送 ARP 请求
if (sendto(sockfd, buffer, sizeof(struct ether_header) + sizeof(struct ether_arp), 0,
(struct sockaddr *)&sa, sizeof(sa)) < 0) {
perror("sendto failed");
exit(1);
}
}
int handle_arp_response(const u_char *packet, const char *target_ip, int *conflict_detected)
{
struct ether_header *eth_header = (struct ether_header *) packet;
struct ether_arp *arp_packet = (struct ether_arp *) (packet + sizeof(struct ether_header));
if (ntohs(eth_header->ether_type) == ETHERTYPE_ARP && ntohs(arp_packet->ea_hdr.ar_op) == ARPOP_REPLY)
{
char sender_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, arp_packet->arp_spa, sender_ip, INET_ADDRSTRLEN);
if (strcmp(sender_ip, target_ip) == 0) {
#if 0
printf("ARP Reply: %s is at %02x:%02x:%02x:%02x:%02x:%02x\n",
sender_ip,
arp_packet->arp_sha[0],
arp_packet->arp_sha[1],
arp_packet->arp_sha[2],
arp_packet->arp_sha[3],
arp_packet->arp_sha[4],
arp_packet->arp_sha[5]);
#endif
(*conflict_detected)++;
return 1;
}
}
return 0;
}
int ipconflic(const char * iface, const char * target_ip)
{
int sockfd=-1;
char buffer[ETH_FRAME_LEN];
struct sockaddr saddr;
socklen_t saddr_len = sizeof(saddr);
int conflict_detected = 0;
ssize_t num_bytes = -1;
int retval = -1;
time_t start_time, current_time;
const int timeout = 5;//5s
fd_set read_fds;
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
/* 1.创建一个原始套接字 */
sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sockfd < 0) {
perror("Socket creation failed");
return -1;
}
/* 2.发送 ARP 请求 */
send_arp_request(sockfd, iface, target_ip);
//获取当前时间
start_time = time(NULL);
/* 3.接收 ARP 响应 */
while (1)
{
FD_ZERO(&read_fds);
FD_SET(sockfd, &read_fds);
retval = select(sockfd + 1, &read_fds, NULL, NULL, &tv);
if(retval == -1)
{
perror("select failed");
close(sockfd);
return 1;
}
else if(retval)
{
num_bytes = recvfrom(sockfd, buffer, ETH_FRAME_LEN, 0, &saddr, &saddr_len);
if (num_bytes < 0) {
perror("Packet receive failed");
close(sockfd);
return -1;
}
// 处理 ARP 响应
handle_arp_response((const u_char *) buffer, target_ip, &conflict_detected);
}
else
{
current_time = time(NULL);
if(difftime(current_time, start_time) >= timeout)
break;
}
}
close(sockfd);
return conflict_detected;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <interface> <IP address>\n", argv[0]);
return 1;
}
const char *iface = argv[1];
const char *target_ip = argv[2];
printf("ret=%d\n", ipconflic(iface, target_ip));
return 0;
}
补充知识
ARP数据报格式
在网络通讯时,源主机的应用程序知道目的主机的IP地址和端口号,却不知道目的主机的硬件地址,而数据包首先是被网卡接收到再去处理上层协议的,如果接收到的数据包的硬件地址与本机不符,则直接丢弃。
因此在通讯前必须获得目的主机的硬件地址。
ARP协议就起到这个作用。源主机发出ARP请求,询问“IP地址是192.168.0.1的主机的硬件地址是多少”,并将这个请求广播到本地网段(以太网帧首部的硬件地址填FF:FF:FF:FF:FF:FF表示广播),目的主机接收到广播的ARP请求,发现其中的IP地址与本机相符,则发送一个ARP应答数据包给源主机,将自己的硬件地址填写在应答包中。
arp缓冲表
每台主机都维护一个ARP缓存表,缓存表中的表项有过期时间(一般为20分钟),如果20分钟内没有再次使用某个表项,则该表项失效,下次还要发ARP请求来获得目的主机的硬件地址。想一想,为什么表项要有过期时间而不是一直有效?
ARP数据报的格式如下所示:
ARP数据报格式
源MAC地址、目的MAC地址在以太网首部和ARP请求中各出现一次,对于链路层为以太网的情况是多余的,但如果链路层是其它类型的网络则有可能是必要的。
- 硬件类型指链路层网络类型,1为以太网,
- 协议类型指要转换的地址类型,0x0800为IP地址,
- 后面两个地址长度对于以太网地址和IP地址分别为6和4(字节),
- op字段为1表示ARP请求,op字段为2表示ARP应答。
#include <netinet/if_ether.h>
//以太网头部
struct ether_header {
u_int8_t ether_dhost[ETH_ALEN]; // 目标 MAC 地址
u_int8_t ether_shost[ETH_ALEN]; // 源 MAC 地址
u_int16_t ether_type; // 以太网类型
};
//arp数据包
#include <netinet/if_ether.h>
struct ether_arp {
struct arphdr ea_hdr; // ARP 头部
u_int8_t arp_sha[ETH_ALEN]; // 发送方硬件地址(MAC 地址)
u_int8_t arp_spa[4]; // 发送方协议地址(IP 地址)
u_int8_t arp_tha[ETH_ALEN]; // 目标硬件地址(MAC 地址)
u_int8_t arp_tpa[4]; // 目标协议地址(IP 地址)
};
#include <netinet/if_ether.h>
struct arphdr {
unsigned short ar_hrd; // 硬件类型
unsigned short ar_pro; // 协议类型
unsigned char ar_hln; // 硬件地址长度
unsigned char ar_pln; // 协议地址长度
unsigned short ar_op; // 操作码(ARP 请求或响应)
};
ARP请求帧
ARP请求帧
0000 ff ff ff ff ff ff 6c ec f6 76 60 64 08 06 00 01 ......l..v`d....
0010 08 00 06 04 00 01 6c ec f6 76 60 64 00 00 00 00 ......l..v`d....
0020 00 00 00 00 00 00 c0 a8 01 3d .........=
以太网首部(14字节)
0000 ff ff ff ff ff ff //目标 MAC 地址 采用广播地址,ff ff ff ff ff ff,表示该请求包将发送到网络中的所有设备。
0000 6c ec f6 76 60 64 //源 MAC 地址
0000 08 06 // 以太网类型, 0806是ARP
ARP帧(28字节)
0000 00 01 //硬件类型:0x0001表示以太网
0010 08 00 //协议类型:0x0800表示IPv4协议
0010 06 //硬件地址(MAC地址)长度
0010 04 //协议地址(IP地址)长度:4
0010 00 01 //操作码,表示ARP操作类型,对于ARP请求,值为0x0001
0010 6c ec f6 76 60 64 //源MAC地址
0010 00 00 00 00 //源IP地址
0020 00 00 00 00 00 00 //目标MAC地址
0020 c0 a8 01 3d //目标IP地址 192.168.1.61
ARP应答帧
0000 6c ec f6 76 60 64 2a b3 c4 d5 de f4 08 06 00 01 l..v`d*.........
0010 08 00 06 04 00 02 2a b3 c4 d5 de f4 c0 a8 01 3d ......*........=
0020 6c ec f6 76 60 64 00 00 00 00 00 00 00 00 00 00 l..v`d..........
0030 00 00 00 00 00 00 00 00 00 00 00 00 ............
以太网首部
0000 6c ec f6 76 60 64 // 目标 MAC 地址,也就是发送广播的设备的MAC地址
0000 2a b3 c4 d5 de f4 //应答设备的MAC地址
0000 08 06 // 以太网类型, ARP
ARP帧
0000 00 01 //硬件类型
0010 08 00 //协议类型
0010 06 //硬件大小
0010 04 //协议大小
0010 00 02 //
0010 2a b3 c4 d5 de f4 //发送端MAC地址
0010 c0 a8 01 3d //发送端IP地址 192.168.1.61
0020 6c ec f6 76 60 64 //目标MAC地址,也就是发送广播的设备
0020 00 00 00 00 //目标IP地址
后续的00是填充位
由于以太网规定最小数据长度为46字节,ARP帧长度只有28字节,因此有18字节填充位,填充位的内容没有定义,与具体实现相关。
转载请标明出处 IP冲突检测-优快云博客