深层解析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的函数:

#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条目。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值