linux socket使用ARP判断局域网指定IP是否被占用

2019-07-13 06:12发布

项目中需要判断局域网内某个IP是否被占用,一开始想到的是ping命令,但是ping只能判断同一网段的IP。后来发现linux使用arping命令可以判断,如使用arping -D -f -w 1 x.x.x.x 但是对于经过裁剪的嵌入式linux,busybox中不一定还保留arping命令,而且C代码中调用shell命令需要临时创建一个子进程来执行,频繁操作会浪费资源。于是决定参考busybox源码中的arping.c自己实现C代码socket的ARP,以下代码是从busybox的arping.c中提取的。

需要能区分出以下6种情况:

  • 同网段本IP且IP不冲突
  • 同网段本IP且IP冲突
  • 同网段其他IP且IP存在
  • 同网段其他IP且IP不存在
  • 跨网段IP存在
  • 跨网段IP不存在

如何使用

调用函数arp_get_mac即可,参数if_name 是网卡名称,嵌入式中经常是eth0,str_src_ip是本设备的IP,str_dst_ip是需要判断的目标IP,dst_mac返回目标IP对应设备的MAC,timeout_ms指定超时时间,单位是ms。 返回值中,对于str_src_ip与str_dst_ip不相同的情况下,该IP正被占用返回1,未被占用返回0.对于str_src_ip与str_dst_ip相同的情况,该IP只有本设备在使用则返回2,本设备的IP与别的设备IP冲突,且该IP正被别的设备占用则返回1。 /** 返回值: -1 错误 0 不存在 1 存在 2 请求IP是本设备IP,且IP可用 */ int arp_get_mac(char *if_name,char *str_src_ip,char *str_dst_ip,unsigned char *dst_mac,int timeout_ms)

流程解释

先按需求设置好socket,封装好struct sockaddr以及需要发送的数据,以UDP广播的方式发送出去,然后接收广播包,在接收到的数据包中分析提取需要的信息,如果能提取到则说明该IP正在被占用。

实例代码:

/** 根据指定IP地址获取MAC地址,可用于判断IP是否被占用 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "arp_get_mac.h" #define MS_TDIFF(tv1,tv2) ( ((tv1).tv_sec-(tv2).tv_sec)*1000 + ((tv1).tv_usec-(tv2).tv_usec)/1000 ) int send_pack(int fd, struct in_addr src, struct in_addr dst, struct sockaddr_ll *ME, struct sockaddr_ll *HE) { int advert=0; int err; struct timeval now; unsigned char buf[256]; struct arphdr *ah = (struct arphdr*)buf; unsigned char *p = (unsigned char *)(ah+1); ah->ar_hrd = htons(ME->sll_hatype); /* 硬件地址类型*/ if (ah->ar_hrd == htons(ARPHRD_FDDI)) ah->ar_hrd = htons(ARPHRD_ETHER); ah->ar_pro = htons(ETH_P_IP); /* 协议地址类型 */ ah->ar_hln = ME->sll_halen; /* 硬件地址长度 */ ah->ar_pln = 4; /* 协议地址长度 */ ah->ar_op = advert ? htons(ARPOP_REPLY) : htons(ARPOP_REQUEST);/* 操作类型*/ memcpy(p, &ME->sll_addr, ah->ar_hln); /* 发送者硬件地址*/ p+=ME->sll_halen; /*以太网为6*/ memcpy(p, &src, 4); /* 发送者IP */ p+=4; /* 目的硬件地址*/ if (advert) memcpy(p, &ME->sll_addr, ah->ar_hln); else memcpy(p, &HE->sll_addr, ah->ar_hln); p+=ah->ar_hln; memcpy(p, &dst, 4); /* 目的IP地址*/ p+=4; gettimeofday(&now, NULL); err = sendto(fd, buf, p-buf, 0, (struct sockaddr*)HE, sizeof(*HE)); return err; } int is_time_out(struct timeval *start,int timeout_ms) { struct timeval tv; gettimeofday(&tv, NULL); if ((start->tv_sec==0)&&(start->tv_usec==0)){ *start = tv; } if (timeout_ms && MS_TDIFF(tv,*start) > timeout_ms) return 1; else return 0; } /*数据包分析主程序. 把ARP 请求和答复的数据包格式 |---------------28 bytes arp request/reply-----------------------------| |--------ethernet header----| _____________________________________________________________________________________________________ |ethernet | ethernet| frame|hardware|protocol|hardware|protocol|op|sender |sender|target |target| |dest addr|src addr | type| type |type | length |length | |eth addr| IP |eth addr| IP | ----------------------------------------------------------------------------------------------------- 6 types 6 2 2 2 1 1 2 6 4 6 4 */ int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM, struct sockaddr_ll *me, struct in_addr *src, struct in_addr *dst, unsigned char *dst_mac, int *recv_count) { struct timeval tv; struct arphdr *ah = (struct arphdr*)buf; unsigned char *p = (unsigned char *)(ah+1); struct in_addr src_ip, dst_ip; gettimeofday(&tv, NULL); /* Filter out wild packets */ if (FROM->sll_pkttype != PACKET_HOST && FROM->sll_pkttype != PACKET_BROADCAST && FROM->sll_pkttype != PACKET_MULTICAST){ return 0; } /*到这里pkttype为HOST||BROADCAST||MULTICAST*/ /* Only these types are recognised */ /*只要ARP request and reply*/ if (ah->ar_op != htons(ARPOP_REQUEST) && ah->ar_op != htons(ARPOP_REPLY)){ return 0; } /* ARPHRD check and this darned FDDI hack here :-( */ if (ah->ar_hrd != htons(FROM->sll_hatype) && (FROM->sll_hatype != ARPHRD_FDDI || ah->ar_hrd != htons(ARPHRD_ETHER))){ return 0; } /* Protocol must be IP. */ if (ah->ar_pro != htons(ETH_P_IP)){ return 0; } if (ah->ar_pln != 4){ return 0; } if (ah->ar_hln != me->sll_halen){ return 0; } if (len < sizeof(*ah) + 2*(4 + ah->ar_hln)){ return 0; } /*src_ip:对方的IP det_ip:我的IP*/ memcpy(&src_ip, p+ah->ar_hln, 4); memcpy(&dst_ip, p+ah->ar_hln+4+ah->ar_hln, 4); (*recv_count)++; __ERR("[%s:%d] res_src=%s, ",__FUNCTION__,__LINE__,inet_ntoa(src_ip)); __ERR("res_dst=%s ",inet_ntoa(dst_ip)); /* DAD packet was: src_ip = 0 (or some src) src_hw = ME dst_ip = tested address dst_hw = ; We fail, if receive request/reply with: src_ip = tested_address src_hw != ME if src_ip in request was not zero, check also that it matches to dst_ip, otherwise dst_ip/dst_hw do not matter. */ /*dst.s_addr是我们发送请求是置的对方的IP,当然要等于对方发来的包的src_ip啦*/ if (src_ip.s_addr != dst->s_addr){ __ERR("[%s:%d] res_src=%s, ",__FUNCTION__,__LINE__,inet_ntoa(src_ip)); __ERR("req_dst=%s ",inet_ntoa(*dst)); return 0; } if (memcmp(p, &me->sll_addr, me->sll_halen) == 0){ return 0; } /*同理,src.s_addr是我们发包是置的自己的IP,要等于对方回复包的目的地址*/ if (src->s_addr && src->s_addr != dst_ip.s_addr){ __ERR("[%s:%d] req_src=%s, ",__FUNCTION__,__LINE__,inet_ntoa(*src)); __ERR("res_dst=%s ",inet_ntoa(dst_ip)); return 0; } int i=0; for(i=0;(iar_hln);i++){ dst_mac[i] = p[i]; } return 1; } /** 返回值 : -1 错误 0 不存在 1 存在 2 请求IP是本设备IP,且IP可用 */ int arp_get_mac(char *if_name,char *str_src_ip,char *str_dst_ip,unsigned char *dst_mac,int timeout_ms) { __ERR("arp_get_mac: if_name(%s) src_ip(%s) dst_ip(%s) timeout_ms(%d) ",if_name,str_src_ip,str_dst_ip,timeout_ms); if((if_name==NULL)||(str_src_ip==NULL)||(str_dst_ip==NULL)||(dst_mac==NULL)||(timeout_ms==0)){ return -1; } int i=0; for(i=0;ih_addr, 4); } me.sll_family = AF_PACKET; me.sll_ifindex = ifindex; me.sll_protocol = htons(ETH_P_ARP); /* 只想要由me指定的接口收到的数据包*/ if (bind(fd, (struct sockaddr*)&me, sizeof(me)) == -1) { perror("bind"); close(fd); return -1; } int alen = sizeof(me); /*get link layer information 是下面这些.因为sll_family sll_ifindex sll_protocol已知 unsigned short sll_hatype; Header type unsigned char sll_pkttype; Packet type unsigned char sll_halen; Length of address unsigned char sll_addr[8]; Physical layer address */ if (getsockname(fd, (struct sockaddr*)&me, &alen) == -1) { perror("getsockname"); close(fd); return -1; } if (me.sll_halen == 0) { __ERR("Interface "%s" is not ARPable (no ll address) ", if_name); close(fd); return -1; } he = me; /*把他的地址设为ff:ff:ff:ff:ff:ff 即广播地址,当然假设是以太网*/ memset(he.sll_addr, -1, he.sll_halen); __ERR("ARPING %s ", inet_ntoa(dst)); __ERR("from %s %s ", inet_ntoa(src), if_name ? : ""); send_pack(fd, src, dst, &me, &he); struct timeval start; memset(&start,0,sizeof(struct timeval)); int recv_status = 0; while(1) { if(is_time_out(&start, timeout_ms)) break; char packet[4096]; struct sockaddr_ll from; memset(&from,0,sizeof(struct sockaddr_ll)); int alen = sizeof(from); int cc=0; /*注意s的类型是SOCK_DGRAM,所以收到的数据包里没有link layer info,这些信息被记录在from里*/ if ((cc = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr *)&from, &alen)) < 0) { perror("arping: recvfrom"); continue; } recv_status = recv_pack(packet, cc, &from, &me, &src, &dst, dst_mac, &recv_count); if(is_same_ip && (!recv_status)){ if(recv_count>0){ recv_status=2; break; } } if(!recv_status) continue; break; } if(is_same_ip && (recv_status!=1)){ recv_status=2; } if(recv_status>0){ __ERR("recv_status=%d,dst_mac=[%02X:%02X:%02X:%02X:%02X:%02X] ",recv_status,dst_mac[0],dst_mac[1],dst_mac[2],dst_mac[3],dst_mac[4],dst_mac[5]); } return recv_status; } #if 0 int main(int argc, char **argv) { if(argc!=2) return 0; char *if_name = "eth0"; char *str_src_ip = "192.168.4.158"; char *str_dst_ip = argv[1]; unsigned char dst_mac[ARP_MAC_BYTE]={0}; int ret = arp_get_mac(if_name,str_src_ip,str_dst_ip,dst_mac, 1000); printf("ret=%d,dst_mac=[%02X:%02X:%02X:%02X:%02X:%02X] ",ret,dst_mac[0],dst_mac[1],dst_mac[2],dst_mac[3],dst_mac[4],dst_mac[5]); return 0; } #endif