赞
踩
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的函数:
- #include "test.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
-
- #include <sys/ioctl.h>
- #include <arpa/inet.h>
- #include <linux/if_arp.h>
-
-
- int pdkSendArpPacket()
- {
- unsigned char sender_ip[4] ; //可通过获取网卡ip
- unsigned char target_ip[4] ; //请求的目标IP
- //创建buffer
- unsigned char buffer[buffer_len];
- memset(buffer, 0, buffer_len);
- //创建以太网头部指针,指向buffer
- struct ethhdr *eth_req = (struct ethhdr*)buffer;
- //创建ARP包指针,指向buffer的后46字节,因为以太网头包含:2*6B(MAC地址)+2B(协议地址)=14B
- struct arp_head *arp_req = (struct arp_head*)(buffer+14);
- //创建sockaddr_ll结构地址
- struct sockaddr_ll sock_addr;
- //创建socket
- int sock_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
- if(sock_fd == -1){
- perror("socket()");
- exit(-1);
- }
- /* 获取网卡等需要的信息
- * ifreq结构体可以用于设置或者获取网卡等相关信息,定义在if.h中
- * 配合ioctl()一起使用
- * ioctl()的具体参数用法和系统实现相关,不是通用的,具体参见ioctls.h
- * 以下获取的信息都会保存在ifreq不同字段之中
- */
- struct ifreq ifr;
-
- /*根据网卡设备名获取Index*/
- strcpy(ifr.ifr_name,NICName);
- if(ioctl(sock_fd, SIOCGIFINDEX, &ifr) == -1)
- {
- perror("SIOCGIFINDEX");
- exit(-1);
- }
- int ifindex = ifr.ifr_ifindex;
- printf("网卡索引为:%d\n",ifindex);
-
- /*获取网卡设备MAC地址*/
- if(ioctl(sock_fd, SIOCGIFHWADDR, &ifr) == -1)
- {
- perror("SIOCGIFHWADDR");
- exit(-1);
- }
-
- /*将MAC地址写入所需结构*/
- for(int i=0;i<6;i++)
- {
- //以太网帧的目标MAC,即广播MAC,全1
- eth_req->h_dest[i] = (unsigned char)0xff;
- //ARP请求包目标MAC,全0
- arp_req->target_mac[i] = (unsigned char)0x00;
- //以太网帧源MAC,即本机MAC
- //ifr_hwaddr是sockaddr结构体格式
- eth_req->h_source[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
- //ARP请求包源MAC,即本机MAC
- arp_req->sender_mac[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
- //sockaddr中的MAC,也是本地MAC
- sock_addr.sll_addr[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
- }
-
- /*打印MAC地址*/
- printf("网卡MAC地址: %02X:%02X:%02X:%02X:%02X:%02X\n",
- eth_req->h_source[0],
- eth_req->h_source[1],
- eth_req->h_source[2],
- eth_req->h_source[3],
- eth_req->h_source[4],
- eth_req->h_source[5]);
-
- /*获取本机IP地址*/
-
- if(ioctl(sock_fd, SIOCGIFADDR, &ifr) == -1)//直接获取IP地址
- {
- perror("ioctl error");
- return -1;
- }
- struct sockaddr_in* addr = (struct sockaddr_in*)&(ifr.ifr_addr);
- memcpy(sender_ip, &(addr->sin_addr.s_addr), 4);
- //free ARP
- memcpy(target_ip, &(addr->sin_addr.s_addr), 4);
- // struct sockaddr_in sin;
- // const char* ip_str = inet_ntoa(sin.sin_addr);
- // memcpy(sender_ip, ip_str, IPV4_LENGTH);
-
- /*完善sockaddr_ll结构体*/
- sock_addr.sll_family = PF_PACKET;
- sock_addr.sll_protocol = htons(ETH_P_ARP);
- sock_addr.sll_ifindex = ifindex;
- sock_addr.sll_hatype = htons(ARPHRD_ETHER);
- sock_addr.sll_halen = ETH_ALEN;
-
- /*完善以太网帧头*/
- eth_req->h_proto = htons(ETH_P_ARP);
-
- /*完善ARP包头*/
- arp_req->hardware_type = htons(0x01);
- arp_req->protocol_type = htons(ETH_P_IP);
- arp_req->hardware_size = ETH_ALEN;
- arp_req->protocol_size = IPV4_LENGTH;
- arp_req->opcode = htons(ARPOP_REQUEST);
- memcpy(arp_req->sender_ip,&(addr->sin_addr.s_addr),IPV4_LENGTH);
- memcpy(arp_req->target_ip,&(addr->sin_addr.s_addr),IPV4_LENGTH);
-
- /*发送ARP请求*/
- if(sendto(sock_fd, buffer, 60, 0, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) == -1)
- {
- perror("sendto()");
- exit(-1);
- }
- printf("发送ARP请求包:");
- for(int i=0;i<60;i++)
- {
- if(i%16==0)
- printf("\n");
- printf("%02X ",buffer[i]);
- }
- printf("\n");
- close(sock_fd);
- return 0;
- }
其中IP通过ioctl从socket获取,功能实现了一个FREEARP的广播,用于更新本机在有效网络上的ARP条目。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。