Linux原始套接字之ARP協議實現

轉自:http://blog.csdn.net/chenjin_zhong/article/details/7272156

作者: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 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協議示意圖:


http://images.51cto.com/files/uploadimg/20091211/095250676.jpg

可以看出,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

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幀,然後一層一層的剝去首部,就可以得到最終用戶發送的數據。
發佈了4 篇原創文章 · 獲贊 7 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章