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 地址衝突的功能時,不至於像博主一樣因爲檢測不全面而耗費了那麼多時間.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章