如何用C语言操作arp cache

·

3 min read

如何用C语言操作arp cache

arp cache中存放着局域网内IP地址和MAC地址的对应关系,对socket通信是至关重要的,arp cache由Linux内核进行维护,本文介绍如何用ioctl获取arp cache记录,添加新记录到arp cache中,删除arp cache中记录,每一种操作均给出了完整的源程序,本文程序在ubuntu 20.4中编译运行成功,gcc版本9.4.0

1. ARP cache

  • ARP cache中存放着已知的IP地址与MAC地址的对应关系,局域网内计算机之间通信时,数据链路层需要将目的计算机的MAC地址填写在以太网报头中,这时就需要查询ARP cache以获得目的MAC地址;
  • ARP cache是由内核进行维护的,用户程序在用户空间无法对其进行直接操作;
  • 如果用户程序希望操作ARP cache就需要与内核进行通信,Linux下的ioctl提供了三种有关操作ARP cache的方法:
    1. SIOCDARP:从ARP cache中删除一条记录
    2. SIOCGARP:从ARP cache中获取一条记录
    3. SIOCSARP:向ARP cache中添加一条记录
  • 如果仅仅是要查询ARP cache中的记录,也可以读取文件 /proc/net/arp,该文件是内核中ARP cache的一个映射;
  • ARP cache中的记录分为动态记录和静态记录,动态记录是有时效的,时效到了记录会失效;静态记录则永久有效;
  • 通常,通过在局域网上广播arp请求获得的arp回应,在ARP cache中一定是动态记录,而使用ioctl设置的记录,通过设置arp_flags可以成为静态记录,永久有效,当然也可以是动态记录,在后面实例中会说明这一点;
  • 当我们 ping [IP address] 的时候,这个[IP address]的arp记录就会被加到ARP cache中;
  • 既然ARP cache中的记录多为动态记录,而动态记录有一个超时时间,过了这个时间,记录就会失效,那么这个超时时间是多少呢?
  • 内核中有专门的arp垃圾清除程序,这个垃圾清除程序可以清除已经失效的arp记录,该程序的启动遵循以下原则:
    • 内核中一些与arp垃圾清除有关的变量有:gc_thresh1、gc_thresh2、gc_thresh3和gc_interval
    • 可以用下面命令查看这些变量的值
      cat /proc/sys/net/ipv4/neigh/default/gc_thresh1
      cat /proc/sys/net/ipv4/neigh/default/gc_thresh2
      cat /proc/sys/net/ipv4/neigh/default/gc_thresh3
      cat /proc/sys/net/ipv4/neigh/default/gc_interval
      
    • 当arp cache中的记录数量小于gc_thresh1时,不启动arp垃圾清除程序,也就是说不会删除arp cache中的任何记录,包括过期记录;
    • 当arp cache中记录数在gc_thresh1和gc_thresh2之间时,按照一定频率启动arp垃圾清除程序,这个频率由gc_interval设置;
    • 当arp cache中记录数在gc_thresh2和gc_thresh3之间时,每5秒启动一次arp垃圾清除程序;
    • 当arp cache中记录数在gc_thresh3时,当加入新记录时,强制启动arp垃圾清除程序。
  • 所以,当你用程序插入一条动态记录时,你会发现等了很长时间这条记录都不会被删除,这可能是你的arp cache中记录数太少了。

2. ioctl的调用方法

  • 对于arp cache的操作,其调用方法为:

    #include <sys/sockio.h>
    #include <sys/socket.h>
    
    #include <net/if.h>
    #include <net/if_arp.h>
    
    struct arpreq arpreq;
    
    ioctl(s, SIOCSARP, (struct arpreq *)&arpreq);
    ioctl(s, SIOCGARP, (struct arpreq *)&arpreq);
    ioctl(s, SIOCDARP, (struct arpreq *)&arpreq);
    
  • ioctl的第一个参数s是socket,第二个参数是要进行的操作,第三个参数是一个arp请求的结构;
  • 第二个参数使用的三个宏定义,定义在头文件 bits/ioctls.h 中;
  • ARP ioctl请求结构,定义在net/if_arp.h或者linux/if_arp.h中;
    /* ARP ioctl request.  */
    struct arpreq {
        struct sockaddr arp_pa;       /* Protocol address.  */
        struct sockaddr arp_ha;       /* Hardware address.  */
        int arp_flags;                /* Flags.  */
        struct sockaddr arp_netmask;  /* Netmask (only for proxy arps).  */
        char arp_dev[16];
    };
    
    • arp_pa即Prototol Address,也就是IP地址;
    • arp_ha即Hardware Address,也就是MAC地址;
    • arp_netmask仅用于代理ARP,本文中用不上;
    • arp_dev用于指定本地网络接口的名称,需要填写;
  • 获取arp cache中的记录(SIOCGARP)

    • 需要填写(struct arpreq)中的arp_pa和arp_dev两个字段;
    • arp_pa实际对应(struct sockaddr_in),其中sin_family字段必须为AF_INET,sin_addr字段填请求MAC地址对应的IP地址,sin_port不用填;

      struct sockaddr {  
          sa_family_t sin_family;       /* Protocol family.  */
         char sa_data[14];
      };
      
      struct sockaddr_in {
          sa_family_t sin_family;       /* Protocol family.  */
          in_port_t sin_port;           /* Port number.  */
          struct in_addr sin_addr;      /* Internet address.  */
      
          /* Pad to size of `struct sockaddr'.  */
          unsigned char sin_zero[8];
      };
      
    • 调用ioctl成功后,arp_pa中包含的IP地址对应的MAC地址保存在arp_ha.sa_data中;
    • 若指定的网络接口不存在,或存在但与指定的目标主机IP地址不对应,则ioctl以"(ENXIO)No such device or address"错误调用失败;
    • 无法通过ioctl操作获取整个ARP cache,Linux下的命令‘arp -a’是通过读取文件/proc/net/arp来实现的;
  • 向arp cache中添加一条记录(SIOCSARP)
    • 需要填写arp_pa、arp_dev、arp_ha.sa_data和arp_flags四个字段;
    • arp_pa实际对应(struct sockaddr_in),其中sin_family字段必须为AF_INET,sin_addr字段填请求MAC地址对应的IP地址,sin_port不用填;
    • arp_ha中,sa_family填AF_UNSPEC,sa_data中填入在arp_pa中的IP地址对应的MAC地址;
    • arp_flags填'ATF_PERM | ATF_COM',表示这条记录为完整的、永久性记录,因为arp cache中的动态记录都是有时效的,过一段时间就会失效,只有设置为永久记录才使其成为一条静态记录,不会失效;
    • arp_flags中每一位代表一个标志,每一位的定义在linux/if_arp或者net/if_arp中:
      /* ARP Flag values.  */
      #define ATF_COM         0x02      /* Completed entry (ha valid).  */
      #define ATF_PERM        0x04      /* Permanent entry.  */
      #define ATF_PUBL        0x08      /* Publish entry.  */
      #define ATF_USETRAILERS 0x10      /* Has requested trailers.  */
      #define ATF_NETMASK     0x20      /* Want to use a netmask (only
                                           for proxy entries).  */
      #define ATF_DONTPUB     0x40      /* Don't answer this addresses.  */
      #define ATF_MAGIC       0x80      /* Automatically added entry.  */
      
  • 删除arp cache中的一条记录(SIOCDARP)
    • 需要填写arp_pa和arp_dev两个字段;
    • arp_pa实际对应(struct sockaddr_in),其中sin_family字段必须为AF_INET,sin_addr字段填请求MAC地址对应的IP地址,sin_port不用填;
    • 调用ioctl成功后,在arp_pa中指定IP地址对应的ARP记录被删除;
    • 若指定的网络接口不存在,或存在但与指定的目标主机IP地址不对应,则ioctl以"(ENXIO)No such device or address"错误调用失败;

3. 获取arp cache中记录的实例

  • ioctl获取arp cache的记录,只能一条一条的获取,不能一下获取一个完整的arp cache,要获取一个完整的arp cache,似乎唯一的办法就是读取文件:/proc/net/arp,至少我还没有找到其它方法;
  • 在这个实例中,我们仅仅获得arp cache中的一条记录,文件名为:arp-get.c(点击文件名下载源程序)
  • 编译
    gcc -Wall arp-get.c -o arp-get
    
  • 运行
    cat /proc/net/arp
    ./arp-get enp0s3 192.168.2.112
    
  • 运行截屏

    screenshot of arp_get


4. 从/proc/net/arp文件中获取完整arp cache

  • 如果想要看arp cache的完整记录,只能去读文件/proc/net/arp了,本例用读取文件的方式显示arp cache的全部记录;
  • 如果你对宏str(s)和xstr(s)的用法不熟悉,可以参考《Stringizing Operator (#) in C》
  • 文件名:arp-get-all.c(点击文件名下载源程序)
  • 文件/proc/net/arp中有6个字段,本程序只读出了其中的3个:IP地址、设备名称和MAC地址,如果需要,可以修改程序读出全部字段;
  • 编译
    gcc -Wall arp-get-all.c -o arp-get-all
    
  • 运行
    ./arp-get-all
    
  • 运行截图

    screenshot of arp_get_all


5. 在arp cache中添加一条静态记录的实例

  • 在这个程序中要重点解释的是关于arp_flags的设置
    arp_req.arp_flags = ATF_PERM | ATF_COM;
    
  • 关于arp_flags相关宏的定义在前面已经有介绍,其中ATF_COM表示记录是完整的,ATF_PERM表示记录是永久性的;
  • arp_flags是必须要设置ATF_COM的,否则这条记录将被认为是不完整的,用arp -ae显示arp记录时,该记录将被标记为(incomplete);
  • 当需要添加一条动态记录时,arp_flags = ATF_COM即可,当需要添加一条静态记录时,要设置arp_flags = ATF_COM | ATF_PERM
  • 文件名:arp-set.c(点击文件名下载源程序)
  • 编译
    gcc -Wall arp-set.c -o arp-set
    
  • 因为要修改内核中的arp cache,所以运行这个程序,是需要root权限的
    arp -ae
    sudo ./arp-set enp0s3 192.168.2.118 85:3b:4c:36:41:f5
    arp -ae
    
  • 运行截图

    screenshot of arp_set


  • 可以看出运行完arp_set后,arp cache中多了一条我们设置的记录,其标志为CM,前面介绍过有关arp_flags的宏定义,CM其实就是(ATF_COM | ATF_PERM),含义为:完整的永久记录;我们加入的这条记录是静态记录,永久有效,其它的记录是动态记录,是有时效的,所以其它记录的标志为C,也就是常数ATF_COM,表示记录完整。

6. 在arp cache中删除一条记录的实例

  • arp cache实际上是以[IP address]为键值的,所以,删除时只需要知道设备名称和IP地址即可;
  • 文件名:arp-del.c(点击文件名下载源程序)
  • 编译
    gcc -Wall arp-del.c -o arp-del
    
  • 这个程序也是要root权限才能运行的,我们将上一节添加的记录删除掉
    arp -ae
    sudo ./arp-del enp0s3 192.168.2.118
    arp -ae
    
  • 运行截屏

    screenshot of arp_del


欢迎访问我的博客:whowin.cn

email:

donation