您的位置:首页 > 运维架构 > Linux

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

2012-03-14 15:54 323 查看
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

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帧,然后一层一层的剥去首部,就可以得到最终用户发送的数据。

====
http://blog.csdn.net/chenjin_zhong/article/details/7272156
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: