热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux-ARP请求C程序

2017.05.23学习ARP协议好几天了。今天终于把在Linux上的ARP请求程序完成了。中间经历了好多坎坷。弄不出来搞的我十分紧张。终于在16:30分解决掉遇到的所有问题了。下面简述下我的
2017.05.23学习ARP协议好几天了。今天终于把在Linux上的ARP请求程序完成了。中间经历了好多坎坷。弄不出来搞的我十分紧张。终于在16:30分解决掉遇到的所有问题了。
下面简述下我的经历和存留的疑问。直接上代码,分析代码的问题。

**2017.06.04这下又无法淡定了。同样的程序我就加了一行
printf("mac=%02x:%02x:%02x:%02x:%02x:%02x\n", local_mac[0],local_mac[1], local_mac[2], local_mac[3],local_mac[4],local_mac[5]);
然后sendto又出现了invalid arguements的报错信息。再次删除这句printf语句,程序又可以正常执行了。
还请大神帮忙看看。小弟感激不尽。。**。

**2017.06.21补充。sendto()报错的问题终于解决了。彻底解决了。现在加上我的笔记。
arp发送和接受的socket有两种方法。不同的方法对应着不同的流程。
/* 先填充arp包头, padding使用bzero或者memset清空一下 */
1、sockfd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ARP) );
根据man 2 socket中的内容,这种方法已经不推荐使用了,但是继续使用也是可以的。
流程如下:
    struct sockaddr sa;
    bzero(&sa, sizeof(sa));
    sa.sa_family = AF_INET;         //指定地址族,要和socket()函数中使用的相同。
    strcpy(sa.sa_data, “eth0”);     //指定发送的接口。
    sendto(sockfd, &arp_pck, sizeof(arp_pck), 0,  &sa, sizeof(sa));
2、或者
    sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) );
    struct sockaddr_ll sa_ll;
    bzero(&sa_ll, sizeof(sa_ll)); //之前忘记使用bzero清空,也会导致sendto报“invalid argument”错误。
    sa_ll.sll_family = PF_PACKET,
    sa_ll.sll_ifindex = if_nametoindex(“eth0”);
    sendto(sockfd, &arp_pck, sizeof(arp_pck), 0, (struct sockaddr*)&sa_ll, sizeof(sa_ll) );
总结:sendto()报“invalid argument”带来的错误引出的思考和注意点。
1、  socket()函数的使用要注意,尤其是原始套接字。
2、  对于不同的socket()方法,使用的结构体不同。struct sockaddr和struct sockadd_ll。其数据成员的填充方式不同。
3、  上面提到的结构体,定义之后一定要进行清内存操作。否则也会导致sendto传入的参数错误。
**2017.06.21    
链接:[http://pan.baidu.com/s/1pLFMjmN](http://pan.baidu.com/s/1pLFMjmN) 密码:yplq

MAC.h获取指定网卡的IP地址和MAC地址

#ifndef _MAC_H
#define _MAC_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


extern int get_eth_MAC(char *eth_name, unsigned char *MAC);
extern int get_eth_IP(char *eth_name, unsigned char *IP);
extern int get_eth_broadaddr(char *eth_name, unsigned char *broadaddr);


#endif

MAC.c暂时能用,还有简写。(可以加入一个选项参数,选择获取ip或者MAC)

#include "MAC.h"


int get_eth_MAC(char *eth_name, unsigned char *MAC)
{
    struct ifreq ifr;
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock_fd <0)
    {
        perror("socket");
        return sock_fd;
    }
    strncpy(ifr.ifr_name,(char *)eth_name, sizeof(ifr.ifr_name) );

    int ret_ioctl = ioctl(sock_fd, SIOCGIFHWADDR, &ifr);
    if(ret_ioctl <0)
    {
        perror("ioctl");
        return ret_ioctl;
    }

    int i = 0;
        for(i = 0 ; i <14; i++)
    {
        printf("%02x\t",(unsigned char)ifr.ifr_hwaddr.sa_data[i]);
    }
    printf("\n");
    memcpy(MAC, ifr.ifr_hwaddr.sa_data, 6);
    close(sock_fd);
    return 0;
}

int get_eth_IP(char *eth_name, unsigned char *IP)
{
    struct ifreq ifr;
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock_fd <0)
    {
        perror("socket");
        return sock_fd;
    }
    strncpy(ifr.ifr_name,(char *)eth_name, sizeof(ifr.ifr_name) );

    int ret_ioctl = ioctl(sock_fd, SIOCGIFADDR, &ifr);
    if(ret_ioctl <0)
    {
        perror("ioctl");
        return ret_ioctl;
    }
    int i = 0;
    for(i = 0; i <14; i++)
    {
        printf("%d\t", (unsigned char)ifr.ifr_addr.sa_data[i]);
    }
    printf("\n");
    memcpy(IP, ifr.ifr_addr.sa_data+2, 4);
    close(sock_fd);
    return 0;
}


int get_eth_broadaddr(char *eth_name, unsigned char *broadaddr)
{

    struct ifreq ifr;
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock_fd <0)
    {
        perror("socket");
        return sock_fd;
    }
    strncpy(ifr.ifr_name,(char *)eth_name, sizeof(ifr.ifr_name) );

    int ret_ioctl = ioctl(sock_fd, SIOCGIFBRDADDR, &ifr);
    int i = 0;
    for(i = 0; i <14; i++)
    {
        printf("%d\t", (unsigned char)ifr.ifr_broadaddr.sa_data[i]);
    }
    printf("\n");
    memcpy(broadaddr, ifr.ifr_broadaddr.sa_data+2, 4);

    close(sock_fd);
    return 0;
}

main.c实现ARP请求的代码

#include "MAC.h"
#include 

#pragma pack(1)
typedef struct
{
    //DLC HEADER
    unsigned char dlc_dst_mac[6];
    unsigned char dlc_src_mac[6];
    unsigned short dlc_frame;

    //ARP PACKET
    unsigned short arp_hwtype;
    unsigned short arp_protype;
    unsigned char   arp_hwlen;
    unsigned char   arp_prolen;
    unsigned short arp_op;
    unsigned char arp_src_mac[6];
    unsigned char arp_src_ip[4];
    unsigned char arp_dst_mac[6];
    unsigned char arp_dst_ip[4];
    unsigne char arp_padding[18];
}arp_packet;


int main()
{

    printf("sizeof(arp_packet)=%d\n", sizeof(arp_packet));
    //fill ARP packet
    arp_packet arp_pck;
    unsigned char brd_mac[6] = {0xff,0xff,0xff,0xff,0xff,0xff};  //广播地址
    unsigned char dst_ip[4] = {192,168,1,110};                   //目标ip
    unsigned char local_mac[6] = {0};
    unsigned char local_ip[4] = {0};
    get_eth_MAC("eth0", local_mac);
    get_eth_IP("eth0", local_ip);

    //DLC HEADER填充
    memcpy(arp_pck.dlc_src_mac, local_mac, 6);
    memcpy(arp_pck.dlc_dst_mac, brd_mac, 6);
    arp_pck.dlc_frame = htons(ETH_P_ARP);

    //arp 包填充
    arp_pck.arp_hwtype = htons(0x0001);
    arp_pck.arp_protype = htons(ETH_P_IP);
    arp_pck.arp_hwlen  = 6;
    arp_pck.arp_prolen = 4;
    arp_pck.arp_opr = htons(0x0001);
    memcpy(arp_pck.arp_src_mac, local_mac, 6);
    memcpy(arp_pck.arp_src_ip, local_ip, 4);
    memcpy(arp_pck.arp_dst_mac, brd_mac, 6);
    memcpy(arp_pck.arp_dst_ip, dst_ip, 4);

    //struct sockaddr_ll
    struct sockaddr_ll eth_in;
    bzero(ð_in, sizeof(eth_in);
    eth_in.sll_family = PF_PACKET;
    printf("index=%d\n", eth_in.sll_ifindex = if_nametoindex("eth0") );

    //raw_socket
    int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) );
    if(sockfd <0)
        {
            perror("socket");
            return 0;
        }
    printf("sockfd=%d\n", sockfd);
    int ret = sendto(sockfd, &arp_pck, sizeof(arp_pck), 0, (struct sockaddr *)ð_in, sizeof(eth_in) );
    if(ret <0)
        {
            perror("sendto");
            printf("errno=%d\n", errno);
    }

    return 0;
}

接下来就说说为了让程序能够正确运行经历的心酸历程:
1、关于sockaddr的困惑。
通过ioctl()函数可以获取指定网卡的IP和MAC值。参见博客。Linux底层网络编程–ARP,PING等
观察struct ifreq结构体代码片段:

union
      {
        struct sockaddr ifru_addr;
        struct sockaddr ifru_dstaddr;
        struct sockaddr ifru_broadaddr;
        struct sockaddr ifru_netmask;
        struct sockaddr ifru_hwaddr;
        short int ifru_flags;
        int ifru_ivalue;
        int ifru_mtu;
        struct ifmap ifru_map;
        char ifru_slave[IFNAMSIZ];  /* Just fits the size */
        char ifru_newname[IFNAMSIZ];
        __caddr_t ifru_data;
      } ifr_ifru;
# define ifr_name ifr_ifrn.ifrn_name /* interface name */
# define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
# define ifr_addr ifr_ifru.ifru_addr /* address */
# define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
# define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
# define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
# define ifr_flags ifr_ifru.ifru_flags /* flags */
# define ifr_metric ifr_ifru.ifru_ivalue /* metric */
# define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
# define ifr_map ifr_ifru.ifru_map /* device map */
# define ifr_slave ifr_ifru.ifru_slave /* slave device */
# define ifr_data ifr_ifru.ifru_data /* for use by interface */
# define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
# define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
# define ifr_qlen ifr_ifru.ifru_ivalue /* queue length */
# define ifr_newname ifr_ifru.ifru_newname /* New name */

发现ifr_name,ifr_addr,ifr_hwaddr等都是sockaddr结构体类型的数据:

#include 

struct sockaddr
{
    unsigned short    sa_family;    // 2 bytes address family, AF_xxx
    char              sa_data[14];  // 14 bytes of protocol address
};

// IPv4 AF_INET sockets:

struct sockaddr_in
{
    short            sin_family;     // 2 bytes e.g. AF_INET, AF_INET6
    unsigned short   sin_port;       // 2 bytes e.g. htons(3490)
    struct in_addr   sin_addr;       // 4 bytes see struct in_addr, below
    char             sin_zero[8];    // 8 bytes zero this if you want to
};

struct in_addr
{
    unsigned long s_addr;            // 4 bytes load with inet_pton()
};

想IP地址,MAC,子网掩码等信息全都保存在sockaddr结构体的sa_data[14]成员中。这里我产生了疑问,这个sa_data[14]中ip、MAC、NETMASK是怎样的存储结构呢?所以在MAC.c代码中使用printf打印了sa_data[14]的信息。(要注意的是sa_data是char型数据,打印时类型强转成了unsigned char)。打印结果如下:
这里写图片描述可以看到sa_data[0~5]存放的是MAC。sa_data[2~5]存放的是IP地址、广播地址。ioctl还有一些其他的REQUEST我没试。

2、蛋疼的问题(还没搞清楚)
编译时报错结构体成员中没有*成员。

3、struct sockaddr_ll头文件未包含报错。头文件为”linux/if_packet.h”
使用命令查找头文件grep -R “^struct sockaddr_ll” find / -name include -print
4、原始套接字出错
使用AF_INET时,错误。

int sockfd = socket(AF_INET, SOCK_RAW, htons(ETH_P_ARP) );

使用PF_PACKET或AF_PACKET都可以。实际上PF_PACKET和AF_INET数值相同,但是意义不同。PF_PACKET表示协议族,AF_PACKET表示地址族。

int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) );
//也可以使用int sockfd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ARP) );

通过man socket可知要使用AF_PACKET:
这里写图片描述

5、sendto报错:invalid arguments,errno=22.
这个问题真是找了我老长时间。使用sendto在操作原始套接字和UDP套接字时,要用指导对端的地址。
这里写图片描述于是定位到struct sockaddr_ll结构体赋值语句。发现问题其实不在此处。参见博客(本人真的是感谢这位博主,如果不是您的播客,我遇到的问题不知道什么时候才能查出来)linux原始套接字(1)-arp请求与接收
读过分析这篇博客的“ARP请求代码”发现博主直接使用数组来构造ARP包。42这个数字引起了我的注意。
因为在博客【linux环境编程】 ARP编程这里写图片描述
这里明确写了还要18个填充字节。

char buf[42];

我已开始觉得会不会是结构体arp_packet长度过长。用了sizeof()一看结果是60。我又以为会不会是编译器默认4字节对齐,还特意加了一句#pragma pack(1)。发现sizeof()结果还是60。我就不淡定了。然后自己一计算6+6+2 +2+2+1+1+2+6+4+6+4+6+4=60。我尝试者把arp_padding[18]注释掉。结果sendto不再报错,程序运行无误。wireshark抓包结果如下。虚拟机给主机发送ARP包。
这里写图片描述

作为一个网络编程菜鸟,还要太多主要积累。特写出此经历,大家共勉。谢谢阅读。
2017.06.21添加的笔记内容解答了大部分我遇到的问题。


推荐阅读
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 本文讨论了一个数列求和问题,该数列按照一定规律生成。通过观察数列的规律,我们可以得出求解该问题的算法。具体算法为计算前n项i*f[i]的和,其中f[i]表示数列中有i个数字。根据参考的思路,我们可以将算法的时间复杂度控制在O(n),即计算到5e5即可满足1e9的要求。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了Codeforces Round #321 (Div. 2)比赛中的问题Kefa and Dishes,通过状压和spfa算法解决了这个问题。给定一个有向图,求在不超过m步的情况下,能获得的最大权值和。点不能重复走。文章详细介绍了问题的题意、解题思路和代码实现。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
author-avatar
Pingac
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有