项目中需要判断局域网内某个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