提供一个接口,根据目的ip地址,获取相应的mac地址。
首先,根据目的ip,通过路由表找到出口设备;然后,通过socket发送icmp echo request报文,因为目的ip对应的mac地址还没有,所以linux tcp/ip协议栈会发送arp request报文,这样linux协议栈收到目的ip主机发送的arp reply报文后,就会学到目的ip对应得mac地址,从而添加一条arp信息到arp table里;最后,利用目的ip和出口设备的ifname找到目的ip的mac地址。
参考iproute2源码中ip route get命令实现根据目的ip查找dev name【netlink RTM_GETROUTE】
利用socket发送imcp echo request报文来触发协议栈发送arp request 报文,从而是linux邻居子系统获取目的ip对应得arp表项
利用ioctl SIOCGARP查找对应得arp表项
就是利用netlink 送内核发送路由请求消息,接收内核发送的路由信息,解析该路由信息,得到所需的信息。
1.构造netlink route request消息,消息结构体定义及格式如下:
2.该结构体是iproute源码定义的ip route 命令发送route cmd的netlink消息
3.创建netlink socket,发送netlink route request消息,接收内核发送的reponse消息
4.解析内核发出的reponse消息,找到目的ip的出口设备的ifname
利用socket发送imcp echo request报文,来间接触发协议栈发送arp request 报文【在没有arp的情况下】,从而是linux邻居子系统获取目的ip对应得arp表项
利用socket发送imcp echo request报文来间接触发协议栈发送arp request 报文,而不是直接构造arp request报文的方法的原因,是基于linux的安全考虑的【在默认情况下,如果一台主机收到一个ARPOP_REPLY,但它没有对应得未决ARPOP_REQUEST,那么它就会将该应答丢弃】,所以采用这种间接的方法。
利用ioctl及 SIOCGARP查找arp的方法比较固定,这里就不详细讲解了。
源码如下:
/*根据目的ip查找出口设备*/
struct rtnl_handle
{
int fd;
struct sockaddr_nl local;
struct sockaddr_nl peer;
unsigned int seq;
};
/*
function:
@get interface name by index
parameter:
@ifindex: interface index
@ifname : for storage interface name
return:
@OK : get interface name by index successd
@ERROR : failed
*/
int os_index_to_name(int ifindex, char *ifname)
{
int sockfd;
struct ifconf ifc;
char buf[4000] = {0x0};
int i = 0;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
printf("in %s in %d socket error",__FUNCTION__,__LINE__);
return ERROR;
}
/* Init struct ifconf. */
ifc.ifc_len = 4000;
ifc.ifc_buf = (caddr_t)buf;
/* Get all interface info. */
if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0)
{
printf("in %s in %d ioctl execute failed\n",__FUNCTION__,__LINE__);
close(sockfd);
return ERROR;
}
/* Match ifindex for get ifname. */
struct ifreq *ifr;
struct ifreq ifrcopy;
ifr = (struct ifreq*)buf;
for(i = (ifc.ifc_len/sizeof(struct ifreq)); i>0; i--)
{
ifrcopy = *ifr;
if(ioctl(sockfd, SIOCGIFINDEX, &ifrcopy) < 0)
{
printf("in %s in %d ioctl execute failed\n",__FUNCTION__,__LINE__);
close(sockfd);
return ERROR;
}
if(ifrcopy.ifr_ifru.ifru_ivalue == ifindex)
{
strcpy(ifname, ifr->ifr_ifrn.ifrn_name);
printf("ifindex[%d] to ifname[%s]\n",ifindex,ifname);
close(sockfd);
return OK;
}
ifr++;
}
close(sockfd);
return ERROR;
}
int os_index_to_name_test(int ifindex)
{
int sockfd;
struct ifconf ifc;
char buf[1024] = {0x0};
char ifname[100] = {0x0};
int i = 0;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
printf("in %s in %d socket error",__FUNCTION__,__LINE__);
return ERROR;
}
//初始化ifconf结构
ifc.ifc_len = 1024;
ifc.ifc_buf = (caddr_t)buf;
//获取所有接口信息
if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0)
{
printf("in %s in %d ioctl execute failed\n",__FUNCTION__,__LINE__);
close(sockfd);
return ERROR;
}
//遍历每一个ifreq结构
struct ifreq *ifr;
struct ifreq ifrcopy;
ifr = (struct ifreq*)buf;
for(i = (ifc.ifc_len/sizeof(struct ifreq)); i>0; i--)
{
printf("ifname[%s]\n",ifr->ifr_ifrn.ifrn_name);
//get ifindex
ifrcopy = *ifr;
if(ioctl(sockfd, SIOCGIFINDEX, &ifrcopy) < 0)
{
printf("in %s in %d ioctl execute failed\n",__FUNCTION__,__LINE__);
close(sockfd);
return ERROR;
}
if(ifrcopy.ifr_ifru.ifru_ivalue == ifindex)
{
strcpy(ifname, ifr->ifr_ifrn.ifrn_name);
printf("ifindex[%d] to ifname[%s]\n",ifindex,ifname);
close(sockfd);
return OK;
}
ifr++;
}
close(sockfd);
return ERROR;
}
/*
function:
@Create netlink socket for route
parameter:
@rth: netlink socket handle for route
*/
int nl_create(struct rtnl_handle *rth)
{
int sndbuf = 32768;
int rcvbuf = 32768;
int addr_len = 0;
rth->fd = socket ( AF_NETLINK, SOCK_RAW, NETLINK_ROUTE );
if ( rth->fd < 0 )
{
printf ( "Can't create socket: %s", strerror ( errno ) );
return ERROR;
}
if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0)
{
printf("SO_SNDBUF");
close(rth->fd);
return ERROR;
}
if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0)
{
printf("SO_RCVBUF");
close(rth->fd);
return ERROR;
}
memset(&rth->local, 0, sizeof(struct sockaddr_nl));
rth->local.nl_family = AF_NETLINK;
rth->local.nl_groups = 0;
if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(struct sockaddr_nl)) < 0)
{
printf("Cannot bind netlink socket");
close(rth->fd);
return ERROR;
}
addr_len = sizeof(struct sockaddr_nl);
if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0)
{
printf("Cannot getsockname");
close(rth->fd);
return ERROR;
}
if (addr_len != sizeof(struct sockaddr_nl))
{
printf("Wrong address length %d\n", addr_len);
close(rth->fd);
return ERROR;
}
if (rth->local.nl_family != AF_NETLINK)
{
printf("Wrong address family %d\n", rth->local.nl_family);
close(rth->fd);
return ERROR;
}
rth->seq = 1;
return OK;
}
/*
function:
@send netlink request msg,link cmd "ip route get A.B.C.D"
parameter:
@rth: netlink socket handle for route
@n : netlink msg header
*/
int nl_send(struct rtnl_handle *rth, struct nlmsghdr *n)
{
int status = -1;
int seq;
struct nlmsghdr *h;
struct sockaddr_nl dst_nl;
struct iovec iov = {(void*)n, n->nlmsg_len};
char recv_buf[16384];
struct msghdr msg ={
(void*)&dst_nl, sizeof(dst_nl),
&iov, 1,
NULL, 0,
0
};
/* destination:send to linux kernel. */
memset(&dst_nl, 0x0, sizeof(dst_nl));
dst_nl.nl_family = AF_NETLINK;
dst_nl.nl_pid = 0;
dst_nl.nl_groups = 0;
n->nlmsg_seq = seq = 2;
status = sendmsg(rth->fd, &msg, 0);
if(status < 0)
{
printf("send netlink msg failed\n");
return ERROR;
}
memset(recv_buf, 0x0, sizeof(recv_buf));
iov.iov_base = recv_buf;
while(1)
{
iov.iov_len = sizeof(recv_buf);
status = recvmsg(rth->fd, &msg, 0);
if (status < 0)
{
if (errno == EINTR)
continue;
printf("OVERRUN");
continue;
}
if (status == 0)
{
printf("EOF on netlink\n");
return ERROR;
}
if (msg.msg_namelen != sizeof(dst_nl))
{
printf("sender address length == %d\n", msg.msg_namelen);
return ERROR;
}
for (h = (struct nlmsghdr*)recv_buf; status >= sizeof(*h); )
{
int err;
int len = h->nlmsg_len;
int l = len - sizeof(*h);
if (l<0 || len>status)
{
if (msg.msg_flags & MSG_TRUNC)
{
printf("Truncated message\n");
return ERROR;
}
printf("!!!malformed message: len=%d\n", len);
return ERROR;
}
if (dst_nl.nl_pid != 0 ||
h->nlmsg_pid != rth->local.nl_pid ||
h->nlmsg_seq != seq)
{
continue;
}
if (h->nlmsg_type == NLMSG_ERROR)
{
struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h);
if (l < sizeof(struct nlmsgerr))
{
printf("ERROR truncated\n");
}
else
{
errno = -err->error;
if (errno == 0) {
if (n)
memcpy(n, h, h->nlmsg_len);
return ERROR;
}
printf("RTNETLINK answers");
}
return ERROR;
}
if (n)
{
memcpy(n, h, h->nlmsg_len);
return OK;
}
printf("Unexpected reply!!!\n");
status -= NLMSG_ALIGN(len);
h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len));
}
}
}
/*
function:
@copy to iproute soucre code,like this cmd "ip route get A.B.C.D"
parameter:
@dstip: input value,destination ip
@dev : return value,interface name
*/
int iproute_get_by_dstip(char *dstip, char *dev)
{
struct {
struct nlmsghdr n;
struct rtmsg r;
char buf[1024];
} req;
struct rtattr *rta;
struct in_addr tarip;
struct rtnl_handle rth;
int ret = -1;
if(!dstip || !dev)
{
return ERROR;
}
if(inet_pton(AF_INET, dstip, (void*)&tarip)!= 1)
{
return ERROR;
}
memset(&req, 0, sizeof(req));
/* Init netlink msg=sizeof(struct nlmsghdr)+sizeof(struct rtmsg). */
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
req.n.nlmsg_flags = NLM_F_REQUEST;
req.n.nlmsg_type = RTM_GETROUTE;
req.r.rtm_family = AF_UNSPEC;
req.r.rtm_table = 0;
req.r.rtm_protocol = 0;
req.r.rtm_scope = 0;
req.r.rtm_type = 0;
req.r.rtm_src_len = 0;
req.r.rtm_dst_len = 0;
req.r.rtm_tos = 0;
/* Encapsulate netlink message body. */
req.r.rtm_family = AF_INET;
/* Equal to rta = (struct rtattr*)req.buf */
rta = (struct rtattr*)(((void *) (&req.n)) + NLMSG_ALIGN(req.n.nlmsg_len));
rta->rta_type = RTA_DST;
rta->rta_len = sizeof(struct rtattr) + 4;
memcpy(RTA_DATA(rta), (void*)&tarip.s_addr, 4);
req.n.nlmsg_len = NLMSG_ALIGN (req.n.nlmsg_len) + sizeof(struct rtattr) + 4;
req.r.rtm_dst_len = 32;
/* Create netlink socket for route handle. */
ret = nl_create(&rth);
if(ret != OK)
{
return ERROR;
}
/* Send route request netlink msg to kernel. */
ret = nl_send(&rth, &req.n);
/* Parse route info from kernel*/
if(ret == OK)
{
struct rtattr *tb[RTA_MAX+1];
struct rtmsg *r = NLMSG_DATA(&req.n);
int host_len = -1;
int oif = -1;
int len = req.n.nlmsg_len;
len -= NLMSG_LENGTH(sizeof(*r));
if (len < 0) {
printf("BUG: wrong nlmsg len %d\n", len);
close(rth.fd);
return ERROR;
}
if(req.r.rtm_family == AF_INET)
{
host_len = 32;
}
else
{
close(rth.fd);
return ERROR;
}
/* Copy reponse data to tb[]. */
parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
if (tb[RTA_OIF])
{
oif = *(int*)RTA_DATA(tb[RTA_OIF]);
printf("out interface index %d\n",oif);
ret = os_index_to_name(oif, dev);
if(ret == OK)
{
printf("%s dev %s\n",dstip,dev);
}
close(rth.fd);
return ret;
}
close(rth.fd);
return ERROR;
}
close(rth.fd);
return ERROR;
}
int iproute_get_by_dstip_test(char *dstip)
{
struct {
struct nlmsghdr n;
struct rtmsg r;
char buf[1024];
} req;
struct rtattr *rta;
struct in_addr tarip;
struct rtnl_handle rth;
char ifname[100] = {0x0};
int ret = -1;
if(!dstip)
{
return ERROR;
}
if(inet_pton(AF_INET, dstip, (void*)&tarip)!= 1)
{
return ERROR;
}
memset(&req, 0, sizeof(req));
/* 初始化netlink msg=sizeof(struct nlmsghdr)+sizeof(struct rtmsg). */
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
req.n.nlmsg_flags = NLM_F_REQUEST;
req.n.nlmsg_type = RTM_GETROUTE;
req.r.rtm_family = AF_UNSPEC;
req.r.rtm_table = 0;
req.r.rtm_protocol = 0;
req.r.rtm_scope = 0;
req.r.rtm_type = 0;
req.r.rtm_src_len = 0;
req.r.rtm_dst_len = 0;
req.r.rtm_tos = 0;
/* 封装netlink消息体*/
req.r.rtm_family = AF_INET;
/* Equal to rta = (struct rtattr*)req.buf */
rta = (struct rtattr*)(((void *) (&req.n)) + NLMSG_ALIGN(req.n.nlmsg_len));
rta->rta_type = RTA_DST;
rta->rta_len = sizeof(struct rtattr) + 4;
memcpy(RTA_DATA(rta), (void*)&tarip.s_addr, 4);
req.n.nlmsg_len = NLMSG_ALIGN (req.n.nlmsg_len) + sizeof(struct rtattr) + 4;
req.r.rtm_dst_len = 32;
/*Create netlink socket*/
ret = nl_create(&rth);
if(ret != OK)
{
return ERROR;
}
/* Send netlink msg. */
ret = nl_send(&rth, &req.n);
/*parse route info*/
if(ret == OK)
{
struct rtattr *tb[RTA_MAX+1];
struct rtmsg *r = NLMSG_DATA(&req.n);
int host_len = -1;
int oif = -1;
int len = req.n.nlmsg_len;
len -= NLMSG_LENGTH(sizeof(*r));
if (len < 0) {
printf("BUG: wrong nlmsg len %d\n", len);
close(rth.fd);
return ERROR;
}
if(req.r.rtm_family == AF_INET)
{
host_len = 32;
}
else
{
close(rth.fd);
return ERROR;
}
/*copy reponse data to tb[]*/
parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
if (tb[RTA_OIF])
{
oif = *(int*)RTA_DATA(tb[RTA_OIF]);
printf("out interface index %d\n",oif);
ret = os_index_to_name(oif, ifname);
if(ret == OK)
printf("ifindex[%d] ifname[%s]",oif, ifname);
close(rth.fd);
return ret;
}
close(rth.fd);
return ERROR;
}
close(rth.fd);
return ERROR;
}
int get_if_ipaddr(const char *ifname, struct in_addr *ipaddr)
{
struct ifreq ifr;
struct sockaddr_in *sa=NULL;
int sockfd = -1;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
printf("create socket failed in %s in %d line",__FUNCTION__,__LINE__);
return ERROR;
}
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)-1);
if(ioctl(sockfd, SIOCGIFADDR, &ifr) < 0)
{
printf("get %s ipaddr failed:%s\n",ifname, strerror(errno));
close(sockfd);
return ERROR;
}
close(sockfd);
sa = (struct sockaddr_in *)&ifr.ifr_addr;
*ipaddr = sa->sin_addr;
return OK;
}
unsigned short in_cksum(unsigned short * addr,int len) //校验和函数
{
register int nleft = len;
register unsigned short *w = addr;
register int sum = 0;
unsigned short answer = 0;
/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
/* mop up an odd byte, if necessary */
if (nleft == 1) {
*(unsigned char *)(&answer) = *(unsigned char *)w ;
sum += answer;
}
/* add back carry outs from top 16 bits to low 16 bits */
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = (unsigned short)(~sum); /* truncate to 16 bits */
return(answer);
}
利用icmp socket 间接触发协议栈发送arp request报文,从而获取相应的arp表项
#define ECN_ICMP_MINLEN 8
#define ECN_ICMP_ECHOREPLY 0 // echo reply
#define ECN_ICMP_ECHO 8 // echo service
#define ECN_ICMP_PID 25515 // pid
#define ECN_ICMP_SEQ 12345 // 系列号
typedef struct
{
unsigned char ecn_icmp_type;
unsigned char ecn_icmp_code;
unsigned short ecn_icmp_check;
unsigned short ecn_icmp_id;
unsigned short ecn_icmp_seq;
}S_SYS_ICMP;
/*
function:
@通过icmp echo request触发协议栈发送arp request报文, 从而得到arp entry
param:
@targetip target ip address pointer,it's value must like("A.B.C.D")
@numTries send icmp echo request times
@numTicks send icmp echo request time interval
return:
@ERROR failed
@OK success
*/
static int send_icmp_echo_request(const char *targetip, int numTries, int numTicks)
{
int ret = ERROR;
struct sockaddr_in dest;
int rawsock = 0;
unsigned int tar_ip = 0;
char send_buf[72];
int size = 0;
int i = 0;
int j = 0;
if(!targetip)
{
return ERROR;
}
/*生成使用icmp的原始套接字,这种套接字只有root才能生成*/
rawsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if(rawsock < 0)
{
perror("socket");
return ERROR;
}
tar_ip = inet_addr(targetip);
if(tar_ip == INADDR_NONE)
{
printf("targetip must like this A.B.C.D,but targetip is %s\n",targetip);
close(rawsock);
return ERROR;
}
memcpy((char*)&dest.sin_addr, &tar_ip, sizeof(tar_ip));
if(g_syslib_dbg)
{
/*打印提示*/
tar_ip = dest.sin_addr.s_addr;
printf("PING %s (%ld.%ld.%ld.%ld) 56(84) bytes of data.\n",
targetip,
(tar_ip&0xFF000000)>>24,
(tar_ip&0x00FF0000)>>16,
(tar_ip&0x0000FF00)>>8,
(tar_ip&0x000000FF)>>0
);
}
/*创建icmp报文*/
S_SYS_ICMP* icm=(S_SYS_ICMP*)send_buf;
icm->ecn_icmp_type=ECN_ICMP_ECHO;
icm->ecn_icmp_code=0;
icm->ecn_icmp_check=0;
icm->ecn_icmp_id= ECN_ICMP_PID;
icm->ecn_icmp_seq=ECN_ICMP_SEQ;
icm->ecn_icmp_check=in_cksum((unsigned short *)icm,ECN_ICMP_MINLEN);
for(i = 0; i < numTries; i++)
{
/*Send icmp echo request to targetip*/
size = sendto (rawsock, send_buf, 64, 0,
(struct sockaddr *)&dest, sizeof(dest) );
if(size < 0)
{
printf("Ping %s failed!\n",targetip);
}
else
{
printf("Ping %s success!\n",targetip);
ret = OK;
}
while(j < numTicks * 1000)
{
j++;
}
}
close(rawsock);
return ret;
}
3.利用ioctl查找arp表项
int get_arp_entry(const char *dev, const char *ip ,char *pHwAddr)
{
int sfd, saved_errno, ret;
unsigned char *mac;
struct arpreq arp_req;
struct sockaddr_in *sin;
sin = (struct sockaddr_in *)&(arp_req.arp_pa);
memset(&arp_req, 0, sizeof(arp_req));
sin->sin_family = AF_INET;
inet_pton(AF_INET, ip, &(sin->sin_addr));
strncpy(arp_req.arp_dev, dev, IFNAMSIZ-1);
sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
return ERROR;
saved_errno = errno;
ret = ioctl(sfd, SIOCGARP, &arp_req);
if (ret < 0) {
printf("Get ARP entry failed : %s\n", strerror(errno));
close(sfd);
return(EXIT_FAILURE);
}
errno = saved_errno;
if (arp_req.arp_flags & ATF_COM) {
mac = (unsigned char *)arp_req.arp_ha.sa_data;
memcpy(pHwAddr,mac,6);
printf("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
close(sfd);
return 1;
} else {
printf("MAC: Not in the ARP cache.\n");
close(sfd);
return 0;
}
}
参考内容: