一个简单的ARP实例。
先声明,本文是基于于一篇来自邹鑫的专栏的文章,地址如下:
http://blog.csdn.net/zouxinfox/archive/2008/10/25/3141323.aspx
山人根据自己的实际情况稍微改动一下代码,不过山人纯粹是用来学习的,没有干坏事。
下面的图片依然来自《TCP/IP详解》一书。
在前篇文章中,我们知道,一个ARP包(分组)除了以太网头部和ARP请求/应答字段外,还有18个字节的填充字段(山人根据PAD一词猜的),这样一个ARP包就有14+28+18=60字节(不相信的话,大家可以慢慢数一数每个字段的字节数)。下面我们将会看到,代码中就是分配了60字节的空间。
ARP头部结构体是这样的:
struct arphdr
{
unsigned short int ar_hrd; /* Format of hardware address. */
unsigned short int ar_pro; /* Format of protocol address. */
unsigned char ar_hln; /* Length of hardware address. */
unsigned char ar_pln; /* Length of protocol address. */
unsigned short int ar_op; /* ARP opcode (command). */
};
(来自<net/if_arp.h>)
它其实就是“28字节ARP请求/应答”中前面的8个字节。
第一个成员表示硬件地址的格式,就是图中的“硬件类型”,它有如下这些:
#define ARPHRD_NETROM 0 /* From KA9Q: NET/ROM pseudo. */
#define ARPHRD_ETHER 1 /* Ethernet 10/100Mbps. */
#define ARPHRD_EETHER 2 /* Experimental Ethernet. */
#define ARPHRD_AX25 3 /* AX.25 Level 2. */
#define ARPHRD_PRONET 4 /* PROnet token ring. */
#define ARPHRD_CHAOS 5 /* Chaosnet. */
#define ARPHRD_IEEE802 6 /* IEEE 802.2 Ethernet/TR/TB. */
#define ARPHRD_ARCNET 7 /* ARCnet. */
#define ARPHRD_APPLETLK 8 /* APPLEtalk. */
#define ARPHRD_DLCI 15 /* Frame Relay DLCI. */
#define ARPHRD_ATM 19 /* ATM. */
#define ARPHRD_METRICOM 23 /* Metricom STRIP (new IANA id). */
#define ARPHRD_IEEE1394 24 /* IEEE 1394 IPv4 - RFC 2734. */
#define ARPHRD_EUI64 27 /* EUI-64. */
#define ARPHRD_INFINIBAND 32 /* InfiniBand. */
(同上)
其实第2个是以太网,在代码中使用它。
第二个成员表示协议类型,这里是ARP协议(一句必要的废话)。
最后一个成员表示操作类型,在<net/if_arp.h>是这样的:
/* ARP protocol opcodes. */
#define ARPOP_REQUEST 1 /* ARP request. */
#define ARPOP_REPLY 2 /* ARP reply. */
#define ARPOP_RREQUEST 3 /* RARP request. */
#define ARPOP_RREPLY 4 /* RARP reply. */
#define ARPOP_InREQUEST 8 /* InARP request. */
#define ARPOP_InREPLY 9 /* InARP reply. */
#define ARPOP_NAK 10 /* (ATM)ARP NAK. */
其中就是ARP请求,ARP应答。
但是,光有这个结构体还是不行的,我们需要的是ARP包。
这个结构体是这样的:
struct ether_arp {
struct arphdr ea_hdr; /* fixed-size header */
u_int8_t arp_sha[ETH_ALEN]; /* sender hardware address */
u_int8_t arp_spa[4]; /* sender protocol address */
u_int8_t arp_tha[ETH_ALEN]; /* target hardware address */
u_int8_t 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
(来自<netinet/if_ether.h>)
它包含了ARP头部那个结构体,所以在程序中直接使用这个就可以了。这个结构体就是根据上面那个图来定义,没啥好说的。不过,这里要注意一下人家代码的技巧。它在下面定义一些宏,一开始我很不理解,后面才发现这种用法很好,值得学习。比如我们要赋值给ARP头部的op,可以这样
arp->ea_hdr->ar_op
也可以这样
arp->arp_op
(假设arp是ether_arp结构体的指针)。代码中就是使用后面那种方式的,比较简便。
废话讲完,该上菜了。下面给出代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <net/ethernet.h> #include <netinet/if_ether.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #define SRC_IP "192.168.184.100" #define TARGET_IP "192.168.184.1" short SRC_MAC[]={0x6c,0x61,0x74,0x65,0x6c,0x65}; /* latele */ short TARGET_MAC[]={0x00,0x50,0x56,0xc0,0x00,0x08}; void send_arp_reply(void) { struct ether_header *eth_hdr; struct ether_arp *arp; char datagram[60]; eth_hdr=(struct ether_header *)datagram; memset(datagram,0,sizeof(datagram)); //set the ethernet header eth_hdr->ether_dhost[0]=TARGET_MAC[0]; eth_hdr->ether_dhost[1]=TARGET_MAC[1]; eth_hdr->ether_dhost[2]=TARGET_MAC[2]; eth_hdr->ether_dhost[3]=TARGET_MAC[3]; eth_hdr->ether_dhost[4]=TARGET_MAC[4]; eth_hdr->ether_dhost[5]=TARGET_MAC[5]; eth_hdr->ether_shost[0]=SRC_MAC[0]; eth_hdr->ether_shost[1]=SRC_MAC[1]; eth_hdr->ether_shost[2]=SRC_MAC[2]; eth_hdr->ether_shost[3]=SRC_MAC[3]; eth_hdr->ether_shost[4]=SRC_MAC[4]; eth_hdr->ether_shost[5]=SRC_MAC[5]; eth_hdr->ether_type=htons(ETHERTYPE_ARP); // 0x0806 //set the arp header arp=(struct ether_arp*)(datagram+sizeof(struct ether_header)); arp->arp_hrd=htons(ARPHRD_ETHER); arp->arp_pro=htons(ETHERTYPE_IP); arp->arp_hln=6; arp->arp_pln=4; arp->arp_op=htons(ARPOP_REPLY); //arp body //sender MAC and IP memcpy((void*)arp->arp_sha,(void*)eth_hdr->ether_shost,6); struct in_addr inadd_sender; inet_aton(SRC_IP,&inadd_sender); memcpy((void*)arp->arp_spa,(void*)&inadd_sender,4); //target MAC and IP memcpy((void*)arp->arp_tha,(void*)eth_hdr->ether_dhost,6); struct in_addr inadd_target; inet_aton(TARGET_IP,&inadd_target); memcpy((void *)arp->arp_tpa,(void*)&inadd_target,4); //establish socket int fd=socket(AF_INET,SOCK_PACKET,htons(ETH_P_ARP)); if(fd<0) { perror("socket"); exit(-1); } struct sockaddr sa; strcpy(sa.sa_data,"eth7"); sendto(fd,datagram,sizeof(datagram),0,&sa,sizeof(sa)); close(fd); } int main(void) { while(1) { printf("send arp reply.../n"); send_arp_reply(); sleep(30); } return 0; }
再次声明,代码非原创,链接地址见文章开头处。
在虚拟机Linux中测试,在物理机Windows系统中查看结果,网络连接为NAT8。
Linux系统,eth7,IP地址192.168.184.1.100,代码中的物理地址是瞎写的。
运行程序后,在Windows系统的的cmd命令行下输入arp -a,可以看到Linux系统中的MAC地址已经变为代码中的地址了。
限于环境及个人能力,不敢作太多测试。权当学习例子而已。