原理其实很简单,那就是广播一个arp包,然后recv,如果没有数据(这里要设置延时),那么说明这个ip是可用的,否则就检测这个数据是否为回复我们发出的arp的应答包.如果是则证明ip已被使用,否则继续等待.
这里可以看下busybox的dhcp中的检测程序。
networking/udhcp/arpping.c
C代码
- /*vi:setsw=4ts=4:*/
- /*
- *arpping.c
- *
- *Mostlystolenfrom:dhcpcd-DHCPclientdaemon
- *byYoichiHariguchi<yoichi@fore.com>
- */
- #include<netinet/if_ether.h>
- #include<net/if_arp.h>
- #include"common.h"
- #include"dhcpd.h"
- //这里是arp包的格式,其中的数据格式都是宏了,比如uint_8_t为无符char.
- structarpMsg{
- /*Ethernetheader*/
- uint8_th_dest[6];/*00destinationetheraddr*/
- uint8_th_source[6];/*06sourceetheraddr*/
- uint16_th_proto;/*0cpackettypeIDfield*/
- /*ARPpacket*/
- uint16_thtype;/*0ehardwaretype(mustbeARPHRD_ETHER)*/
- uint16_tptype;/*10protocoltype(mustbeETH_P_IP)*/
- uint8_thlen;/*12hardwareaddresslength(mustbe6)*/
- uint8_tplen;/*13protocoladdresslength(mustbe4)*/
- uint16_toperation;/*14ARPopcode*/
- uint8_tsHaddr[6];/*16sender'shardwareaddress*/
- uint8_tsInaddr[4];/*1csender'sIPaddress*/
- uint8_ttHaddr[6];/*20target'shardwareaddress*/
- uint8_ttInaddr[4];/*26target'sIPaddress*/
- uint8_tpad[18];/*2apadformin.ethernetpayload(60bytes)*/
- }PACKED;
- enum{
- ARP_MSG_SIZE=0x2a
- };
- /*Returns1ifnoreplyreceived*/
- //主程序,如果返回1说明此ip可用
- intarpping(uint32_ttest_ip,uint32_tfrom_ip,uint8_t*from_mac,constchar*interface)
- {
- inttimeout_ms;
- //这里使用poll来检测句柄。
- structpollfdpfd[1];
- #defines(pfd[0].fd)/*socket*/
- intrv=1;/*"noreplyreceived"yet*/
- structsockaddraddr;/*forinterfacename*/
- structarpMsgarp;
- //建立scoket.由于我们是要直接访问访问链路层并自己组arp包.因此我们使用PF_PACKET协议簇.socket类型为SOCK_PACKET.
- s=socket(PF_PACKET,SOCK_PACKET,htons(ETH_P_ARP));
- if(s==-1){
- bb_perror_msg(bb_msg_can_not_create_raw_socket);
- return-1;
- }
- if(setsockopt_broadcast(s)==-1){
- bb_perror_msg("cannotenablebcastonrawsocket");
- gotoret;
- }
- //进行组包,由于是要广播,因此目的mac地址为全0.
- /*sendarprequest*/
- memset(&arp,0,sizeof(arp));
- memset(arp.h_dest,0xff,6);/*MACDA*/
- memcpy(arp.h_source,from_mac,6);/*MACSA*/
- arp.h_proto=htons(ETH_P_ARP);/*protocoltype(Ethernet)*/
- arp.htype=htons(ARPHRD_ETHER);/*hardwaretype*/
- arp.ptype=htons(ETH_P_IP);/*protocoltype(ARPmessage)*/
- arp.hlen=6;/*hardwareaddresslength*/
- arp.plen=4;/*protocoladdresslength*/
- arp.operation=htons(ARPOP_REQUEST);/*ARPopcode*/
- memcpy(arp.sHaddr,from_mac,6);/*sourcehardwareaddress*/
- memcpy(arp.sInaddr,&from_ip,sizeof(from_ip));/*sourceIPaddress*/
- /*tHaddriszero-fiiled*//*targethardwareaddress*/
- memcpy(arp.tInaddr,&test_ip,sizeof(test_ip));/*targetIPaddress*/
- memset(&addr,0,sizeof(addr));
- safe_strncpy(addr.sa_data,interface,sizeof(addr.sa_data));
- //广播arp包.
- if(sendto(s,&arp,sizeof(arp),0,&addr,sizeof(addr))<0){
- //TODO:errormessage?callerdidn'texpectustofail,
- //justreturning1"noreplyreceived"misleadsit.
- gotoret;
- }
- /*waitforarpreply,andcheckit*/
- //等待时间,超时则认为此ip地址可用
- timeout_ms=2000;
- do{
- intr;
- unsignedprevTime=monotonic_us();
- pfd[0].events=POLLIN;
- //这边他是害怕poll被信号打断,因此加了层循环,其实这边我们还可以使用ppoll的,就可以了。
- r=safe_poll(pfd,1,timeout_ms);
- if(r<0)
- break;
- if(r){
- //读取返回数据.
- r=read(s,&arp,sizeof(arp));
- if(r<0)
- break;
- //检测是否为应打包,发送ip是否为我们所请求的ip,这里是为了防止其他的数据包干扰我们检测。
- if(r>=ARP_MSG_SIZE
- &&arp.operation==htons(ARPOP_REPLY)
- /*don'tcheckit:Linuxdoesn'treturnpropertHaddr(fixedin2.6.24?)*/
- /*&&memcmp(arp.tHaddr,from_mac,6)==0*/
- &&*((uint32_t*)arp.sInaddr)==test_ip
- ){
- //说明ip地址已被使用
- rv=0;
- break;
- }
- }
- timeout_ms-=((unsigned)monotonic_us()-prevTime)/1000;
- }while(timeout_ms>0);
- ret:
- close(s);
- DEBUG("%srpreplyreceivedforthisaddress",rv?"Noa":"A");
- returnrv;
- }