Qt下通过packet库实现ARP数据包的发送和接收

Qt中暂时据我了解暂时没有对底层网卡操作的类和相关库,这次通过ARP协议写的局域网ip搜索程序都是采用微软的底层网卡操作相关库,此次主要了libpacket.a和libwpcap.a库。操作步骤如下:

(1)      首先可以在添加必要的库文件,可以在官网http://www.winpcap.org/install/default.htm下载最新的WinPcap安装包,并安装,运行基于winpcap的程序需要其支持。

(2)      http://www.winpcap.org/devel.htm 下载开发包,把winpcap开发包中的Include目录下的所有内容拷贝到qt\Qt\4.8.1\mingw\include目录下。

(3)      winpcap开发包中的Lib目录下的Packet.awpcap.a拷贝到\Qt\4.8.1\mingw\lib目录下。但是我感觉那样没有成功,我是拷贝到工程目录下;再在工程头文件中添加库如下代码:

LIBS+=$$PWD/libpacket.a\

$$PWD/libwpcap.a

还有就是要在库文件中添加#include"Packet32.h"头文件(参考网址http://blog.csdn.net/q5707802/article/details/38373929)

(4)      这样相关的库和头文件都准备好了开始程序编写。实现ARP数据包的发送和接收主要为获取本机网卡信息、打开网卡、将封装好的数据包发送出去、接收返回的ARP数据包、数据解析、关闭网卡等步骤。步骤看着不多,但是里面有很多细节实现起来还是比较麻烦。也是费了很大的力气才把程序调试好。下面一步一步来讲解Qt下实现ip搜索的步骤

(5)      获取网卡信息

  在IP搜索程序中主要用到了网卡名称,网卡的MAC地址,IP到是没有用到。这里主要有两种方法实现网卡信息的获取。其一通过Packet32.h中的函数PacketGetAdapterNames()函数获取,这个还没有尝试过;其二通过Qt自带的类QNetworkInterface,这个类可以获取网卡的所有信息,代码实现如下

QList<QNetworkInterface>listMac=netWorkInerface.allInterfaces();

这一句代码就把网卡的所有信息都获取到了,现在只需要通过listMac输出即可,例如

listMac.at(0).hardwareAddress();//获取本机MAC

localMacName=listMac.at(0).name();//获取本机MAC名称

使用起来很方便,这样就获取到了网卡信息。但是这时还要对获取到网卡信息进行转换,网卡MAC要转换成16进制数才能进行下一步的操作。这里要提示的是网卡名称其实可以不用做其它操作就可以在下面的步骤中使用了,但是通过packet库发送ARP数据包在打开网卡时使用NPF方式,所以在获取到网卡名称中后添加如下代码:

localMacName="\\Device\\NPF_"+localMacName;

(6)      打开网卡

打开网卡到是很简答,通过PacketOpenAdapter(网卡名称),如果打开成功返回打开的网卡句柄,如果打开失败返回NULL。这里提示一定要保存次网卡句柄,以便在后面使用

(7)  封装数据包

    这里比较重要的是要把封装的数据包的格式转换为运输层要发送的数据包格式,可以通过htons()函数实现,htons主要功能是将无符号短整型转换成网络字节。

(8)  发送数据包

    这里将用到两个比较重要的结构体

数据结构:_ADAPTER(关于Network Adapter的)

 

typedef struct _ADAPTER

{

HANDLE hFile;                              // 一个打开的NPF driver实例的句柄:

CHAR SymbolicLink[MAX_LINK_NAME_LENGTH];   // 当前打开的网卡的名字:

int NumWrites;                     // 在这块Adapter上,一个数据包被写的次数:

HANDLE ReadEvent;              /* 这块Adapter上的read操作的通知事件。它可以被传递给标准Win32函数(如WaitForSingleObject或者WaitForMultipleObjects),这样可以等待到driver的缓冲区内有数据到来。在同时等待几个事件的GUI程序中,它特别有用。在Windows2000/XP中,函数PacketSetMinToCopy()可以用来设置内核缓冲区中激发本事件的最小数据大小:*/

UINT ReadTimeOut;   // 设置一个时间,到时候,即使没有捕获任何包,read操作也会被释放,ReadEvent也会被触发:

} ADAPTER, *LPADAPTER;

数据结构:_PACKET(关于Packet的)

 

typedef struct _PACKET

{

HANDLE hEvent;          // 向后兼容用的:

OVERLAPPED OverLapped;  // 向后兼容用的:

PVOID Buffer;           // 存放Packets的缓冲区:

UINT Length;            // 缓冲区的大小:

DWORD ulBytesReceived;  // 当前缓冲区中有效的字节数,如,上一次调用PacketReceivePacket()函数接收到的字节数:

BOOLEAN bIoComplete     // 向后兼容用的:

} PACKET, *LPPACKET;

发送代码如下,发送前提是数据包已经封装好了

lpPacket=PacketAllocatePacket();//给PACKET结构指针分配内存

       if(lpPacket==NULL)

       {

           PacketCloseAdapter(adapterHandle);

           returnfalse;

       }

    PacketInitPacket(lpPacket,&broadcastARPcmd,sizeof(broadcastARPcmd));

    if(PacketSetNumWrites(adapterHandle,1)==0)       //每次只发送一个包

       {

           PacketFreePacket(lpPacket);

           PacketCloseAdapter(adapterHandle);

           returnfalse;

       }

    BOOLflag=PacketSendPacket(adapterHandle,lpPacket,true);

    //判断是否发送成功

    if(flag==FALSE)

     {

       returnfalse;

     }

这里面有几个函数PacketAllocatePacket()给PACKET结构指针分配内存,这个函

函数必须要要不然数据发送不出去。

PacketInitPacket(lpPacket,&broadcastARPcmd,sizeof(broadcastARPcmd))其中lpPacket_PACKET结构体指针,broadcastARPcmd为要发送数据包,这个函数必须有,PacketSetNumWrites()设置发送次数可以没有,默认发送一次。最后通过PacketSendPacket(adapterHandle,lpPacket,true)发送数据包,其中adapterHandle为网卡句柄,第二个参数说过了,第三个参数不用说了很明白。

这样数据包就发送出去了。我也是调试了很长时间才调试好的。

(9)解析数据包

前面的步骤完了算是做完了一大半了,解析数据包主要是对ARP广播信号非回应信息解析。这里在winpcap开发包中有参考例子,可以进行参考的。我也是参考例程代码的。这里不在详细介绍。相关参考代码如下:

if(PacketSetHwFilter(adapterHandle,0x00000020)==FALSE)

    {

      qDebug("Warning:unabletosetpromiscuousmode!\n");

      returnfalse;

    }

    if(PacketSetBuff(adapterHandle,500*1024)==false)              //设置网卡缓存大小

        {

           PacketFreePacket(lpPacket);

           PacketCloseAdapter(adapterHandle);

           returnfalse;

       }

    if(PacketSetReadTimeout(adapterHandle,100)==false)  //设置超时时间

       {

           PacketFreePacket(lpPacket);

           PacketCloseAdapter(adapterHandle);

           returnfalse;

       }

    PacketInitPacket(lpPacket,(char*)recBuf,sizeof(recBuf));

  while(readNULLFlag)

  {

       tempresult=PacketReceivePacket(adapterHandle,lpPacket,TRUE);//某个网卡来接受

       if(tempresult==FALSE||lpPacket->ulBytesReceived<1)

           {

                     tempresult=PacketReceivePacket(adapterHandle,lpPacket,TRUE);

                     if(tempresult==FALSE||lpPacket->ulBytesReceived<1)

                     {

                         readNULLFlag=false;

                     }

           }

       receiveDataPacket(lpPacket);

   }

这里说明的是PacketSetHwFilter(adapterHandle,0x00000020)中的0x00000020,这里设置过滤模式为混杂模式,读取网卡上收到的所有信息,在MFC中用的是宏定义,我在QT下查找时没有找到于是在MFC中查找的宏定义的值直接复制过来的。其中宏定义为

#define NDIS_PACKET_TYPE_DIRECTED               0x00000001

#define NDIS_PACKET_TYPE_MULTICAST              0x00000002

#define NDIS_PACKET_TYPE_ALL_MULTICAST          0x00000004

#define NDIS_PACKET_TYPE_BROADCAST              0x00000008

#define NDIS_PACKET_TYPE_SOURCE_ROUTING         0x00000010

#define NDIS_PACKET_TYPE_PROMISCUOUS            0x00000020

#define NDIS_PACKET_TYPE_SMT                    0x00000040

#define NDIS_PACKET_TYPE_ALL_LOCAL              0x00000080

#define NDIS_PACKET_TYPE_GROUP                  0x00001000

#define NDIS_PACKET_TYPE_ALL_FUNCTIONAL         0x00002000

#define NDIS_PACKET_TYPE_FUNCTIONAL             0x00004000

#define NDIS_PACKET_TYPE_MAC_FRAME              0x00008000

#define NDIS_PACKET_TYPE_NO_LOCAL               0x00010000

其它宏定义自己可以网上查看相关说明不在一一说明。这些都设置完成后就可通过PacketReceivePacket()函数接收数据,最后通过receiveDataPacket()函数进行解析,这个函数是自己定义实现的。相关代码在 winpcap 开发包中有参考例子可供参考
  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是关于ARP数据包发送和解析的完整代码: ``` #include <winsock2.h> #include <pcap.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #pragma comment(lib, "wpcap.lib") #pragma comment(lib, "ws2_32.lib") #define MAC_LEN 6 // MAC地址长度为6字节 #define IP_LEN 4 // IP地址长度为4字节 #define ETH_P_ARP 0x0806 // ARP协议类型 #define ETH_P_IP 0x0800 // IP协议类型 #define ETH_HDR_LEN 14 // 以太网头部长度为14字节 #define PACKET_LEN 42 // ARP数据包长度为42字节(包括以太网头部和ARP头部) typedef struct arp_hdr { u_short hardware_type; // 硬件类型 u_short protocol_type; // 协议类型 u_char hardware_len; // MAC地址长度 u_char protocol_len; // IP地址长度 u_short operation; // 操作类型 u_char src_mac[6]; // 源MAC地址 u_char src_ip[4]; // 源IP地址 u_char dest_mac[6]; // 目的MAC地址 u_char dest_ip[4]; // 目的IP地址 } arp_hdr; void send_arp_packet(pcap_t* adhandle, u_char* src_mac, u_char* src_ip, u_char* dest_mac, u_char* dest_ip, int op) { arp_hdr arp; memset(&arp, 0, sizeof(arp)); arp.hardware_type = htons(ARPHRD_ETHER); // 硬件类型为以太网 arp.protocol_type = htons(ETH_P_IP); // 协议类型为IP arp.hardware_len = MAC_LEN; // MAC地址长度为6 arp.protocol_len = IP_LEN; // IP地址长度为4 arp.operation = htons(op); // 操作类型为请求或响应 memcpy(arp.src_mac, src_mac, MAC_LEN); // 源MAC地址 memcpy(arp.src_ip, src_ip, IP_LEN); // 源IP地址 memcpy(arp.dest_mac, dest_mac, MAC_LEN); // 目的MAC地址 memcpy(arp.dest_ip, dest_ip, IP_LEN); // 目的IP地址 u_char packet[PACKET_LEN]; memset(packet, 0, sizeof(packet)); memcpy(packet, dest_mac, MAC_LEN); memcpy(packet + MAC_LEN, src_mac, MAC_LEN); packet[12] = ETH_P_ARP / 256; packet[13] = ETH_P_ARP % 256; memcpy(packet + ETH_HDR_LEN, &arp, sizeof(arp)); if (pcap_sendpacket(adhandle, packet, PACKET_LEN) != 0) { printf("Error sending ARP packet: %s\n", pcap_geterr(adhandle)); } } void parse_arp_packet(const u_char* packet) { arp_hdr* arp = (arp_hdr*)(packet + ETH_HDR_LEN); // 获取ARP头部信息 printf("ARP packet:\n"); printf(" Source MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", arp->src_mac[0], arp->src_mac[1], arp->src_mac[2], arp->src_mac[3], arp->src_mac[4], arp->src_mac[5]); printf(" Source IP address: %d.%d.%d.%d\n", arp->src_ip[0], arp->src_ip[1], arp->src_ip[2], arp->src_ip[3]); printf(" Destination MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n", arp->dest_mac[0], arp->dest_mac[1], arp->dest_mac[2], arp->dest_mac[3], arp->dest_mac[4], arp->dest_mac[5]); printf(" Destination IP address: %d.%d.%d.%d\n", arp->dest_ip[0], arp->dest_ip[1], arp->dest_ip[2], arp->dest_ip[3]); } int main() { pcap_if_t* alldevs; pcap_if_t* d; int i = 0; int inum; pcap_t* adhandle; char errbuf[PCAP_ERRBUF_SIZE]; struct bpf_program fcode; bpf_u_int32 netmask; bpf_u_int32 netaddr; // 获取本地网卡列表 if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) { printf("Error in pcap_findalldevs_ex: %s\n", errbuf); return 1; } // 输出网卡列表 for (d = alldevs; d != NULL; d = d->next) { printf("%d. %s\n", ++i, d->name); if (d->description) printf(" (%s)\n", d->description); else printf(" (No description available)\n"); } // 选择要抓包的网卡 printf("Enter the interface number (1-%d):", i); scanf_s("%d", &inum); if (inum < 1 || inum > i) { printf("Invalid interface number\n"); pcap_freealldevs(alldevs); return 1; } // 定位到选择的网卡 for (d = alldevs, i = 0; i < inum - 1; d = d->next, i++); // 打开网卡 if ((adhandle = pcap_open(d->name, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf)) == NULL) { printf("Unable to open the adapter. %s is not supported by WinPcap\n", d->name); pcap_freealldevs(alldevs); return 1; } // 获取网卡的子网掩码 if (d->addresses != NULL) { netmask = ((struct sockaddr_in*)(d->addresses->netmask))->sin_addr.S_un.S_addr; } else { netmask = 0xffffff; } // 编译过滤器 if (pcap_compile(adhandle, &fcode, "arp", 1, netmask) < 0) { printf("Unable to compile the packet filter. Check the syntax.\n"); pcap_freealldevs(alldevs); return 1; } // 设置过滤器 if (pcap_setfilter(adhandle, &fcode) < 0) { printf("Error setting the filter.\n"); pcap_freealldevs(alldevs); return 1; } // 发送ARP请求 u_char src_mac[6] = { 0x00, 0x0c, 0x29, 0x7b, 0x63, 0x15 }; // 本机MAC地址 u_char src_ip[4] = { 192, 168, 1, 100 }; // 本机IP地址 u_char dest_mac[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; // 广播地址 u_char dest_ip[4] = { 192, 168, 1, 1 }; // 目标IP地址 send_arp_packet(adhandle, src_mac, src_ip, dest_mac, dest_ip, ARPOP_REQUEST); // 接收ARP响应 struct pcap_pkthdr* header; const u_char* packet; int res; while ((res = pcap_next_ex(adhandle, &header, &packet)) >= 0) { if (res == 0) continue; parse_arp_packet(packet); } pcap_close(adhandle); pcap_freealldevs(alldevs); return 0; } ``` 注意:在运行代码时,需要安装WinPcap,并在项目属性中添加对该的引用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值