书:计算机网络高级软件编程技术(P46) 之 基础训练:使用Arp协议获得本地局域网内在线主机MAC地址


Section I Problem Specification

本次实验是用C/C++写一个使用Arp协议获得本地局域网内在线主机MAC地址的程序。目前使用广泛的局域网都是基于802.3协议,在该局域网内的主机都是通过网卡间的MAC地址来通信。在此之前,一直认为是利用ip地址来保障通信。通过这节课,我明白了:在一个局域网内,一个ip地址对应一个mac地址,当我想向某一个ip地址发送数据时,其实是提取了本机的mac地址,然后将数据发送到有相应mac地址的网卡。我们可以利用命令行:arp –a查看win7存在本地ip与mac地址的对应的列表。也可以利用arp –d删除。

既然通信是依靠mac地址,那么有一些知道ip,却不知道mac地址的情况下是如何通信的呢。这里就用arp协议,过程是:本机会向局域网内所有发送一个含有自己的mac地址的arp的请求包,这个包的意思就是:谁有ip xxx.xxx.xxx.xxx,请把你的mac地址发送给我。所有本地局域网的主机收到这个之后,就看自己的ip是不是这个,是的话,就发一个含有自己mac地址的arp应答包给请求的mac地址。

Section II Solution Method and Design

具体来来说:我要做的就是自行构造一个arp请求包,然后发送出去,再接住返回给我arp应答包。我再将其解析,打印出mac地址即可。请看下图:


该图清晰的展示了这个frame的具体构造。结合下图的例子,让我能够轻松构建arp请求包:


那么,在此包的构造我想已经讲清楚了。在构造包的时候,会需要自己的mac地址,那么我使用的方法是利用iphlpapi.h这个包里的一个api :GetAdaptersInfo():获取设备的信息,其中就有mac地址。

 

接着是如何发包和接包,其实发包和接包都是调用了winpcap里面的api。调用api的关键就是要搭配环境的问题,我也是参考了别人的做法,

博客地址:http://blog.csdn.net/zhbzljxw/article/details/6101656

其中需要的头文件,我已经上传到我的云盘了。名字是:WpdPack(vs2010使用的winpcap的api.rar

实际调用api的话,就不那么复杂了,只要认真的读懂了函数。调用起来应该不难,在我的代码中,我主要调用了:

pcap_findalldevs():找到所有设备

pcap_open():打开某个设备

pcap_sendpacket():发包

pcap_next_ex():接包

这里粗略解说,源码内我写了详细的注释。


本次实验的流程图如下页所示:


Section III Test Cases and Results Analysis

查询单个ip:


查询本局域网内全部的mac地址:


Section IV Conclusion

本次实验让我初步掌握了arp协议的原理以及arp包的构造。

成功搭配了使用winpcap的api的环境。成功调用了相关api。

学会使用了wireshark来抓包,分析数据,协助编程。

在实际的编码过程中,还有以下收获:

1.  原来在构造arp请求包的时候,发送方(我的)的ip不填写也可以。

仔细思考也觉得确实可以,因为本来就是依据mac地址来通信。

2.  如果我的机子上有两个网卡,就有两个mac地址,那么请求包内的mac地址填哪个的都无所谓,都可以收到arp回应包。按理:应该是哪个网卡接入局域网,哪个填写哪个网卡的mac地址。

目前这个问题我还没有参悟的特别透。

老师解答:发包时,mac地址随便填一个都行。我实验了一下,只要mac地址把不是广播地址。我测试的时候发出方的mac地址全部填0xaa。

结果还是顺利收到回应包了。我认为这肯定和网络机制有关,会不会对方机子就认为我的mac地址就0xaa了。反正因为是我发出的,所以肯定会还给我,不管我的mac地址是多少。然后我程序一判断,是不是回应包,是不是就打印出来了。

记在这里追问老师。


因为虽然我乱填了了一个mac地址,但是交换机会去查的时候,发现没有这个乱填的。交换机就会用flooding算法,算是广播一个返回的arp。我的网卡又设置成了混杂模式,所以就直接把这个接住了。然后返回给我了。

此外:
主机联外网的的流程:
我想联google,先通过本机的DNS查到google的ip
再利用google的ip,根据本机的ip和mask码可以“与”出一个东西,google的ip和我本机的mask码也可以“与”出一个东西。这两个一比,就知道google的这个ip是外网的。那么就直接把这个包丢给网关(即路由器),然路由器往外发。

3.  第一页的图里显示构造的包应该是一个64个字节的数组,但是我在使用wireshark抓包的时候发现,arp的包都是60字节的数组(如下图 )。少了FCS的后4个字节。


还没参透为什么。计算机网络的基础差,需要看一下基础的书。

老师的解答:这个CRC部分是底层的硬件替我们完成了。发和收的时候硬件都替我们完成添加CRC和验证数据的工作。

有待改进的地方:本次使用结构体使用的不是特别彻底,对c语言的使用的能力不佳,又待加强,很多地方都是有数字的方式来处理。

 

 

Section V References

计算机网络高级软件编程技术 第二章Ethernet帧结构解析程序

 

在vs2010搭配使用winpcap的开发环境:

http://blog.csdn.net/zhbzljxw/article/details/6101656

 

 

winpcap的api的使用:

http://blog.chinaunix.net/uid-28698407-id-3843171.html

http://blog.sina.com.cn/s/blog_48fa680e010002xy.html

 

 

wireshark使用:

http://wenku.baidu.com/view/15f82868011ca300a6c390cf.html

谢谢几个大哥的帮助! 

Section VI Appendix

全部源码,但主意一定要搭配环境

#include "winsock2.h" 
#include "windows.h" //测试过,可以删,可能因为我之前要获得ip的时候调用了一些函数吧。用处包含了其他Windows头文件 :定义了Windows的所有资料型态、函数调用、资料结构和常数识别字,
#include "iphlpapi.h"  
#include "pcap.h" 

#pragma comment(lib, "Iphlpapi.lib")
#pragma comment(lib,"ws2_32.lib")//测试过,可以删,提供了对以下网络相关API的支持,若使用其中的API,则应该将ws2_32.lib加入工程  
#pragma comment(lib, "wpcap.lib ")

typedef struct Frame{
	unsigned char destinationAddress[6];//疑问:原来我填的本机mac地址无论是无线的还是有线的。只要是本机的就行。为什么
	unsigned char sourceAddress[6];
	unsigned short ethernetType;
	unsigned short hardwareType;
	unsigned short protocalType;
	unsigned char hardwareSize;
	unsigned char protocalSIze;
	unsigned short Opcode;
	unsigned char senderHardwareAddress[6];
	unsigned char senderIP[4];//不用填也可以。
	unsigned char targetHardwareAddress[6];
	unsigned char targetIP[4];
};
Frame senderFrame;
int count=0;//用于在for循环中计数
//int DeviceCount=0;//设备的个数

void GetLocalMacAndSendArp(pcap_t* curDev){
	PIP_ADAPTER_INFO pAdapter = 0;  
	ULONG uBuf = 0;  
	DWORD dwRet;  
	dwRet = GetAdaptersInfo(pAdapter,&uBuf); 
	if(dwRet == ERROR_BUFFER_OVERFLOW)  
	{  
		pAdapter = (PIP_ADAPTER_INFO)GlobalAlloc(GPTR,uBuf);  
		dwRet = GetAdaptersInfo(pAdapter,&uBuf);

		if (dwRet == ERROR_SUCCESS)  {
				for (count=0;count<6;count++)
				{
					senderFrame.sourceAddress[count]=pAdapter->Address[count];
					senderFrame.senderHardwareAddress[count]=pAdapter->Address[count];
				}

				unsigned char frame[60];
				if (senderFrame.targetIP[3]==255)
				{
					for (senderFrame.targetIP[3]=1;senderFrame.targetIP[3]<255;senderFrame.targetIP[3]++)
					{
						memset(&frame,0,sizeof(frame));
						memcpy(&frame,&senderFrame,sizeof(senderFrame));
						pcap_sendpacket(curDev,frame,sizeof(frame));
					}
				}else{
					memset(&frame,0,sizeof(frame));
					memcpy(&frame,&senderFrame,sizeof(senderFrame));
					pcap_sendpacket(curDev,frame,sizeof(frame));
				}
		}  
	}
	//GlobalFree(pAdapter);  
}

pcap_t* PrepareAdapterDev()//打开第i个设备 
{	printf("请选择您的上网设备:\n");
	int c=1; 
	pcap_if_t* Dev,*allDevs; 
	pcap_t* curDev; 
	char errBuf[PCAP_ERRBUF_SIZE]; //存放错误信息的缓冲.下面那个pcap_findalldevs 函数用来存放错误信息。http://blog.chinaunix.net/uid-28698407-id-3843171.html
	if(pcap_findalldevs(&allDevs,errBuf)!=-1)//列举所有设备 ,这里用来获取网络适配器信息的函数.pcap_findalldevs() returns 0 on success and -1 on failure.当errBuf满了会返回错误信息。
	{ 	int i=1;//记录选择的设备的号码
	for(Dev=allDevs;Dev;Dev=Dev->next)
	{	
		if (Dev->description) {
		printf("%d. %s\n", i++, Dev->description);
		//DeviceCount++;
		}else{
			printf(" 抱歉,未能找到详尽的描述\n");
		}
	}
	scanf("%d",&i);
	for(Dev=allDevs;Dev!=NULL;Dev=Dev->next,c++) //从这里来看,执行了pcap_findalldevs之后,只是有一个list,然后alldevs会指向第一个。然后我就一个一个向下传。就可以找到第一个了。
	{ 
		if(c==i)//我们需要的设备 
		{		//下一个pcap_open函数,就返打开一个设备。并返回。1参数是打开哪一个。2参数是需要保留的数据包的长度。
			//3参数保存一些由于抓包需要的标志。 3参数PCAP_OPENFLAG_PROMISCUOUS表示是否进入混杂模式(是指一台机器能够接收所有经过它的数据流,而不论其目的地址是否是他。)。1就是进入,0就是不进入。其是值还可以为2和4.具体看网页
			//4参数被用来设置在遇到一个数据包的时候读操作不必立即返回,而是等待一段时间。单位毫秒
			//5参数。假如不是远程抓包,该指针被设置为NULL。
			//6参数:errbuf:一个指向用户申请的缓冲区的指针,存放当该函数出错时的错误信息。
			if((curDev=pcap_open(Dev->name,65535,PCAP_OPENFLAG_PROMISCUOUS,1000,NULL,errBuf))==NULL)//打开设备 http://blog.sina.com.cn/s/blog_48fa680e010002xy.html
				break; 
			pcap_freealldevs(allDevs);// pcap_freealldevs()  is  used  to  free  a  list allocated by pcap_find_alldevs().看到有些博客介绍说好像不释放掉会出点问题。
			return curDev; 
		} 
	} 
	} 
	pcap_freealldevs(allDevs); 
	return NULL; 
}

void initTargetMACandProtocal() 
{
	for(count;count<6;count++){
		senderFrame.destinationAddress[count]=0xff;
		senderFrame.targetHardwareAddress[count]=00;
	}
	senderFrame.ethernetType=htons(0x0806);
	senderFrame.hardwareType=htons(0x0001);
	senderFrame.protocalType=htons(0x0800);
	senderFrame.hardwareSize=6;
	senderFrame.protocalSIze=4;
	senderFrame.Opcode=htons(0x0001);
}

void printTargetMAC( pcap_t* curDev ) 
{
	int j=0;//其实只是一个用于超时的量。
	pcap_pkthdr* hdr; 
	const u_char *pkt_datas;
	printf("\n结果:\tIP:\t\tMAC:\t\t Ctrl+C 可中止查询\n");
	while(true){
		j++;
		pcap_next_ex(curDev,&hdr,&pkt_datas); 
		unsigned char *data=NULL;
		data=(unsigned char*)pkt_datas;
		unsigned char GetFrame[60];
		int i=0;
		for(i;i<60;i++){
			GetFrame[i]=*data;
			*data=*data+1;
		}
		//下面这个方式显得是有点二,不过确实有效,我是看着wireshark一个一个数着对应的。
		if(data[12]==0x08&&data[13]==0x06&&data[20]==0x00&&data[21]==0x02&&(data[31]==senderFrame.targetIP[3]||senderFrame.targetIP[3]==255))
		{	
			printf("\t%d.%d.%d.%d\t",data[28],data[29],data[30],data[31]);
			printf("%02X-%02X-%02X-%02X-%02X-%02X\n",data[6],data[7],data[8],data[9],data[10],data[11]);
			if(senderFrame.targetIP[3]!=255){
				printf("-----------------------------------------------------\n\n");
				break;
			}	//是255的就表示对所有在线的ip都发一个。	所以就不能停下来。
		}
		if (j>1000)
		{	if (senderFrame.targetIP[3]!=255){printf("请求包早已发出,至今没收到回应:也许您输入的ip并不在线!!!\n\n");j=0;}//莫名其妙,非要加这个一句count=0 
			    else{printf("查询结束!!!\n\n");}
			break;
		}
	}
}

void main(){
	
	pcap_t* curDev=PrepareAdapterDev();//初始化设备
	
	initTargetMACandProtocal();//初始化目标mac地址以及协议。因为是广播,所以全是FF
	while (1)
	{
		//请用户输入目标ip的地址
		printf("查询方式:\n1.直接输入格式为XXX.XXX.XXX.XXX则可查询单个活动主机\n2.直接输入XXX.XXX.XXX.255则可查询本局域网的全部在线主机\n3.只输入0则终止程序\n");
		scanf("%d.%d.%d.%d", 
			&senderFrame.targetIP[0], 
			&senderFrame.targetIP[1], 
			&senderFrame.targetIP[2],
			&senderFrame.targetIP[3]);
		if(senderFrame.targetIP[0]==0){
			break;
		}
		printf("您输入的IP地址为:%d.%d.%d.%d\n", 
			senderFrame.targetIP[0], 
			senderFrame.targetIP[1], 
			senderFrame.targetIP[2],
			senderFrame.targetIP[3]);

		GetLocalMacAndSendArp(curDev);//初始化发送主机的物理地址和本机IP。

		printTargetMAC(curDev);//一定得到返回信息就会打印。

	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值