pAdapter=pAdapter->next;
//打开网卡,之前要先设置bPromiscMode,为1表示混杂模式,为0表示直接模式
pcap_t*hAdapterHandle;//网卡句柄
hAdapterHandle=pcap_open_live(pAdapter->name,//网络设备名称
65535,//允许截获数据包的最大长度
bPromiscMode,//混杂模式标志
300,//读取数据超时时间
errbuf);//错误信息缓冲区
3.根据过滤规则设置过滤器。
//pAdapter指向要设置的网卡,首先获取子网掩码
u_intuNetMask;
if(pAdapter->addresses)
uNetMask=((sockaddr_in*)(pAdapter->addresses->netmask))->sin_addr.s_addr;
else
uNetMask=0xffffff;//忽略子网掩码
charszFilterCode[1024];
……/*根据规则构造过滤表达式,并存放在szFilterCode缓冲区中*/
//编译过滤器
bpf_programfpCode;
if(pcap_compile(hAdapterHandle,&fpCode,szFilterCode,TRUE,uNetMask)<0)
{
TRACE("Unabletocompilethefilter.ErrorMsg%s",pcap_geterr(hAdapterHandle));
return;
}
//设置过滤器
pcap_setfilter(hAdapterHandle,&fpCode);
程序要求可以实现4种类型的过滤:
1)基于协议的过滤(包括ARP、IP、ICMP、TCP和UDP协议);2)基于MAC地址的过滤;3)基于IP地址的过滤;4)基于端口的过滤。
Winpcap的强大功能之一就是它的过滤器引擎,该功能用到了pcap_compile()和pcap_setfilter()两个函数。
其中前者将一个包含高级布尔表达式的过滤规则字符串转化为底层的字节代码,这种字节代码可以由Winpcap包驱动程序的过滤器引擎来解释并执行;后一个函数用于将一个编译好的过滤器规则同一个捕获会话联系起来,即该函数执行之后,过滤规则就将应用于指定的包捕获过程。
由此可以看出,设置过滤器最主要的工作在于如何根据规则构造过滤表达式。
下面就对过滤表达式语法进行一下简单介绍。
其中关键字或限定符用黑体字表示,应当代换的信息用斜体字表示。
1)表达式支持逻辑操作符,可使用关键字and,or,not对子表达式进行组合,同时支持使用小括号。
2)基于协议的过滤要使用协议限定符,协议限定符可以为ip,arp,rarp,tcp和udp等。
3)基于MAC地址的过滤要使用限定符ether(代表Ethernet网地址)。
当该MAC地址仅作为源地址时过滤表达式为ethersrcmac_addr,仅作为目的地址时表达式为etherdstmac_addr,既作为源地址又作为目的地址时表达式为etherhostmac_addr。
此外应注意mac_addr应遵循由冒号分隔的十六进制格式,如00:
E0:
4C:
E0:
38:
88,否则编译过滤器时会出错。
4)基于IP地址的过滤应使用限定符host(代表主机地址)。
当该IP地址仅作为源地址时过滤表达式为srchostip_addr,仅作为目的地址时表达式为dsthostip_addr,既作为源地址又作为目的地址时表达式为hostip_addr。
5)基于端口的过滤应使用限定符port。
例如仅接收80端口的数据报则表达式为port80。
下面给出一些具体的例子:
例1:
只捕获arp或icmp数据包
过滤表达式为:
arpor(ipandicmp),或者简写为:
arporicmp
例2:
捕获以192.168.1.23为源或目的地址的端口为80的tcp数据包
过滤表达式为:
(ipandtcp)and(host192.168.1.23)and(port80)
例3:
捕获主机192.168.1.23与192.168.1.28之间传递的所有udp数据包
过滤表达式为:
(ipandudp)and((srchost192.168.1.23anddsthost192.168.1.28)or
(dsthost192.168.1.23andsrchost192.168.1.28))
例4:
捕获从mac地址00-13-D3-A1-D2-F6到00-50-56-C0-00-01之间所有的arp包
过滤表达式为:
arpand(ethersrc00:
13:
D3:
A1:
D2:
F6andetherdst00:
50:
56:
C0:
00:
01)
4.捕获数据包。
可以使用pcap_next_ex()从网络接口中读取一个数据包,该函数第一个参数是接口句柄,后两个参数由函数返回,分别为数据包的相关信息(包括时间戳、读取数据包长度和包的实际长度)和数据包本身。
函数返回1表示正常接收一个数据包,返回0表示超时,-1表示发生错误。
每捕获到一个数据包,就调用PacketHandler()函数对数据包进行后续解析处理。
while((iResult=pcap_next_ex(hAdapterHandle,&pPktHdr,&pPktData))>=0)
{
//接收超时继续循环
if(iResult==0)continue;
//处理收到的数据包
PacketHandler();
}
6.协议解析模块
该模块的主要功能就是对捕获的数据包按照数据链路层(MAC)、网络层(IP、ARP/RARP)、传输层(TCP、UDP、ICMP)和应用层(HTTP等)的层次结构自底向上进行解析,最后将解析结果显示输出。
协议解析的过程如图16-5所示。
图16-5协议解析流程图
该模块在编码时要注意各协议报头多字节整型字段从网络字节序到主机字节序转换。
对于多字节的整型数据(如short、int、long等),由于不同计算机系统在内存中存放的方式不尽相同,将低序字节存储在起始地址的称为小端点机,如Pentium,否则称为大端点机。
在网络中数据的传输统一采用大端点方式,所以数据包头中表示长度或类型等的多字节数据字段应按需转换成本地主机的表示形式。
可以用函数ntohs()或ntohl()将双字节或四字节整型由网络字节序转换为主机字节序。
5.解析Ethernet帧。
Ethernet帧结构中,前导码、帧前定界符和帧校验和(CRC)三个字段均属于物理层内容,而我们使用的Winpcap抓包驱动程序工作在数据链路层,捕获的原始数据中不包含这些字段,因此只需解析目的地址、源地址、长度/类型和数据四个字段即可。
首先定义Mac头部数据结构。
注意所有的头部结构定义均不包含数据部分。
//MAC头部结构
typedefstruct
{
BYTEDesMacAddr[6];//目的地址
BYTESrcMacAddr[6];//源地址
WORDLengthOrType;//数据长度或类型
}MAC_HEADER;
//MAC帧类型定义
constu_shortMAC_TYPE_IP=0x0800;
constu_shortMAC_TYPE_ARP=0x0806;
constu_shortMAC_TYPE_RARP=0x8035;
下面给出通过MAC_HEADER解析MAC头各个字段的代码。
其中strItem是CString类型,用于描述协议分析信息,代码中省略了信息的输出。
pPkt是数据包缓冲区指针,iLen指出数据包的长度。
MAC_HEADER*pMacHdr=(MAC_HEADER*)pPkt;
//Mac目的地址
strItem.Format("Destinationaddress:
%02X:
%02X:
%02X:
%02X:
%02X:
%02X",
pMacHdr->DesMacAddr[0],
pMacHdr->DesMacAddr[1],
pMacHdr->DesMacAddr[2],
pMacHdr->DesMacAddr[3],
pMacHdr->DesMacAddr[4],
pMacHdr->DesMacAddr[5]);
//Mac源地址
strItem.Format("Sourceaddress:
%02X:
%02X:
%02X:
%02X:
%02X:
%02X",
pMacHdr->SrcMacAddr[0],
pMacHdr->SrcMacAddr[1],
pMacHdr->SrcMacAddr[2],
pMacHdr->SrcMacAddr[3],
pMacHdr->SrcMacAddr[4],
pMacHdr->SrcMacAddr[5]);
//类型/长度字段
if(ntohs(pMacHdr->LengthOrType)>1500) //类型字段(EthernetV2.0)
{
//根据类型字段调用相应的上层协议处理函数
if(ntohs(pMacHdr->LengthOrType)==MAC_TYPE_IP)//IP协议
{
strItem="IP";
ParseIPPacket((BYTE*)pMacHdr+sizeof(MAC_HEADER),
iLen-sizeof(MAC_HEADER));
}
elseif(ntohs(pMacHdr->LengthOrType)==MAC_TYPE_ARP)//ARP协议
{
strItem="ARP";
ParseARPPacket((BYTE*)pMacHdr+sizeof(MAC_HEADER),
iLen-sizeof(MAC_HEADER));
}
elseif(ntohs(pMacHdr->LengthOrType)==MAC_TYPE_RARP)//RARP协议
{
strItem="RARP";
ParseRARPPacket((BYTE*)pMacHdr+sizeof(MAC_HEADER),
iLen-sizeof(MAC_HEADER));
}
else//其他
strItem="UNKNOWN";
}
else//长度字段(IEEE802格式)
{
strItem.Format("Length:
%dbytes",ntohs(pMacHdr->LengthOrType));
……/*解析IEEE802数据帧,省略*/
}
对于Ethernet帧的封装,由于历史的原因,产生了两种不同的标准,一种是EthernetV2.0标准(定义在RFC894中),该标准定义源地址之后的字段是类型字段。
另一种是IEEE802标准(在RFC1042中定义),它将前述字段定义为长度字段。
由于EthernetV2.0标准规定的有效类型值均大于Ethernet网帧的数据最大长度1500,因此可以通过该值区分类型/长度字段的具体含义,即该数值大于1500表示类型字段,否则为长度字段。
这里只对EthernetV2.0封装格式进行了解析,因为这是最常见的封装格式,读者有兴趣可以自己完成对IEEE802格式数据帧的解析。
二者的不同封装格式如图16-6所示。
图16-6Ethernet网帧的EthernetV2.0和IEEE802封装格式
6.解析ARP数据包。
首先定义ARP头部数据结构:
//ARP头部结构
typedefstruct
{
u_shorthardware_type;//16位硬件类型
u_shortproto_type;//16位协议类型
u_charhardware_addr_len;//8位硬件地址长度
u_charproto_addr_len;//8位协议地址长度
u_shortoperation_code;//16位操作码
u_charsrc_mac_addr[6];//源Ethernet网地址
u_charscr_ip_addr[4];//源IP地址
u_chardest_mac_addr[6];//目的Ethernet网地址
u_chardest_ip_addr[4];//目的IP地址
}ARP_HEADER;
//ARP报文操作码类型,1为请求,2位应答
constu_shortARP_OP_REQUEST=1;//ARP请求
constu_shortARP_OP_REPLY=2;//ARP应答
接下来解析ARP数据包:
ARP_HEADER*pARPHdr=(ARP_HEADER*)pPkt;
ntohs(pARPHdr->hardware_type);//硬件类型
ntohs(pARPHdr->proto_type);//上层协议类型
pARPHdr->hardware_addr_len;//硬件地址长度
pARPHdr->proto_addr_len;//协议地址长度
//操作类型
if(ntohs(pARPHdr->operation_code)==ARP_OP_REQUEST)
strItem="(Request)";
elseif(ntohs(pARPHdr->operation_code)==ARP_OP_REPLY)
strItem="(Reply)";
//源Mac地址
strItem.Format("Sender'shardwareaddress:
%02X:
%02X:
%02X:
%02X:
%02X:
%02X",
pARPHdr->src_mac_addr[0],
pARPHdr->src_mac_addr[1],
pARPHdr->src_mac_addr[2],
pARPHdr->src_mac_addr[3],
pARPHdr->src_mac_addr[4],
pARPHdr->src_mac_addr[5]);
//源IP地址
in_addripAddr;
memcpy(&ipAddr,pARPHdr->scr_ip_addr,sizeof(in_addr));
inet_ntoa(ipAddr);//转化为点分十进制字符串
//目的Mac地址
strItem.Format("Target'shardwareaddress:
%02X:
%02X:
%02X:
%02X:
%02X:
%02X",
pARPHdr->dest_mac_addr[0],