IP 地址冲突检测程序源码(解决某种情况下检测无效的问题)

1. 参考代码

先上一个 arp 参考代码,但是这个参考代码不能全面的检测 IP 地址冲突。
http://blog.csdn.net/wanxiao009/article/details/5622296#

博文后面有改进版的示例代码,它能够全面的检测 IP 地址冲突。


2. 解决方案

很多博文介绍了 arp 机制检测 IP 地址冲突的原理与实现, 大致实现如下:

  • 使用广播 MAC 地址发送 arp 请求
  • 发送者 MAC 地址 和 IP 地址 分别填写本机 MAC 地址 和 IP 地址
  • 目标者 MAC 地址 和 IP 地址 分别填写 00:00:00:00:00:00 地址 和 待检测的 IP 地址

而检测 IP 地址冲突的情况分为以下两种:

  • 发送者 IP 地址和需要检测的 IP 地址不同(一般出现在 DHCP 请求到 IP 地址后检测局域网中该 IP 地址是否已使用,若没有才使用本次请求的 IP 地址)
  • 发送者 IP 地址和需要检测的 IP 地址相同(主要目的就是检测局域网中是否存在冲突的 IP 地址)

而在第二种情况下,使用很多博文介绍的方法(在发送 arp 请求时把发送者 IP 地址和目标者 IP 地址都填写本机的 IP 地址),很难接收到 arp reply,或者接收不到响应(博主在使用虚拟机的Linux系统上和在小型设备Linux系统上测试的结果有所不同,测试对象为小米手机和三星手机的结果也有所不同,看来存在不小差异性).

所以面对第二种情况,需按照如下方式来填写 arp 协议字段值:

  • 发送者 MAC 地址和 IP 地址分别填写 本机的 MAC 地址 和 0.0.0.0 IP 地址
  • 目标者 MAC 地址和 IP 地址分别填写 00:00:00:00:00:00 地址 和 待检测的 IP 地址

因此想要全面的检测 IP 地址冲突,需要按照如上 arp 协议字段值来进行填充。
总的来说,按照如上填充协议字段值,能够提高 arp 检测 IP 地址的准确性。

注:经过测试发现:当小米和三星手机锁屏并黑屏之后,改进前的版本和改进后的版本都降低了检测 IP 地址冲突的准确性,即发送 arp request 之后接收 arp reply 的概率偏低,而激活屏幕之后一切正常。以上测试在 Linux 设备和 Win7 系统的电脑上都测试过,结果是一样的。这表明手机在休眠状态下,为了降低功耗,可能把 WiFi 模块的 rx 接收端进行了控制。如果是这样的,那就没有百分百的概率能够检测 IP 地址冲突,只能尽可能的提高这个概率。针对检测对象是休眠型的设备可以间隔一段时间定期 arp request 检测。同时还发现,非手机类型的设备,使用该检测机制还是很准确的。


3. WIN7 系统检测 IP 地址冲突的过程

通过抓取 WIN7 系统检测 IP 地址冲突时进行的 arp 请求得知,也是按照上述方式进行的.

抓取准备工作:手机和电脑处于同一局域网,首先把手机设置为静态 IP(如: 192.168.1.200),然后把电脑也设置成同样的静态 IP,在点击确认的那一刻,电脑上会提示 IP 地址冲突,抓包软件也抓到了 arp request 和 arp reply。

电脑发送的 arp 请求
电脑发送的 arp 请求

接收到的 arp reply
这里写图片描述


4. 遇到的问题

当设备上存在有线网卡和无线网卡,并且两个网卡都连接同一局域网,而设备同一时刻只使用一个网卡的情况下,当有线网卡检测 IP 地址冲突时:

如果无线网卡的 IP 地址恰好是检测的 IP 地址,那么也会发送 arp reply;
经过测试发现,两个网卡同时工作的情况下,容易出现意想不到的情况,尽管无线网卡 IP 地址不相同,也发送了 arp reply。

解决方法:可以断开无线网卡的连接,再进行有线网卡 IP 地址冲突的检测。


5. 代码

本示例代码是在博文开头处展示的 arp 参考代码的基础上进行改进的。
编译之后,在 Linux 平台上执行如下命令进行测试:

./编译的程序名  网卡名  待检测的IP地址

checkip.h:

#ifndef __CHECKIP_H
#define __CHECKIP_H

struct arpMsg {
    struct ethhdr ethhdr;       /* Ethernet header */
    u_short htype;              /* hardware type (must be ARPHRD_ETHER) */
    u_short ptype;              /* protocol type (must be ETH_P_IP) */
    u_char  hlen;               /* hardware address length (must be 6) */
    u_char  plen;               /* protocol address length (must be 4) */
    u_short operation;          /* ARP opcode */
    u_char  sHaddr[6];          /* sender's hardware address */
    u_char  sInaddr[4];         /* sender's IP address */
    u_char  tHaddr[6];          /* target's hardware address */
    u_char  tInaddr[4];         /* target's IP address */
    u_char  pad[18];            /* pad for min. Ethernet payload (60 bytes) */
};

struct server_config_t {
    u_int32_t server;       /* Our IP, in network order */
    u_int32_t start;        /* Start address of leases, network order */
    u_int32_t end;          /* End of leases, network order */
    struct option_set *options; /* List of DHCP options loaded from the config file */
    char *interface;        /* The name of the interface to use */
    int ifindex;            /* Index number of the interface to use */
    unsigned char arp[6];       /* Our arp address */
    unsigned long lease;        /* lease time in seconds (host order) */
    unsigned long max_leases;   /* maximum number of leases (including reserved address) */
    char remaining;         /* should the lease file be interpreted as lease time remaining, or
                     * as the time the lease expires */
    unsigned long auto_time;    /* how long should udhcpd wait before writing a config file.
                     * if this is zero, it will only write one on SIGUSR1 */
    unsigned long decline_time;     /* how long an address is reserved if a client returns a
                         * decline message */
    unsigned long conflict_time;    /* how long an arp conflict offender is leased for */
    unsigned long offer_time;   /* how long an offered address is reserved */
    unsigned long min_lease;    /* minimum lease a client can request*/
    char *lease_file;
    char *pidfile;
    char *notify_file;      /* What to run whenever leases are written */
    u_int32_t siaddr;       /* next server bootp option */
    char *sname;            /* bootp server name */
    char *boot_file;        /* bootp boot file option */
};  

#endif /* __CHECKIP_H */

checkip.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> 
#include <string.h>
#include <errno.h>
#include <time.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

#include <netinet/in.h> 
#include <netinet/if_ether.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <arpa/inet.h>  

#include "checkip.h"

char ETH_INTERFACE[32];     

unsigned char MAC_BCAST_ADDR[6] = {0xff,0xff,0xff,0xff,0xff,0xff};

struct server_config_t server_config;

/*参数分别表示 网卡设备类型 接口检索索引 主机IP地址 主机arp地址*/
int read_interface(char *interface, int *ifindex, u_int32_t *addr, unsigned char *arp)
{
    int fd;
    /*ifreq结构定义在/usr/include\net/if.h,用来配置ip地址,激活接口,配置MTU等接口信息的。
    其中包含了一个接口的名字和具体内容——(是个共用体,有可能是IP地址,广播地址,子网掩码,MAC号,MTU或其他内容)。
    ifreq包含在ifconf结构中。而ifconf结构通常是用来保存所有接口的信息的。
    */
    struct ifreq ifr;
    struct sockaddr_in *our_ip;

    memset(&ifr, 0, sizeof(struct ifreq));
    /*建立一个socket函数,SOCK_RAW是为了获取第三个参数的IP包数据,
     IPPROTO_RAW提供应用程序自行指定IP头部的功能。
    */
    if((fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) >= 0) {
        ifr.ifr_addr.sa_family = AF_INET;
        /*将网卡类型赋值给ifr_name*/
        strcpy(ifr.ifr_name, interface);

        if (addr) {
            /*SIOCGIFADDR用于检索接口地址*/
            if (ioctl(fd, SIOCGIFADDR, &ifr) == 0) {
                /*获取本机IP地址,addr是一个指向该地址的指针*/
                our_ip = (struct sockaddr_in *) &ifr.ifr_addr;
                *addr = our_ip->sin_addr.s_addr;
                printf("%s (our ip) = %s\n", ifr.ifr_name, inet_ntoa(our_ip->sin_addr));
            } else {
                printf("SIOCGIFADDR failed, is the interface up and configured?: %s\n",
                        strerror(errno));
                return -1;
            }
        }

        /*SIOCGIFINDEX用于检索接口索引*/
        if (ioctl(fd, SIOCGIFINDEX, &ifr) == 0) {
            printf("adapter index %d\n", ifr.ifr_ifindex);
            /*指针ifindex 获取索引*/
            *ifindex = ifr.ifr_ifindex;
        } else {
            printf("SIOCGIFINDEX failed!: %s\n", strerror(errno));
            return -1;
        }
        /*SIOCGIFHWADDR用于检索硬件地址*/
        if (ioctl(fd, SIOCGIFHWADDR, &ifr) == 0) {
            /*所获取的硬件地址复制到结构server_config的数组arp[6]参数中*/
            memcpy(arp, ifr.ifr_hwaddr.sa_data, 6);
            printf("adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x\n",
                arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]);
        } else {
            printf("SIOCGIFHWADDR failed!: %s\n", strerror(errno));
            return -1;
        }
    }
    else {
        printf("socket failed!: %s\n", strerror(errno));
        return -1;
    }
    close(fd);
    return 0;
}

int check_ip(u_int32_t addr)
{
//    return arpping(addr, server_config.server, server_config.arp, ETH_INTERFACE);
     return arpping(addr, 0, server_config.arp, ETH_INTERFACE);
}

/*参数说明 目标IP地址,本机IP地址,本机mac地址,网卡类型*/
int arpping(u_int32_t yiaddr, u_int32_t ip, unsigned char *mac, char *interface)
{
    int timeout = 2;
    int optval = 1;
    int s;                      /* socket */
    int rv = 0;                 /* return value */
    struct sockaddr addr;       /* for interface name */
    struct arpMsg arp;
    fd_set fdset;
    struct timeval tm;
    time_t prevTime;
    struct in_addr ipAddr;

    /*socket发送一个arp包*/
    if ((s = socket (PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP))) == -1) {
        printf("Could not open raw socket\n");
        return -1;
    }

    /*设置套接口类型为广播,把这个arp包广播到这个局域网*/
    if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)) == -1) {
        printf("Could not setsocketopt on raw socket\n");
        close(s);
        return -1;
    }

    /* 对arp设置,这里按照arp包的封装格式赋值即可 */
    memset(&arp, 0, sizeof(arp));
    memcpy(arp.ethhdr.h_dest, MAC_BCAST_ADDR, 6);   /* MAC DA */
    memcpy(arp.ethhdr.h_source, mac, 6);        /* MAC SA */
    arp.ethhdr.h_proto = htons(ETH_P_ARP);      /* protocol type (Ethernet) */
    arp.htype = htons(ARPHRD_ETHER);        /* hardware type */
    arp.ptype = htons(ETH_P_IP);            /* protocol type (ARP message) */
    arp.hlen = 6;                   /* hardware address length */
    arp.plen = 4;                   /* protocol address length */
    arp.operation = htons(ARPOP_REQUEST);       /* ARP op code */
    *((u_int *) arp.sInaddr) = ip;          /* source IP address */
    *((u_int *) arp.tInaddr) = yiaddr;      /* target IP address */
    memcpy(arp.sHaddr, mac, 6);         /* source hardware address */
    memset(arp.tHaddr, 0, 6);           /* target hardware address */

    memset(&addr, 0, sizeof(addr));
    strcpy(addr.sa_data, interface);
    /*发送arp请求*/
    if (sendto(s, &arp, sizeof(arp), 0, &addr, sizeof(addr)) < 0)
    {
        printf("sendto error: %s\n",strerror(errno));
        return -1;
    }

    /* 利用select函数进行多路等待*/
    tm.tv_usec = 0;
    time(&prevTime);
    while (timeout > 0) {
        FD_ZERO(&fdset);
        FD_SET(s, &fdset);
        tm.tv_sec = timeout;
        if (select(s + 1, &fdset, (fd_set *) NULL, (fd_set *) NULL, &tm) < 0) {
            printf("Error on ARPING request: %s\n", strerror(errno));
            if (errno != EINTR) rv = 0;
        } else if (FD_ISSET(s, &fdset)) {
            if (recv(s, &arp, sizeof(arp), 0) < 0 ) 
            {
                rv = 0;
                printf("recv error: %s.\n",strerror(errno));
                break;
            }

            /*如果条件 htons(ARPOP_REPLY) bcmp(arp.tHaddr, mac, 6) == 0 *((u_int *) arp.sInaddr) == yiaddr 三者都为真,则ARP应答有效,说明这个地址是已存在的*/
            if(arp.operation == htons(ARPOP_REPLY))
            {
                fprintf(stdout, "\nrecv arp reply:\n");
            }else if(arp.operation == htons(ARPOP_REQUEST)){
                fprintf(stdout, "\nrecv arp request:\n");
            }
            fprintf(stdout, "Sender Mac %02x.%02x.%02x.%02x.%02x.%02x\n", arp.sHaddr[0],arp.sHaddr[1],arp.sHaddr[2],arp.sHaddr[3],arp.sHaddr[4],arp.sHaddr[5]);
            ipAddr.s_addr = *((u_int *) arp.sInaddr);
            fprintf(stdout, "Sender Ip %s\n", inet_ntoa(ipAddr));
            fprintf(stdout, "Target Mac %02x.%02x.%02x.%02x.%02x.%02x\n", arp.tHaddr[0],arp.tHaddr[1],arp.tHaddr[2],arp.tHaddr[3],arp.tHaddr[4],arp.tHaddr[5]);
            ipAddr.s_addr = *((u_int *) arp.tInaddr);
            fprintf(stdout, "Target Ip %s\n", inet_ntoa(ipAddr));
            fprintf(stdout, "local mac and target mac compare result %d \n", bcmp(arp.tHaddr, mac, 6));
            if (arp.operation == htons(ARPOP_REPLY) &&\
               (bcmp(arp.tHaddr, mac, 6) == 0) &&\
               (*((u_int *) arp.sInaddr) == yiaddr)) 
            {
                rv = 1;
                break;
            }
        }
        timeout -= time(NULL) - prevTime;
        time(&prevTime);
    }
    close(s);
    return rv;
}

int main(int argc, char *argv[])
{      
    int iRes;
    if(argc < 2)
    {   
        printf("Usage: interface and checkip ipaddr\n");
        exit(0);
    }

    strcpy(ETH_INTERFACE, argv[1]);

    /*读以太网接口函数,获取一些配置信息*/
    if (read_interface(ETH_INTERFACE, &server_config.ifindex,
               &server_config.server, server_config.arp) < 0)
    {
        exit(0);
    }

    /*IP检测函数*/
    iRes = check_ip(inet_addr(argv[2]));
    if(iRes == 0)
    {
        printf("IP:%s can use\n", argv[2]); 
    }
    else if(iRes == 1)
    {
        printf("IP:%s conflict\n", argv[2]);
    }

    return 0;
}

6. 结语

希望以上介绍,能够帮助到大家,在需要实现检测 IP 地址冲突的功能时,不至于像博主一样因为检测不全面而耗费了那么多时间.

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值