最近遇到一個需求,或者說是一個使用者現場問題。
我們裝置先安裝,設定dhcp模式獲取ip進行聯網,後來又安裝了其他裝置,但該裝置是手動設定的靜態ip地址,正好與我們裝置衝突,造成網路故障。
那我們就需要有一個能夠檢測ip衝突的方法,這個可以使用ARP協定的ACD功能(Address Conflict Detection,在RFC 5227中提出)。
(ARP協定主要用於區域網IP地址與MAC地址轉換,因為我們網路主機之間收發資料其實是使用的硬體地址而非IP地址;向外網發資料時其實就是發給閘道器,閘道器再發給閘道器以此類推)
ACD功能中使用ARP Request的不同兩種引數填充方式分別作為ARP Probe和ARP Announcement,後者是告訴別人我要用某個IP地址了,前者是聞訊區域網內是否有人已經用了某個IP地址。
因此我們可以使用ARP Probe來檢測IP衝突。
當我們通過網路卡向區域網廣播ARP Probe時,如果其他主機使用了我們聞訊的IP地址,則會響應,我們就知道IP衝突了。
程式碼如下
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <errno.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <linux/if_packet.h> 8 #include <netdb.h> 9 #include <unistd.h> 10 #include <arpa/inet.h> 11 #include <sys/ioctl.h> 12 #include <net/ethernet.h> 13 #include <netinet/ether.h> 14 #include <net/if.h> 15 #include <netinet/ip.h> 16 #include <sys/ioctl.h> 17 #include <net/if.h> 18 #include <netinet/in.h> 19 #include <netdb.h> 20 #include <sys/time.h> 21 22 static int get_mac(const char *if_name, char *mac); 23 static int get_ifidx(const char *if_name, int *idx); 24 25 int main(int argc, char *argv[]) 26 { 27 if (argc < 3) 28 { 29 printf("Usage: %s if_name detect_ip\n\tLike: %s wlan0 192.168.0.211\n", argv[0], argv[0]); 30 exit(EXIT_FAILURE); 31 } 32 33 const char *if_name = argv[1]; 34 const char *dst_ip = argv[2]; 35 36 unsigned int ip4bit = 0; 37 { 38 struct in_addr addr = {0}; 39 if (inet_aton(dst_ip, &addr) == 0) 40 { 41 perror("inet_aton"); 42 exit(EXIT_FAILURE); 43 } 44 ip4bit = addr.s_addr; 45 } 46 47 char mac[6] = {0}; 48 if (get_mac(if_name, mac) != 0) 49 { 50 perror("inet_aton"); 51 exit(EXIT_FAILURE); 52 } 53 54 int sock_client = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); 55 if (sock_client < 0) 56 { 57 perror("socket"); 58 exit(EXIT_FAILURE); 59 } 60 int sock_server; 61 if ((sock_server = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP))) < 0) 62 { 63 perror("cannot create socket"); 64 exit(EXIT_FAILURE); 65 } 66 struct timeval tv_out; 67 tv_out.tv_sec = 1; 68 tv_out.tv_usec = 0; 69 setsockopt(sock_server, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out)); 70 71 unsigned char arp_probe_msg[] = { 72 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /*目的mac地址*/ 73 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*源mac地址*/ 74 0x08, 0x06, /*幀型別*/ 75 76 /*ARP報文頭部(28個位元組)*/ 77 0x00, 0x01, /*硬體型別*/ 78 0x08, 0x00, /*協定型別*/ 79 6, /*硬體地址長度*/ 80 4, /*協定地址長度*/ 81 0x00, 0x01, /*ARP請求*/ 82 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*源mac地址*/ 83 0, 0, 0, 0, /*源IP*/ 84 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /*目的mac地址*/ 85 0, 0, 0, 0 /*目的IP*/ 86 }; 87 88 memcpy(arp_probe_msg + 6, mac, 6); 89 memcpy(arp_probe_msg + 22, mac, 6); 90 memcpy(arp_probe_msg + 38, &ip4bit, 4); 91 92 int if_idx; 93 if (get_ifidx(if_name, &if_idx) != 0) 94 exit(EXIT_FAILURE); 95 96 // 傳送5次 97 for (int i = 0; i < 5; ++i) 98 { 99 struct sockaddr_ll sll; 100 bzero(&sll, sizeof(sll)); 101 sll.sll_ifindex = if_idx; 102 103 if (sendto(sock_client, arp_probe_msg, sizeof arp_probe_msg, 0, (struct sockaddr *)&sll, sizeof(sll)) < sizeof arp_probe_msg) 104 { 105 perror("sendto"); 106 exit(EXIT_FAILURE); 107 } 108 } 109 110 char buffer[42] = {0}; 111 int recv_count = 0; 112 // 接受最多100條或3秒超時 113 struct timeval recv_start_time; 114 gettimeofday(&recv_start_time, NULL); 115 while (recv_count++ < 100 && recv(sock_server, buffer, sizeof(buffer), 0)) 116 { 117 if ((((buffer[12]) << 8) + buffer[13]) != ETH_P_ARP) 118 continue; 119 struct timeval now_time; 120 gettimeofday(&now_time, NULL); 121 if (now_time.tv_sec - recv_start_time.tv_sec > 2) 122 break; 123 char arp_rsp_mac[18] = {0}; 124 char arp_rsp_ip[18] = {0}; 125 sprintf(arp_rsp_mac, "%02x:%02x:%02x:%02x:%02x:%02x", buffer[22], buffer[23], buffer[24], buffer[25], buffer[26], buffer[27]); 126 sprintf(arp_rsp_ip, "%d.%d.%d.%d", buffer[28], buffer[29], buffer[30], buffer[31]); 127 // printf("%s %s\n", arp_rsp_mac, arp_rsp_ip); 128 if (strcmp(arp_rsp_ip, dst_ip) == 0) 129 { 130 printf("%s", arp_rsp_mac); 131 return 0; 132 } 133 } 134 135 return 0; 136 } 137 138 int get_mac(const char *if_name, char *mac) 139 { 140 int fd, rtn; 141 struct ifreq ifr; 142 143 if (!if_name || !mac) 144 { 145 return -1; 146 } 147 fd = socket(AF_INET, SOCK_DGRAM, 0); 148 if (fd < 0) 149 { 150 perror("socket"); 151 return -1; 152 } 153 ifr.ifr_addr.sa_family = AF_INET; 154 strncpy(ifr.ifr_name, (const char *)if_name, IFNAMSIZ - 1); 155 156 if ((rtn = ioctl(fd, SIOCGIFHWADDR, &ifr)) == 0) 157 memcpy(mac, (unsigned char *)ifr.ifr_hwaddr.sa_data, 6); 158 close(fd); 159 return rtn; 160 } 161 162 int get_ifidx(const char *if_name, int *idx) 163 { 164 int fd, rtn; 165 struct ifreq ifr; 166 167 if (!if_name || !idx) 168 { 169 return -1; 170 } 171 fd = socket(AF_INET, SOCK_DGRAM, 0); 172 if (fd < 0) 173 { 174 perror("socket"); 175 return -1; 176 } 177 ifr.ifr_addr.sa_family = AF_INET; 178 strncpy(ifr.ifr_name, (const char *)if_name, IFNAMSIZ - 1); 179 180 if ((rtn = ioctl(fd, SIOCGIFINDEX, &ifr)) == 0) 181 *idx = ifr.ifr_ifindex; 182 close(fd); 183 return rtn; 184 }
程式碼並不複雜,大家注意看71-90行,構造了ARP Probe資料封包。這個封包就是向區域網廣播,聞訊特定IP是否被使用。
資料封包傳送之後,我們就等待其他主機的響應。如果被使用了,則會命中130行程式碼。
這個程式碼不僅可以在手動設定IP之前探測該IP是否被使用,也能在主機正常工作途中實時探測有沒有其他裝置設定了與我們衝突的IP。
linux下使用原始通訊端需要root許可權哦。
最後修改時間 2022-09-28 19:27:37