Linux原始套接字之ARP协议实现

作者:chenjin_zhong

1. ARP协议介绍

 ARP(AddressResolutionProtocol)地址解析协议用于将计算机的网络地址(IP地址32位)转化为物理地址(MAC地址48位)[RFC826].ARP协议是属于链路层的协议,在以太网中的数据帧从一个主机到达网内的另一台主机是根据48位的以太网地址(硬件地址)来确定接口的,而不是根据32位的IP地址。内核(如驱动)必须知道目的端的硬件地址才能发送数据。当然,点对点的连接是不需要ARP协议的。ARP工作时,首先请求主机会发送出一个含有所希望到达的IP地址的以太网广播数据包,然后目标IP的所有者会以一个含有IP和MAC地址对的数据包应答请求主机。这样请求主机就能获得要到达的IP地址对应的MAC地址,同时请求主机会将这个地址对放入自己的ARP表缓存起来,以节约不必要的ARP通信。ARP协议是工作在数据链路层,基于以太网. 所以,必须了解以太网的MAC帧格式和ARP协议格式.

MAC帧示意图:

以太网的头部结构:struct ether_header{u_int8_t ether_dhost[ETH_ALEN]; // destination eth addru_int8_t ether_shost[ETH_ALEN]; // source ether addr u_int16_t ether_type; // packet type ID field} __attribute__ ((__packed__));整个以太网的头部包括: 目的地址(6字节),源地址(6字节),类型(2字节),帧内数据(46-1500个字节),CRC校验和(4字节)#define ETH_ALEN 6 //以太网地址的长度#define ETH_HALEN 14 //以太网头部的总长度 (6+6+2)#define ETH_ZLEN 60 //不含CRC校验数据的数据最小长度(46+14)#define ETH_DATA_LEN 1500 //帧内数据的最大长度#define ETH_FRAME_LEN 1514//不含CRC最大以太网长度(1500+14)

ARP协议示意图:

ARP头部信息:struct arphdr{__be16 ar_hrd;//硬件类型 1-硬件接口为以太网接口-2字节__be16 ar_pro;//协议类型-0x0800高层协议为IP协议 -2字节unsigned char ar_hln;//硬件地址长度-6字节 MAC-1字节unsigned char ar_pln;//协议地址长度-4字节为IP-1字节__be16 ar_op;//ARP操作码-1 ARP请求-2字节}ARP协议数据结构:struct ether_arp{ struct arphdr ea_hdr; //ARPfixed-size header(ARP固定大小的报头)-8字节 u_char arp_sha[ETHER_ADDR_LEN]; //sender hardware address(发送端硬件地址)-6字节 u_char arp_spa[4]; //sender protocol address(发送端协议地址)-4字节 u_char arp_tha[ETHER_ADDR_LEN]; // target hardware address(接收端硬件地址)-6字节 u_char arp_tpa[4]; //target protocol address(接收端协议地址)-4字节};#define arp_hrd ea_hdr.ar_hrd#define arp_pro ea_hdr.ar_pro#define arp_hln ea_hdr.ar_hln#define arp_pln ea_hdr.ar_pln#define arp_op ea_hdr.ar_opARP的头部一共是8个字节.ARP数据部分一共是20字节,所以ARP协议的长度是28个字节.

带以太网首部的ARP协议示意图:

可以看出,ARP协议长度为28个字节,以太网为14个字节,一共42字节而MAC帧的最小长度60个字节,因此必须增加18个字节的填充,构成ARP包.

以太网首部的帧类型用来指示上层协议的类型,是IP还是ARP.

2. ARP请求实例

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <netdb.h>#include <sys/socket.h>#include <sys/un.h>#include <sys/ioctl.h>#include <netinet/in.h>#include <net/if.h>#include <sys/types.h>#include <asm/types.h>#include <features.h> /* 需要里面的 glibc 版本号 */#if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1 #include <netpacket/packet.h> #include <net/ethernet.h> /* 链路层(L2)协议 */#else #include <asm/types.h> #include <linux/if_packet.h> #include <linux/if_ether.h> /* 链路层协议 */#endif#include <netinet/if_ether.h>/**以太网的头部结构:struct ether_header{u_int8_t ether_dhost[ETH_ALEN]; // destination eth addru_int8_t ether_shost[ETH_ALEN]; // source ether addr u_int16_t ether_type; // packet type ID field} __attribute__ ((__packed__));整个以太网的头部包括: 目的地址(6字节),源地址(6字节),类型(2字节),帧内数据(46-1500个字节),CRC校验和(4字节)#define ETH_ALEN 6 //以太网地址的长度#define ETH_HALEN 14 //以太网头部的总长度 (6+6+2)#define ETH_ZLEN 60 //不含CRC校验数据的数据最小长度(46+14)#define ETH_DATA_LEN 1500 //帧内数据的最大长度#define ETH_FRAME_LEN 1514//不含CRC最大以太网长度(1500+14)ARP头部信息:struct arphdr{__be16 ar_hrd;//硬件类型 1-硬件接口为以太网接口__be16 ar_pro;//协议类型-0x0800高层协议为IP协议 unsigned char ar_hln;//硬件地址长度-6字节 MACunsigned char ar_pln;//协议地址长度-4字节为IP__be16 ar_op;//ARP操作码-1 ARP请求}ARP协议数据结构:struct ether_arp{ struct arphdr ea_hdr; //ARPfixed-size header(ARP固定大小的报头) u_char arp_sha[ETHER_ADDR_LEN]; //sender hardware address(发送端硬件地址) u_char arp_spa[4]; //sender protocol address(发送端协议地址) u_char arp_tha[ETHER_ADDR_LEN]; // target hardware address(接收端硬件地址) u_char arp_tpa[4]; //target protocol address(接收端协议地址)};#define arp_hrd ea_hdr.ar_hrd#define arp_pro ea_hdr.ar_pro#define arp_hln ea_hdr.ar_hln#define arp_pln ea_hdr.ar_pln#define arp_op ea_hdr.ar_opsockaddr_ll为设备无关的物理层地址结构,描述发送端的地址结构struct sockaddr_ll{unsigned short sll_family; 总填 AF_PACKET unsigned short sll_protocol; 网络序列的物理层协议号 0x806为ARP协议int sll_ifindex; 接口编号 eth0对应的编号 unsigned short sll_hatype; 头部类型 ARPHRD_ETHER为以太网unsigned char sll_pkttype; 包类型 PACKET_HOSTunsigned char sll_halen; 地址长度 MAC地址长度6字节unsigned char sll_addr[8];物理地址 MAC地址只用了前面的6字节};FF:FF:FF:FF:FF:FFSOCK_RAW原始套接字的分析:(1)socket(AF_INET,SOCK_RAW,IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP);//发送或接收ip数据包,得到原始的IP包(2)socket(PF_PACKET,SOCK_RAW,htons(ETH_P_IP|ETH_P_ARP|ETH_P_RAP|ETH_P_ALL));//发送或接收以太网数据帧(1)使用第一种套接字类型,能得到发往本机的原始的IP数据包,但不能得到发往非本机的IP数据包,被过滤了,也不能得到从本机发出去的数据包。这类协议可自己组织TCP,ICMP,UDP数据包。(2)第二种套接字能收到发往本地的MAC帧,也能收到从本机发出去的数据帧(第3个参数为ETH_P_ALL),能接收到非发往本地的MAC数据帧(网卡需要设置为promisc混杂模式)协议类型:ETH_P_IP 0X800 只接收发往本机的mac的ip类型的数据帧ETH_P_ARP 0X806 只接收发往本机的arp类型的数据帧ETH_P_RARP 0x8035 只接受发往本机的rarp类型的数据帧ETH_P_ALL 0X3 接收发往本机的MAC所有类型ip,arp,rarp数据帧,接收从本机发出去的数据帧,混杂模式打开的情况下,会接收到非发往本地的MAC数据帧此时设备无关的物理地址使用struct sockaddr_ll从而得到MAC帧**///发送ARP数据,ARP协议结构+以太网头部int main(int argc,char*argv[]){ struct arppacket { struct ether_header eh;//以太网的头部 struct ether_arp ea;//arp包数据结构 u_char padding[18];//填充位,ARP包的最小长度是60个字节,不包括以太网的帧的CRC校验和 } arpreq;//不包含CRC校验的以太网的帧的最小长度为60个字节 int fd; if((fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_RARP))) < 0) {//发送ARP数据包 perror("Socket error"); exit(1); } bzero(&arpreq, sizeof(arpreq)); /* 填写以太网头部*/ //目的MAC char eth_dest[ETH_ALEN]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; //源MAC char eth_source[ETH_ALEN]={0x00,0x13,0xD4,0x36,0x98,0x34}; memcpy(arpreq.eh.ether_dhost,eth_dest, ETH_ALEN); memcpy(arpreq.eh.ether_shost, eth_source, ETH_ALEN); arpreq.eh.ether_type = htons(ETHERTYPE_ARP);//协议类型ARP协议 /* 填写arp数据 */ arpreq.ea.arp_hrd = htons(ARPHRD_ETHER);//硬件类型,主机字节序转换成网络字节序 arpreq.ea.arp_pro = htons(ETHERTYPE_IP);//协议类型 arpreq.ea.arp_hln = ETH_ALEN;//MAC地址长度6字节 arpreq.ea.arp_pln = 4;//IP地址长度 arpreq.ea.arp_op = htons(ARPOP_REQUEST);//操作码,ARP请求包 memcpy(arpreq.ea.arp_sha, eth_source, ETH_ALEN); char *source_ip="222.27.253.108"; struct in_addr source; inet_pton(AF_INET,source_ip,&source); /** struct in_addr{ u32 s_addr; } 这个结构体只有一个变量,所以结构体的地址与变量的地址是一样的 u_char arp_spa[4]是4字节的unsigned char型变量,unsigned char型是数值型,所以一共是32位 这样就把source.s_addr内存地址处的4字节二进制IP地址复制到内存地址arp_spa处,而source与source.s_addr 内存地址是一致的,所以可以直接用source的地址 **/ memcpy(arpreq.ea.arp_spa, &source, 4);//源IP char *dst_ip="222.27.253.1"; struct in_addr dst; inet_pton(AF_INET,dst_ip,&dst); //目的IP memcpy(arpreq.ea.arp_tpa,&dst,4); struct sockaddr_ll to; bzero(&to,sizeof(to)); to.sll_family = PF_PACKET; to.sll_ifindex = if_nametoindex("eth0");//返回对应接口名的编号 int i=0; for(i=0;i<1;i++){ int sendsize=sizeof(struct ethhdr)+sizeof(struct ether_arp); int size=sendto(fd,&arpreq,sizeof(arpreq),0,(struct sockaddr*)&to,sizeof(to));//发送arp请求包 printf("size=%d\n",size); } //接收ARP响应包 char buffer[ETH_FRAME_LEN]; bzero(buffer,ETH_FRAME_LEN); struct sockaddr_ll to1; bzero(&to1,sizeof(to1)); int len=sizeof(to1); int recvfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP));//只接受发往本抽的ARP帧 for(;;){ int size=recvfrom(recvfd,buffer,ETH_FRAME_LEN,0,(struct sockaddr*)&to1,&len); //从以太网包头开始输出值 //以太网的目的MAC struct ether_header *h1=(struct ether_header*)buffer; //ARP包 struct ether_arp* arp=(struct ether_arp*)(buffer+14);//以太网的源MAC6个字节 ,目的MAC 6个字节,类型为2字节 printf("目的MAC:"); for(i=0;i<ETH_ALEN;i++){ printf("%02x-",h1->ether_dhost[i]); } printf("\n"); //源MAC地址 printf("源MAC:"); for(i=0;i<ETH_ALEN;i++){ printf("%02x-",h1->ether_shost[i]); } //以太网的帧类型 printf("\n"); printf("帧类型:%0x\n",ntohs(h1->ether_type));//十六进制ARP包 //判断是否是ARP响应包,如果是操作方式码为2 //printf("%d\n",(ntohs)(arp->arp_op));//一定要把网络字节序转换为主机字节序 if((ntohs)(arp->arp_op)==2){ //硬件类型 printf("硬件类型:%0x\n",(ntohs)(arp->arp_hrd));//1代表硬件接口为以太网接口 //协议类型 printf("协议类型:%0x\n",(ntohs)(arp->arp_pro));//0x800代表高层协议为IP //硬件地址长度 printf("硬件地址长度:%0x\n",arp->arp_hln); //协议地址长度 printf("协议地址长度:%0x\n",arp->arp_pln); //发送方的MAC地址 printf("发送方的MAC:"); for(i=0;i<ETH_ALEN;i++){ printf("%02x-",arp->arp_sha[i]); } printf("\n"); printf("发送方的IP:"); char ip[16]; inet_ntop(AF_INET,arp->arp_spa,&ip,16);//arp_spa是一个unsigned char数组 printf("%s\n",ip); printf("接收方的硬件地址:"); for(i=0;i<ETH_ALEN;i++){ printf("%02x-",arp->arp_tha[i]); } printf("\n"); //接收方的IP地址 bzero(&ip,16); printf("接收方的IP地址为:"); inet_ntop(AF_INET,arp->arp_tpa,&ip,16); printf("%s\n",ip); break; } }return 1;}运行结果:./arp

size=60目的MAC:00-13-d4-36-98-34-源MAC:00-0f-e2-5f-3c-8c-帧类型:806硬件类型:1协议类型:800硬件地址长度:6协议地址长度:4发送方的MAC:00-0f-e2-5f-3c-8c-发送方的IP:222.27.253.1接收方的硬件地址:00-13-d4-36-98-34-接收方的IP地址为:222.27.253.108总结:本文主要介绍了以太网的帧结构以及ARP协议,并给出了ARP请求与响应的具体实例。PF_PACKET的原始套接字可以得到MAC帧,然后在MAC帧之上构建ARP数据包即可。通过这个例子,我们可以通过监听网卡,得到经过本机的MAC帧,然后一层一层的剥去首部,就可以得到最终用户发送的数据。临行之前,面对太多的疑问和不解:为何是一个人?

Linux原始套接字之ARP协议实现

相关文章:

你感兴趣的文章:

标签云: