首页 > 代码库 > 【linux环境编程】 ARP编程

【linux环境编程】 ARP编程

(注:部分摘自”Linux C编程一站式学习“)

以太网(RFC 894)帧格式


图一 以太网数据包类型

其中的源地址和目的地址是指网卡的硬件地址(也叫MAC地址),长度是48位,是在网卡出厂时固化的。用ifconfig命令看一下,“HWaddr 00:15:F2:14:9E:3F”部分就是硬件地址。协议字段有三种值,分别对应IP、ARP、RARP。帧末尾是CRC校验码。


以太网帧中的数据长度规定最小46字节,最大1500字节,ARP和RARP数据包的长度不够46字节,要在后面补填充位。最大值1500称为以太网的最大传输单元(MTU),不同的网络类型有不同的MTU,如果一个数据包从以太网路由到拨号链路上,数据包长度大于拨号链路的MTU了,则需要对数据包进行分片(fragmentation)。ifconfig命令的输出中也有“MTU:1500”。注意,MTU这个概念指数据帧中有效载荷的最大长度,不包括帧首部的长度。

在网络通讯时,源主机的应用程序知道目的主机的IP地址和端口号,却不知道目的主机的硬件地址,而数据包首先是被网卡接收到再去处理上层协议的,如果接收到的数据包的硬件地址与本机不符,则直接丢弃。因此在通讯前必须获得目的主机的硬件地址。ARP协议就起到这个作用。源主机发出ARP请求,询问“IP地址是192.168.0.1的主机的硬件地址是多少”,并将这个请求广播到本地网段(以太网帧首部的硬件地址填FF:FF:FF:FF:FF:FF表示广播),目的主机接收到广播的ARP请求,发现其中的IP地址与本机相符,则发送一个ARP应答数据包给源主机,将自己的硬件地址填写在应答包中。

每台主机都维护一个ARP缓存表,可以用arp -a命令查看。缓存表中的表项有过期时间(一般为20分钟),如果20分钟内没有再次使用某个表项,则该表项失效,下次还要发ARP请求来获得目的主机的硬件地址。想一想,为什么表项要有过期时间而不是一直有效?


图二 arp数据包类型

注意到源MAC地址、目的MAC地址在以太网首部和ARP请求中各出现一次,对于链路层为以太网的情况是多余的,但如果链路层是其它类型的网络则有可能是必要的。硬件类型指链路层网络类型,1为以太网,协议类型指要转换的地址类型,0x0800为IP地址,后面两个地址长度对于以太网地址和IP地址分别为6和4(字节),op字段为1表示ARP请求,op字段为2表示ARP应答。

有了上面的基础知识,下面我们就来实战编程:

1、确定socket的参数

根据图一,我们可以看出arp,rarp和ip虽然同属于网络层(又名IP层),但是他们的数据包装是独立的。虽然icmp和igmp也处在IP层,但是它们又需要ip数据报的包装。所以我们在为arp和rarp建立socket的时候,就不能利用ip的原始数据报(SOCK_RAW)了,我们需要最原始的以太网帧的数包(SOCK_PACKET);在对于网络类型的选择上,可以根据需要选择IPv4(AF_INET)或IPv6(AF_INET6);arp的协议类型跟以太网帧数据类型一样,所以应该是0x0806,在linux的”Ethernet Protocol ID“中定义了

#define ETH_P_ARP 0x0806

那么,我们就可以简单的以为socket套接字的创建是:sfd = socket(AF_INET,SOCK_PACKET,htons(ETH_P_ARP));

2、确定ARP数据包的结构

根据图二,确定如下结构体

struct arp_packet
{
    //以太网首部
    unsigned char ap_dstmac[6];  //6字节
    unsigned char ap_srcmac[6];  //6字节
    unsigned short ap_frame;     //2字节
    //arp
    unsigned short ap_hwtype;    //2字节:硬件地址类型
    unsigned short ap_prototype; //2字节:软件地址类型
    unsigned char  ap_hwlen;     //1字节:硬件地址长度   
    unsigned char  ap_prolen;    //1字节:软件地址长度
    unsigned short ap_op;        //2字节:操作类型
    unsigned char  ap_frommac[6];//6字节
    unsigned char  ap_fromip[4]; //4字节
    unsigned char  ap_tomac[6];  //6字节
    unsigned char  ap_toip[4];   //4字节
    //18字节:填充字节,因为以太网数据最少要46字节
    unsigned char  ap_padding[18];
};
3、获取本地ip地址和mac地址

linux里面提供了mac地址和ip地址获取的ioctl参数;

#define SIOCGIFADDR 0x8915      /* get PA address       */
#define SIOCSIFADDR 0x8916      /* set PA address       */
#define SIOCSIFHWADDR   0x8924      /* set hardware address     */
#define SIOCGIFHWADDR   0x8927      /* Get hardware address     */
在获取本机mac地址和ip地址之前,我们需要告诉ioctl我们要获取那个网卡的参数:

struct ifreq  eth;
strcpy(eth.ifr_name,"eth0");
/* Get eth0 Hardware Address */
int ret = ioctl(fds,SIOCGIFHWADDR, ð);
if(ret < 0){
	perror("Get Hardware Address Fail:");
	goto close_socket;
}
/* Get eth0 IP Address */
ret = ioctl(fds, SIOCGIFADDR, ð);
if(ret < 0){
	perror("Get IP Address Fail:");
	goto close_socket;
}
unsigned char mac_addr[6];
memcpy(mac_addr,eth.ifr_hwaddr.sa_data,6);
/* In "struct sockaddr",ip address starts from 'sa_data' two bytes later*/
unsigned char ip_addr[4];
memcpy(ip_addr,eth.ifr_addr.sa_data+2,4);
4、数据包数据填写

struct arp_packet arp_in;

bzero(&arp_in,sizeof(struct arp_packet));
unsigned char broadcast_mac[6] = {0xff,0xff,0xff,0xff,0xff,0xff};
memcpy(arp_in.ap_dstmac,broadcast_mac,6);
memcpy(arp_in.ap_srcmac,mac_addr,6);
arp_in.ap_frame = htons(ETH_P_ARP);
arp_in.ap_hwtype = htons(0x0001);
arp_in.ap_prototype = htons(ETH_P_IP);
arp_in.ap_hwlen = 6;
arp_in.ap_prolen = 4;
arp_in.ap_op = htons(0x0001);//0x0001-ARP req 0x0002-ARP Reply
memcpy(arp_in.ap_frommac,mac_addr,6);
memcpy(arp_in.ap_fromip,ip_addr,4);
5、数据与接收

由于在这种情况下,对方的mac地址都是未知的,而且数据的发送也是作为广播模式发送。

所以这个时候我们只需告诉底层我们需要用那个网卡发送就可以了。

struct sockaddr eth;
eth.sa_family = AF_INET;
strcpy(eth.sa_data,"eth0");

ret = sendto(fds,&arp_in,sizeof(struct arp_packet),0,
		 (struct sockaddr *)ð,sizeof(struct sockaddr));
if(ret < 0){
	perror("Send Reqire ARP Packet Fail:");
	goto close_socket;
}
struct arp_packet arp_rc;
socklen_t slen = sizeof(struct sockaddr);
bzero(&arp_rc,sizeof(struct arp_packet));
ret = recvfrom(fds,&arp_rc,sizeof(struct arp_packet),0,
		   (struct sockaddr *)ð,&slen);
if(ret < 0){
	perror("Receive Replay ARP Packet Fail:");
	goto close_socket;
}

....


close_socket;
close(fds);
return (ret > 0 ? 1 : ret);

6、问题到此结束,测试后的结果如下:

-----------------------------Sendto----------------------------
Dest MAC:FF:FF:FF:FF:FF:FF
SRC  MAC:00:22:15:67:F8:16
HW  TYPE:0806
From    :210.42.158.204
To      :210.42.158.212
ARP   OP:0100
-----------------------------recvfrom-------------------------
Dest MAC:00:22:15:67:F8:16
SRC  MAC:00:E0:4C:DC:AA:1E
HW  TYPE:0806
From    :210.42.158.212
To      :210.42.158.204
ARP   OP:0200

至于ARP攻击,我小测试了一把,还是可以的。但是许多电脑有arp防护,或者防火墙,arp攻击时没有用的。

为了社会的安宁,这里我就不把arp攻击的代码贴出来了。

如果有什么问题,请各位留言指点!谢谢!


【linux环境编程】 ARP编程