源代码链接:https://download.csdn.net/download/chd_lcj/10891420
(不知道为什么积分涨到4积分了。。。原先只是单纯的想分享下的,结果最低只能设置1积分)
//网络编程学习经验记录贴
WinPcap 是由伯克利分组捕获库派生而来的分组捕获库,它是在Windows 操作平台上来实现对底层包的截取过滤。
WinPcap 为用户级的数据包提供了Windows 下的一个平台。
WinPcap 是 BPF 模型和 Libpcap 函数库在 Windows 平台下网络数据包捕获和网络状态分析的一种体系结构,这个体系结构是由一个核心的包过滤驱动程序,一个底层的动态连接库 Packet.dll 和一个高层的独立于系统的函数库 Libpcap 组成。底层的包捕获驱动程序实际为一个协议网络驱动程序,通过对 NDIS 中函数的调用为 Win95、Win98、WinNT、和 Win2000 提供一类似于 UNIX 系统下 Berkeley Packet Filter 的捕获和发送原始数据包的能力。Packet.dll 是对这个 BPF 驱动程序进行访问的 API 接口,同时它有一套符合 Libpcap 接口(UNIX 下的捕获函数库)的函数库。WinPcap的结构图如图1。
WinPcap 包括三个部分:
第一个模块NPF(Netgroup Packet Filter),是一个虚拟设备驱动程序文件。它的功能是过滤数据包,并把这些数据包原封不动地传给用户态模块,这个过程中包括了一些操作系统特有的代码。
第二个模块packet.dll为win32平台提供了一个公共的接口。不同版本的Windows系统都有自己的内核模块和用户层模块。Packet.dll用于解决这些不同。调用Packet.dll的程序可以运行在不同版本的Windows平台上,而无需重新编译。
第三个模块 Wpcap.dll是不依赖于操作系统的。它提供了更加高层、抽象的函数。
packet.dll和Wpcap.dll:packet.dll直接映射了内核的调用。 Wpcap.dll提供了更加友好、功能更加强大的函数调用。WinPcap的优势提供了一套标准的抓包接口,与libpcap兼容,可使得原来许多UNIX平台下的网络分析工具快速移植过来便于开发各种网络分析工具,充分考虑了各种性能和效率的优化,包括对于NPF内核层次上的过滤器支持,支持内核态的统计模式,提供了发送数据包的能力。
利用winpcap进行网络数据包的捕获和过滤的设计步骤
1)打开网卡,并设为混杂模式。
2)回调函数 Network Tap 在得到监听命令后,从网络设备驱动程序处收集数据包把监听到的数据包负责传送给过滤程序。
3)当 Packet filter 监听到有数据包到达时,NDIS 中间驱动程序首先调用分组驱动程序,该程序将数据传递给每一个参与进程的分组过滤程序。
4)然后由 Packet filter 过滤程序决定哪些数据包应该丢弃,哪些数据包应该接收,是否需要将接收到的数据拷贝到相应的应用程序。
5)通过分组过滤器后,将数据未过滤掉的数据包提交给核心缓冲区。然后等待系统缓冲区满后,再将数据包拷贝到用户缓冲区。监听程序可以直接从用户缓冲区中读取捕获的数据包。
6)关闭网卡。
以太网(Ethernet)具有共享介质的特征,信息是以明文的形式在网络上传输,当网络适配器设置为监听模式(混杂模式,Promiscuous)时,由于采用以太网广播信道争用的方式,使得监听系统与正常通信的网络能够并联连接,并可以捕获任何一个在同一冲突域上传输的数据包。IEEE802.3 标准的以太网采用的是持续 CSMA 的方式,正是由于以太网采用这种广播信道争用的方式,使得各个站点可以获得其他站点发送的数据。运用这一原理使信息捕获系统能够拦截的我们所要的信息,这是捕获数据包的物理基础。
以太网是一种总线型的网络,从逻辑上来看是由一条总线和多个连接在总线上的站点所组成各个站点采用上面提到的 CSMA/CD 协议进行信道的争用和共享。每个站点(这里特指计算机通过的接口卡)网卡来实现这种功能。网卡主要的工作是完成对于总线当前状态的探测,确定是否进行数据的传送,判断每个物理数据帧目的地是否为本站地址,如果不匹配,则说明不是发送到本站的而将它丢弃。如果是的话,接收该数据帧,进行物理数据帧的 CRC 校验,然后将数据帧提交给LLC 子层。
网卡具有如下的几种工作模式:
1) 广播模式(Broad Cast Model):它的物理地址(MAC)地址是 0Xffffff 的帧为广播帧,工作在广播模式的网卡接收广播帧。
2)多播传送(MultiCast Model):多播传送地址作为目的物理地址的帧可以被组内的其它主机同时接收,而组外主机却接收不到。但是,如果将网卡设置为多播传送模式,它可以接收所有的多播传送帧,而不论它是不是组内成员。
3)直接模式(Direct Model):工作在直接模式下的网卡只接收目地址是自己 Mac地址的帧。
4)混杂模式(Promiscuous Model):工作在混杂模式下的网卡接收所有的流过网卡的帧,信包捕获程序就是在这种模式下运行的。
网卡的缺省工作模式包含广播模式和直接模式,即它只接收广播帧和发给自己的帧。如果采用混杂模式,一个站点的网卡将接受同一网络内所有站点所发送的数据包这样就可以到达对于网络信息监视捕获的目的。
ICMP IGMP
6 TCP
17 UDP
88 IGRP
89 OSPF
(1)逻辑地址:(工作在网络层,网络级)也称为IP地址,具有特征 ① 全局唯一性;② 使用软件来实现网络中地址管理;③ 占32位,4字节;
(2) 物理地址:也称为硬件地址、链路地址或MAC地址,(工作在网络接口层)具有特征:① 本地范围唯一性;② 使用硬件实现(路由器、计算机有设置MAC地址的位置);③ 占48位,12字节,16进制表示!例如:74-E5-0B-35-60-16 :0111 0100-1110 0101-0000 1011-0011 0101-0110 0000-0001 0110。
运行程序,按提示输入要选择的网卡序列,再次输入需要不活的IP数据包的个数,然后程序自动运行捕获。
捕获后开始解析,从数据链路层开始解析,
1) 如果网络层协议是IP协议,则开始解析网络层IP数据包。
2) 如果运输层协议是TCP协议则解析运输层TCP数据包。
3) 如果网络层协议是APP协议,则不在进一步解析网络层数据包。
4) 如果运输层协议是UDP协议,则不在进一步解析运输层数据包。
#include
#include
#include "pcap.h"
#include "stdio.h"
#include
#include
#include //文件的输入输出;
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"wpcap.lib")
using namespace std;
/*下边是以太网的协议格式 */
struct ethernet_header
{
u_int8_t ether_dhost[6]; /*目的以太地址*/
u_int8_t ether_shost[6]; /*源以太网地址*/
u_int16_t ether_type; /*以太网类型*/
};
/*ip地址格式*/
typedef u_int32_t in_addr_t;
struct ip_header
{
#ifdef WORKS_BIGENDIAN
u_int8_t ip_version:4, /*version:4*/
ip_header_length:4; /*IP协议首部长度Header Length*/
#else
u_int8_t ip_header_length:4,
ip_version:4;
#endif
u_int8_t ip_tos; /*服务类型Differentiated Services Field*/
u_int16_t ip_length; /*总长度Total Length*/
u_int16_t ip_id; /*标识identification*/
u_int16_t ip_off; /*片偏移*/
u_int8_t ip_ttl; /*生存时间Time To Live*/
u_int8_t ip_protocol; /*协议类型(TCP或者UDP协议)*/
u_int16_t ip_checksum; /*首部检验和*/
struct in_addr ip_source_address; /*源IP*/
struct in_addr ip_destination_address; /*目的IP*/
};
/*关于tcp头部的定义*/
struct tcp_header
{
u_int16_t tcp_source_port; //源端口号
u_int16_t tcp_destination_port; //目的端口号
u_int32_t tcp_acknowledgement; //序号
u_int32_t tcp_ack; //确认号字段
#ifdef WORDS_BIGENDIAN
u_int8_t tcp_offset:4 ,
tcp_reserved:4;
#else
u_int8_t tcp_reserved:4,
tcp_offset:4;
#endif
u_int8_t tcp_flags;
u_int16_t tcp_windows; //窗口字段
u_int16_t tcp_checksum; //检验和
u_int16_t tcp_urgent_pointer; //紧急指针字段
};
/*下边实现tcp数据包分析的函数定义tcp_protocol_packet_callback*/
void tcp_protocol_packet_callback(u_char *argument,const struct pcap_pkthdr*
packet_header,const u_char* packet_content)
{
struct tcp_header *tcp_protocol ; /*tcp协议变量*/
u_char flags; /*标记*/
int header_length; /*头长度*/
u_short source_port; /*源端口*/
u_short destination_port; /*目的端口*/
u_short windows; /*窗口大小*/
u_short urgent_pointer; /*紧急指针*/
u_int sequence; /*序列号*/
u_int acknowledgement; /*确认号*/
u_int16_t checksum; /*检验和*/
tcp_protocol=(struct tcp_header *) (packet_content+14+20); /*获得tcp首部内容*/
source_port =ntohs(tcp_protocol->tcp_source_port); /*获得源端口号*/
destination_port =ntohs(tcp_protocol->tcp_destination_port); /*获得目的端口号*/
header_length =tcp_protocol->tcp_offset *4; /*获得首部长度*/
sequence =ntohl(tcp_protocol->tcp_acknowledgement); /*获得序列号*/
acknowledgement =ntohl(tcp_protocol->tcp_ack);
windows = ntohs(tcp_protocol->tcp_windows);
urgent_pointer = ntohs(tcp_protocol->tcp_urgent_pointer);
flags = tcp_protocol->tcp_flags;
checksum =ntohs (tcp_protocol->tcp_checksum);
printf("\n========== 运输层(TCP协议) ==========\n");
printf("源端口:\t %d\n",source_port);
printf("目的端口:\t %d\n",destination_port);
int min= ( destination_port tcp_reserved);
printf("控制位:");
if (flags & 0x08) printf("\t【推送 PSH】");
if (flags & 0x10) printf("\t【确认 ACK】 ");
if (flags & 0x02) printf("\t【同步 SYN】");
if (flags & 0x20) printf("\t【紧急 URG】");
if (flags & 0x01) printf("\t【终止 FIN】");
if (flags & 0x04) printf("\t【复位 RST】");
printf("\n");
printf("窗口大小 :\t%d \n",windows);
printf("检验和 :\t%d\n",checksum);
printf("紧急指针字段 :\t%d\n",urgent_pointer);
}
/*下边实现IP数据包分析的函数定义ethernet_protocol_packet_callback*/
void ip_protocol_packet_callback(u_char *argument,const struct pcap_pkthdr*
packet_header,const u_char* packet_content)
{
struct ip_header *ip_protocol; /*ip协议变量*/
u_int header_length; /*长度*/
u_int offset; /*片偏移*/
u_char tos; /*服务类型*/
u_int16_t checksum; /*首部检验和*/
ip_protocol=(struct ip_header*) (packet_content+14); /*获得ip数据包的内容去掉以太头部*/
checksum=ntohs(ip_protocol->ip_checksum); /*获得校验和*/
header_length=ip_protocol->ip_header_length*4; /*获得长度*/
tos=ip_protocol->ip_tos; /*获得tos*/
offset=ntohs(ip_protocol->ip_off); /*获得偏移量*/
printf("\n########## 网络层(IP协议) ########### \n");
printf("IP版本:\t\tIPv%d\n",ip_protocol->ip_version);
printf("IP协议首部长度:\t%d\n",header_length);
printf("服务类型:\t%d\n",tos);
printf("总长度:\t\t%d\n",ntohs(ip_protocol->ip_length));/*获得总长度*/
printf("标识:\t\t%d\n",ntohs(ip_protocol->ip_id)); /*获得标识*/
printf("片偏移:\t\t%d\n",(offset&0x1fff)*8); /**/
printf("生存时间:\t%d\n",ip_protocol->ip_ttl); /*获得ttl*/
printf("首部检验和:\t%d\n",checksum);
printf("源IP:\t%s\n",inet_ntoa(ip_protocol->ip_source_address)); /*获得源ip地址*/
printf("目的IP:\t%s\n",inet_ntoa(ip_protocol->ip_destination_address));/*获得目的ip地址*/
printf("协议号:\t%d\n",ip_protocol->ip_protocol); /*获得协议类型*/
cout<<"\n传输层协议是:\t";
switch(ip_protocol->ip_protocol)
{
case 6 :
printf("TCP\n");
tcp_protocol_packet_callback(argument,packet_header,packet_content);
break; /*协议类型是6代表TCP*/
case 17:
printf("UDP\n");
break;/*17代表UDP*/
case 1:
printf("ICMP\n");
break;/*代表ICMP*/
case 2:
printf("IGMP\n");
break;/*代表IGMP*/
default :break;
}
}
void ethernet_protocol_packet_callback(u_char *argument,const struct pcap_pkthdr *packet_header,const u_char* packet_content)
{
u_short ethernet_type; /*以太网协议类型*/
struct ethernet_header *ethernet_protocol; /*以太网协议变量*/
u_char *mac_string;
static int packet_number=1;
printf("\n*** ****** ******* ********* ********* ******* ****** ***\n");
printf("\t!!!!!# 第【 %d 】个IP数据包被捕获 #!!!!!\n",packet_number);
printf("\n========== 链路层(以太网协议) ==========\n");
ethernet_protocol =(struct ethernet_header *) packet_content; /*获得一太网协议数据内容*/
printf("以太网类型为 :\t");
ethernet_type=ntohs(ethernet_protocol->ether_type); /*获得以太网类型*/
printf("%04x\n",ethernet_type);
switch(ethernet_type) /*判断以太网类型的值*/
{
case 0x0800 :
printf("网络层是:\tIPv4协议\n");break;
case 0x0806 :
printf("网络层是:\tARP协议\n");break;
case 0x8035 :
printf("网络层是:\tRARP 协议\n");break;
default: break;
}
/*获得Mac源地址*/
printf("Mac源地址:\t");
mac_string=ethernet_protocol->ether_shost;
printf("%02x:%02x:%02x:%02x:%02x:%02x:\n",*mac_string,*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_string+5));
/*获得Mac目的地址*/
printf("Mac目的地址:\t");
mac_string=ethernet_protocol->ether_dhost;
printf("%02x:%02x:%02x:%02x:%02x:%02x:\n",*mac_string,*(mac_string+1),*(mac_string+2),*(mac_string+3),*(mac_string+4),*(mac_string+5));
switch (ethernet_type)
{
case 0x0800:
/*如果上层是IPv4ip协议,就调用分析ip协议的函数对ip包进行贩治*/
ip_protocol_packet_callback(argument,packet_header,packet_content);
break;
default :break;
}
packet_number++;
}
main()
{
cout<<"========== 解析IP数据包 ==========\n";
pcap_if_t *alldevs;
pcap_if_t *d;
int inum=0;
int i=0;
pcap_t *adhandle;
char errbuf[PCAP_ERRBUF_SIZE];
/* 获得网卡的列表 */
if (pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr,"Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* 打印网卡信息 */
for(d=alldevs; d; d=d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
printf(" (No description available)\n");
}
if(i==0)
{
printf("\n没有发现接口!确保安装了LibPcap.\n");
return -1;
}
printf("\n【输入要选择打开的网卡号 (1-%d)】:\t",i);
scanf("%d", &inum); //输入要选择打开的网卡号
if(inum < 1 || inum > i) //判断号的合法性
{
printf("\n网卡号超出范围.\n");
/*释放设备列表 */
pcap_freealldevs(alldevs);
return -1;
}
/* 找到要选择的网卡结构 */
for(d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
/* 打开选择的网卡 */
if ( (adhandle= pcap_open_live(d->name, /* 设备名称*/
65536, /* 最大值.*/
/*65536允许整个包在所有mac电脑上被捕获.*/
1, /* 混杂模式*/
/*
混杂模式是指一台主机能够接受所有经过它的数据流,不论这个数据流的目的地址是不是它,它都会接受这个数据包。也就是说,混杂模式下,网卡会把所有的发往它的包全部都接收。在这种情况下,可以接收同一集线器局域网的所有数据。
*/
1000, /* 读超时为1秒*/
errbuf /* error buffer*/
) ) == NULL)
{
fprintf(stderr,"\n无法打开适配器.\t %s 不被LibPcap支持\n");
/* Free the device list */
pcap_freealldevs(alldevs);
return -1;
}
printf("\n监听 %s...\n", d->description);
/* 现在,我们不再需要设备列表, 释放它 */
pcap_freealldevs(alldevs);
int cnt = -1;
cout<<"\n【将要捕获数据包的个数】:\t\t";
cin>>cnt;
/* 开始以回调的方式捕获包
函数名称:int pcap_loop(pcap_t * p,int cnt, pcap_handler callback, uchar * user);
函数功能:捕获数据包,不会响应pcap_open_live()函数设置的超时时间
*/
pcap_loop(adhandle,cnt, ethernet_protocol_packet_callback, NULL);
cout<<"\n\t!!!!!# 解析IP数据包结束 #!!!!!\n";
return 0;
}