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

来源:互联网 发布:软件项目需求管理 编辑:程序博客网 时间:2024/05/18 14:44


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.htmlif(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.htmlbreak; 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地址以及协议。因为是广播,所以全是FFwhile (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);//一定得到返回信息就会打印。}}


原创粉丝点击