什么是ARP协议
ARP协议是Address Resolution Protocol(地址解析协议)的缩写。在局域网中,网络中实际传输的是数据帧,数据帧里面有目的主机的MAC地址。但这个目的MAC地址是如何获取的呢?就是通过地址解析协议获得的。所谓“地址解析”就是主机在发送数据帧之前将目标IP地址转化成目标MAC地址的过程。ARP协议的基本功能就是通过目标设备的IP地址,查询目标设备的MAC地址,以保证通信的顺利进行。
ARP协议的封装
图1 ARP数据报格式
1.硬件类型字段:2字节。用来定义运行ARP的网络类型。每一个局域网基于其类型被指定一个整数。例如,以太网的类型为1。
2.协议类型字段:2字节。用来定义高层协议的类型。例如,对IPv4协议,这个字段的值为0x0806。
3.硬件地址长度:1字节。用来定义以字节为单位的物理地址长度。例如,对以太网这个值为6。
4.协议地址长度:1字节。用来定义以字节为单位的协议地址长度。例如,对IPv4这个值是4。
5.操作字段:占2字节。用来定义分组的类型。例如,ARP请求分组为1,ARP响应分组为2。
6.发送端硬件地址:6字节。
7.发送端协议地址:4字节。
8.目标端硬件地址:6字节。
9.目标端协议地址:4字节。
如图2所示,ARP数据报是直接封装在数据链路层的数据帧中。
图2 ARP数据报的封装
原始套接字发送ARP数据包
通过以下建立发送ARP数据包的原始套接字:
socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) )
第一个参数协议族为PF_PACKET,定义数据链路层上封包;第二个参数socket类型为SOCK_RAW,定义为原始套接字;第三个参数协议类型不能为0,此处定义为ARP协议。
由于是数据链路层上封包,地址定义需要使用协议无关的sockaddr_ll结构,填写其中的接口索引字段。
按照以太网帧的头部和ARP数据包定义构造发送数据,用sendto函数发送。具体的代码见附录1。
实验
实验采用两台虚拟机,IP地址分别为192.168.182.133和192.168.182.132,使用192.168.182.133作为ARP请求端。
先在ARP请求端删除关于192.168.182.132的ARP记录:
arp -d 192.168.182.132
然后打开wireshark进行抓包,最后执行编写的ARP发送程序,观察wireshark结果。如图3所示,对方进行了ARP应答。
图3 wireshark抓包结果
附录1
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
int main()
{
int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) );
if (sockfd == -1)
{
printf("socket error\n"); return 0;
}
//获取网卡信息
sockaddr_ll addr_ll;
memset(&addr_ll, 0, sizeof(sockaddr_ll));
addr_ll.sll_family = PF_PACKET;
ifreq ifr;
strcpy(ifr.ifr_name, "ens33");
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) == -1)
{
printf("error ioctl SIOCGIFINDEX\n"); return 0;
}
addr_ll.sll_ifindex = ifr.ifr_ifindex; //接口索引
if (ioctl(sockfd, SIOCGIFADDR, &ifr) == -1)
{
printf("error ioctl SIOCGIFADDR\n"); return 0;
}
char* ipSrc = inet_ntoa(((struct sockaddr_in*)(&(ifr.ifr_addr)))->sin_addr);
printf("ip address : %s\n", ipSrc); //source ip
if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == -1)
{
printf("error ioctl SIOCGIFHWADDR\n"); return 0;
}
unsigned char macSrc[ETH_ALEN];
memcpy(macSrc, ifr.ifr_hwaddr.sa_data, ETH_ALEN); //mac address
printf("mac address");
for (int i = 0; i < ETH_ALEN; i++)
printf(":%02x", macSrc[i]);
printf("\n");
//填充以太网首部 和 ARP信息
unsigned char macDst[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
ether_header header;
memcpy(header.ether_dhost, macDst, ETH_ALEN);
memcpy(header.ether_shost, macSrc, ETH_ALEN);
header.ether_type = htons(ETHERTYPE_ARP);
ether_arp arp;
arp.arp_hrd = htons(ARPHRD_ETHER);
arp.arp_pro = htons(ETHERTYPE_IP);
arp.arp_hln = ETH_ALEN;
arp.arp_pln = 4; //IPv4
arp.arp_op = htons(ARPOP_REQUEST);
in_addr src_in_addr, dst_in_addr;
inet_pton(AF_INET, ipSrc, &src_in_addr);
inet_pton(AF_INET, "192.168.182.132", &dst_in_addr);
memcpy(arp.arp_sha, macSrc, ETH_ALEN);
memcpy(arp.arp_spa, &src_in_addr, 4);
memcpy(arp.arp_tha, macDst, ETH_ALEN);
memcpy(arp.arp_tpa, &dst_in_addr, 4);
unsigned char sendBuf[sizeof(ether_header) + sizeof(ether_arp) ];
memcpy(sendBuf, &header, sizeof(ether_header) );
memcpy(sendBuf + sizeof(ether_header), &arp, sizeof(ether_arp));
int len = sendto(sockfd, sendBuf, sizeof(sendBuf), 0, (const sockaddr*)&addr_ll, sizeof(addr_ll) );
if (len > 0)
{
printf("send success\n");
}
return 0;
}
参考
刘兵,刘欣等编著.计算机网络基础[M].北京:中国水利水电出版社.2006.