linux 实现主动arp resolve 功能

  • 实现目的:

提供一个接口,根据目的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地址。

  • 实现步骤:
  1. 参考iproute2源码中ip route get命令实现根据目的ip查找dev name【netlink  RTM_GETROUTE】

  2. 利用socket发送imcp echo request报文来触发协议栈发送arp request 报文,从而是linux邻居子系统获取目的ip对应得arp表项

  3. 利用ioctl SIOCGARP查找对应得arp表项

  • 实现根据目的ip查找出口设备方法

    就是利用netlink 送内核发送路由请求消息,接收内核发送的路由信息,解析该路由信息,得到所需的信息。

    1.构造netlink route request消息,消息结构体定义及格式如下:

    

    2.该结构体是iproute源码定义的ip route 命令发送route cmd的netlink消息

    3.创建netlink socket,发送netlink route request消息,接收内核发送的reponse消息

    4.解析内核发出的reponse消息,找到目的ip的出口设备的ifname

  • 实现间接触发arp request报文的方法

利用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表项

利用ioctl及 SIOCGARP查找arp的方法比较固定,这里就不详细讲解了。

源码如下:

  1. 根据目的ip查找出口设备
/*根据目的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;
    }
}

参考内容:

  1. iproute2源码
  2. linux网络编程第二版(宋敬彬)

猜你喜欢

转载自blog.csdn.net/weixin_43722423/article/details/84189799