当前位置:   article > 正文

深层解析ARP协议_arp状态机

arp状态机

ARP(Address Resolution Protocol)此协议是用于实现IPv4地址到mac转换的协议。

它提供了三层地址到二层地址之间的映射,提供二层首部缓存加速二层头的封装,提供二层报文头的封装。

IPv6到mac转换的NDP协议不同之处有以下两点:

1. 报文格式
ARP协议的报文格式
| 以太网头部 | ARP头部 |
| --- | --- |
| 目标MAC地址 | 源MAC地址 |
| 协议类型 | 协议类型 |
| MAC地址长度 | IP地址长度 |
| 操作码 | 发送方MAC地址 |
| 发送方MAC地址 | 发送方IP地址 |
| 发送方IP地址 | 目标MAC地址 |
| 目标IP地址 | 目标IP地址 |

NDP协议的报文格式
| 以太网头部 | IPv6头部 | NDP头部 |
| --- | --- | --- |
| 目标MAC地址 | 源MAC地址 | 版本号 |
| 协议类型 | 协议类型 | 路由标识符 |
| MAC地址长度 | 流量类别 | 目标IPv6地址 |
| 下一跳缓存寿命 | 负载长度 | NDP消息类型 |
| 跳数限制 | 下一跳IPv6地址/目标IPv6地址(根据消息类型而定) | 原始发送者IPv6地址 |

2. 功能
ARP协议主要用于解析IPv4地址和MAC地址之间的映射关系,可以通过广播方式查询目标设备的MAC地址,并将结果缓存到本地。当需要发送数据时,可以直接使用缓存中的MAC地址进行通信。
NDP协议主要用于解析IPv6地址和MAC地址之间的映射关系,可以通过广播方式查询目标设备的MAC地址,并将结果缓存到本地。此外,NDP协议还提供了一些其他功能,如路由器发现、邻居维护等。

3.由于IPv6的同网段地址数量陡增,NDP协议为了预防网络堵塞没有采用与ARP协议相同的广播方式,而是使用了尽量简略的组播方式以减少对网络的压力

两者实现方式和数据格式却大为不同,另外NDP的协议还提供了一些其他功能例如路由器发现、邻居维护等,因此即便两者虽然同样是用于IP地址到MAC转换的功能,也不能因此混为一谈。

ARP作为一个临时表项存在于我们的系统中,作为一个临时表项,ARP的有效时限极为复杂,我们在LINUX系统下不难发现ARP的配置位置 /proc/sys/net/ipv4/neigh/eth0(eth*)

这些表项在编译期由systemd-networkd中的配置文件写入,大致位置可通过在src源文件夹搜索上图的关键字找到。那么如何理解这些配置文件是如何生效的,我们需要引入一个概念:ARP状态机。

 ARP状态机

实现ARP缓存机制,Linux协议栈实现为ARP缓存维护了一个状态机。 每一个ARP缓存表都对应的存在一个ARP状态机,而从图中可以看到 arp 缓存表仅在Reachable状态对于外发包是可用的,而Stale状态实际是不可用的,可以理解为Linux维护stale只是为了保留一个neighbour结构体,在其状态改变时只是个别字段得到修改或者填充。

通过上图我们可以按照以下理解来解释每一个状态:

0.incomplete:初始状态,表示我们不知道目标IP地址对应的MAC地址。

1.stale:当一个ARP条目进入Stale状态后,它不能直接使用,需要重新进行ARP请求来获取最新的MAC地址。在Stale状态下,如果收到了ARP响应,则该条目会重新回到Reachable状态;如果在Stale状态下连续几次没有收到ARP响应,则该条目会进入Delay状态。

2.Reachable:当一个ARP条目被创建或更新时,它会进入Reachable状态。在Reachable状态下,ARP条目可以直接使用,而且会定期发送ARP请求来更新该条目。如果在Reachable状态下连续几次没有收到ARP响应,则该条目会进入Stale状态。

3. Delay:当一个ARP条目进入Delay状态后,它不能直接使用,并且不会发送ARP请求来更新该条目。在Delay状态下,如果收到了ARP响应,则该条目会重新回到Reachable状态;如果在Delay状态下连续几次没有收到ARP响应,则该条目会被删除。

4.probe:当内核需要验证一个处于stale状态的ARP缓存条目是否仍然有效时,它会向该条目对应的IP地址发送一个ARP请求。如果收到响应,则该条目的状态变为reachable;否则,该条目的状态变为failed。

5.permanent:当管理员手动添加一个ARP缓存条目时,该条目的状态为permanent。这意味着该条目永远不会过期,并且只能通过手动删除来清除。

ARP状态机中,实际上生效的状态仅有reachable一项,当状态机从reachable进入stale状态的时候,为了保留neighbour结构体,优化内存以及CPU利用,实际上 arp缓存表项仅仅是表现为不可用,而不是直接删除。在连接断开或者reachable表项过期进入stale状态的时候,都需要通过重新发送ARP包来实现更新mac地址以防止过期。

所以我们可以概括为:配置文件中提及的gc_stale_time控制我们何时回收过期的ARP表项,有效ARP的时间由base_reachable_time所控制。

ARP数据报:

ARP报文长度可以是42位 也可以是带描述的60位格式。

下面是简单实现了一个自填充手动下发FREEARP的函数:

  1. #include "test.h"
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <unistd.h>
  6. #include <sys/ioctl.h>
  7. #include <arpa/inet.h>
  8. #include <linux/if_arp.h>
  9. int pdkSendArpPacket()
  10. {
  11. unsigned char sender_ip[4] ; //可通过获取网卡ip
  12. unsigned char target_ip[4] ; //请求的目标IP
  13. //创建buffer
  14. unsigned char buffer[buffer_len];
  15. memset(buffer, 0, buffer_len);
  16. //创建以太网头部指针,指向buffer
  17. struct ethhdr *eth_req = (struct ethhdr*)buffer;
  18. //创建ARP包指针,指向buffer的后46字节,因为以太网头包含:2*6B(MAC地址)+2B(协议地址)=14B
  19. struct arp_head *arp_req = (struct arp_head*)(buffer+14);
  20. //创建sockaddr_ll结构地址
  21. struct sockaddr_ll sock_addr;
  22. //创建socket
  23. int sock_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
  24. if(sock_fd == -1){
  25. perror("socket()");
  26. exit(-1);
  27. }
  28. /* 获取网卡等需要的信息
  29. * ifreq结构体可以用于设置或者获取网卡等相关信息,定义在if.h中
  30. * 配合ioctl()一起使用
  31. * ioctl()的具体参数用法和系统实现相关,不是通用的,具体参见ioctls.h
  32. * 以下获取的信息都会保存在ifreq不同字段之中
  33. */
  34. struct ifreq ifr;
  35. /*根据网卡设备名获取Index*/
  36. strcpy(ifr.ifr_name,NICName);
  37. if(ioctl(sock_fd, SIOCGIFINDEX, &ifr) == -1)
  38. {
  39. perror("SIOCGIFINDEX");
  40. exit(-1);
  41. }
  42. int ifindex = ifr.ifr_ifindex;
  43. printf("网卡索引为:%d\n",ifindex);
  44. /*获取网卡设备MAC地址*/
  45. if(ioctl(sock_fd, SIOCGIFHWADDR, &ifr) == -1)
  46. {
  47. perror("SIOCGIFHWADDR");
  48. exit(-1);
  49. }
  50. /*将MAC地址写入所需结构*/
  51. for(int i=0;i<6;i++)
  52. {
  53. //以太网帧的目标MAC,即广播MAC,全1
  54. eth_req->h_dest[i] = (unsigned char)0xff;
  55. //ARP请求包目标MAC,全0
  56. arp_req->target_mac[i] = (unsigned char)0x00;
  57. //以太网帧源MAC,即本机MAC
  58. //ifr_hwaddr是sockaddr结构体格式
  59. eth_req->h_source[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
  60. //ARP请求包源MAC,即本机MAC
  61. arp_req->sender_mac[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
  62. //sockaddr中的MAC,也是本地MAC
  63. sock_addr.sll_addr[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
  64. }
  65. /*打印MAC地址*/
  66. printf("网卡MAC地址: %02X:%02X:%02X:%02X:%02X:%02X\n",
  67. eth_req->h_source[0],
  68. eth_req->h_source[1],
  69. eth_req->h_source[2],
  70. eth_req->h_source[3],
  71. eth_req->h_source[4],
  72. eth_req->h_source[5]);
  73. /*获取本机IP地址*/
  74. if(ioctl(sock_fd, SIOCGIFADDR, &ifr) == -1)//直接获取IP地址
  75. {
  76. perror("ioctl error");
  77. return -1;
  78. }
  79. struct sockaddr_in* addr = (struct sockaddr_in*)&(ifr.ifr_addr);
  80. memcpy(sender_ip, &(addr->sin_addr.s_addr), 4);
  81. //free ARP
  82. memcpy(target_ip, &(addr->sin_addr.s_addr), 4);
  83. // struct sockaddr_in sin;
  84. // const char* ip_str = inet_ntoa(sin.sin_addr);
  85. // memcpy(sender_ip, ip_str, IPV4_LENGTH);
  86. /*完善sockaddr_ll结构体*/
  87. sock_addr.sll_family = PF_PACKET;
  88. sock_addr.sll_protocol = htons(ETH_P_ARP);
  89. sock_addr.sll_ifindex = ifindex;
  90. sock_addr.sll_hatype = htons(ARPHRD_ETHER);
  91. sock_addr.sll_halen = ETH_ALEN;
  92. /*完善以太网帧头*/
  93. eth_req->h_proto = htons(ETH_P_ARP);
  94. /*完善ARP包头*/
  95. arp_req->hardware_type = htons(0x01);
  96. arp_req->protocol_type = htons(ETH_P_IP);
  97. arp_req->hardware_size = ETH_ALEN;
  98. arp_req->protocol_size = IPV4_LENGTH;
  99. arp_req->opcode = htons(ARPOP_REQUEST);
  100. memcpy(arp_req->sender_ip,&(addr->sin_addr.s_addr),IPV4_LENGTH);
  101. memcpy(arp_req->target_ip,&(addr->sin_addr.s_addr),IPV4_LENGTH);
  102. /*发送ARP请求*/
  103. if(sendto(sock_fd, buffer, 60, 0, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) == -1)
  104. {
  105. perror("sendto()");
  106. exit(-1);
  107. }
  108. printf("发送ARP请求包:");
  109. for(int i=0;i<60;i++)
  110. {
  111. if(i%16==0)
  112. printf("\n");
  113. printf("%02X ",buffer[i]);
  114. }
  115. printf("\n");
  116. close(sock_fd);
  117. return 0;
  118. }

其中IP通过ioctl从socket获取,功能实现了一个FREEARP的广播,用于更新本机在有效网络上的ARP条目。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号