轉自:http://blog.csdn.net/chenjin_zhong/article/details/7272156
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 addr
u_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_op
ARP的頭部一共是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 addr
u_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字節 MAC
unsigned 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_op
sockaddr_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_HOST
unsigned char sll_halen; 地址長度 MAC地址長度6字節
unsigned char sll_addr[8];物理地址 MAC地址只用了前面的6字節
};
FF:FF:FF:FF:FF:FF
SOCK_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
目的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幀,然後一層一層的剝去首部,就可以得到最終用戶發送的數據。