IP冲突检测(ARP)

arp协议

定义

Address Resolution Protocol,地址解析协议,是根据IP地址获取物理地址的一个TCP/IP 协议。

作用

在以太网环境中,数据的传输所依赖的是MAC地址而非IP地址,而将已知IP地址转换为MAC地址的工作由ARP协议来完成。

工作过程

假设主机A和主机B在同一网段,主机A要向主机B发送信息,过程如下

  1. ARP缓存表中有目标MAC地址

如果在ARP缓存表中,找到了对应的MAC地址,则使用MAC地址对IP数据包进行帧封装,并将数据包发送给主机B。

  1. 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冲突检测-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值