您好,欢迎来到百家汽车网。
搜索
您的当前位置:首页协议分析器程序

协议分析器程序

来源:百家汽车网


协议分析器程序

编程训练目的

协议分析器可以有效完成对网络上传输数据包的捕获,从而收集与分析网络信息,是网络流量监控和故障检测所必不可少的工具之一,对网络管理与网络安全具有重要的意义。

协议分析器的正当用途是分析网络流量,以便找出网络运行中潜在的问题。例如,当网络中的某一网段运行情况不良,网络时常出现阻塞,报文发送缓慢,此时我们就可以使用协议分析器来作出精确的问题判断。另一方面协议分析器也被称作网络嗅探器(sniffer),它同时有威胁网络安全的一面,如利用网络嗅探器窃取他人帐号、密码等敏感信息,因此也要注意防范其可能造成的危害。

本次编程训练的目的就是通过编程实现一个简单的协议分析器。通过该课题的训练,读者对TCP/IP协议原理的理解、对网络管理与网络安全工具的设计与实现技术的提高会有很大地帮助,同时也可对网络嗅探器的实现机理有深入地了解。

编程训练要求

1. 本次编程训练要求实现一个协议分析器。该协议分析器将包含以下几部分功能:

1) 数据采集——实时捕捉Ethernet网数据包;

说明:数据采集功能需提供两种模式——直接模式和混杂模式,直接模式只用于捕获目的地为本机的数据;混杂模式则可捕获流经Ethernet网络的所有数据包。

1

2) 解析Ethernet网数据帧头部的全部信息;

3) 实现基于MAC地址和帧头部类型字段的数据包过滤;

说明:基于MAC地址的数据过滤主要满足用户获取和分析特定MAC地址的主机通信数据的要求。例如,如果用户只对网内MAC地址为01-25-d3-a4-88-98主机的通信信息感兴趣,那么通过这个功能,用户就可以将与这个主机无关的数据剔除,使数据的捕获更加有针对性,方便数据分析的进行。对帧头部类型字段的数据过滤主要用于区分载荷是IP报文还是ARP或RARP报文。

4) 解析IP数据包的头部信息;

说明:需要解析的字段主要包括:版本,头度,服务类型,总长,TTL,上层协议,源IP,目的IP等。

5) 实现基于IP地址和IP头协议的数据过滤;

说明:这个过滤主要是基于IP地址(源地址或者目的地址),以及IP头部的上层协议字段(比如要求只捕获ICMP数据包等);

6) 对ICMP报文要求至少解析ICMP回显请求、回显应答、超时及目的不可达四种消息报文;

7) 解析传输层TCP、UDP协议头部信息;

8) 实现基于端口的数据包过滤;

2

9) 实现应用层HTTP协议头部分析。

上述9个方面的功能组装起来就是一个简单的网络协议分析器,它包含了数据采集、协议分析和数据过滤等功能。

2. 熟悉Winpcap编程。程序要求在Windows环境下运行,使用Winpcap完成抓包过程。

相关知识

1. 协议分析器基本原理

协议分析器是一种常用的数据收集方法,简单的说,它是在广播式网络环境下利用计算机网络接口截获目的地为其他计算机的数据报文的一种工具。

Ethernet是目前应用最广泛的计算机连网方式,它是基于总线结构,物理层是采用广播方式的。当一台主机向另一台主机发送数据时,发送主机会将包含目的主机正确地址的数据帧发送到总线上,因此同一链路上所有活跃主机的网卡都能接收到该帧。正常情况下,网卡收到传输来的数据帧后,会先检查帧头的目的地址字段,如果该地址不是本机的MAC地址,则丢弃不管,因此只有具有该地址的主机会接受这个数据帧。但是,如果某个程序能够修改网卡的接收模式,使其成为“混杂(promiscuous)”模式,即一台主机可以接收网络上所有的数据帧而不理会帧头的目的地址,利用这一点,就可以实现协议分析器。因此协议分析器就是一种能将本地网卡状态设成“混杂”模式并接收网络上所有数据帧的软件。

3

协议分析器工作在网络环境中的底层,拦截所有正在网络上传送的数据,并且通过相应的解析处理,可以实时分析这些数据的内容,进而分析所处的网络状态和整体布局。

2. TCP/IP分层体系结构

计算机网络实际上是按照不同的通信功能划分的层次结构系统,每一层的功能都由特定的协议来完成。TCP/IP协议族是当前最流行也是最成功的是一套的网络协议栈,其结构自底向上分为4层,分别为主机-网络层、互联网层、传输层和应用层。主机至网络层在TCP/IP中并没有做具体的规定。通常情况下我们选择Ethernet网作为底层网络环境,并使用常用的5层网络体系结构即物理层、数据链路层、网络层、传输层和应用层作为分层标准,如图6-1所示。在TCP/IP协议族的众多协议中,每种协议处理相应层所要求的网络功能,在图6-1中也给出了比较常见的网络协议及其所属的层次位置。

FTPSMTPTELNETHTTPTFTP应用层TCPUDP传输层ICMPIGMP网络层IPARPRARP以太网帧数据链路层网卡硬件物理层 图16-1 TCP/IP常见协议及所在层次位置

4

3. 数据的封装与解析

应用层数据传输层首部封装网络层首部传输层首部应用层数据解析应用层数据以太网首部网络层首部传输层首部应用层数据以太网尾部

图16-2 数据的封装与解析

当应用程序通过IP网络传送数据时,数据被送入TCP/IP协议栈中,然后从上至下逐一通过每一层,直到最后被当作一串比特流送入网络。其中每一层对收到的数据都要增加一些首部信息(有时还要增加尾部信息),这个过程被称作封装。通过以太网传输的比特流称作帧(frame)。在传输的另一端,当目的主机收到一个以太网数据帧时,数据就开始从协议栈由底向上逐层解析,去掉各层协议所加上的报文头部。每层协议均要检查报文头部中的协议标识字段,以确定要接收数据的上层协议,最终从报文中解析出应用层数据后交给应用程序处理。该封装与解析过程如图16-2所示。

本次要编写的协议分析器,就是从网络中捕获数据包并对其进行解析的过程。因此,我们需要了解每层协议所规定的报文格式,然后由底向上逐层对数据包进行解码,最后将分析的结果显示出来。

编程训练设计分析

5

1. 协议分析器总体结构

协议分析器的整体结构按功能应分为三个部分,自底向上分别是数据捕获模块、协议解析模块和用户显示模块,如图16-3所示。其中用户显示模块由于灵活度比较大,既可以采用控制台方式输出,也可以使用Windows图形窗口界面的各种视图(如树形视图、列表视图、编辑视图或普通视图等)对解码结果进行显示,因此这里不做特殊要求,编程时由读者任意选择一种方式即可。后面仅对数据捕获模块和协议解析模块的关键代码进行分析。

用户界面显示协议解析模块数据包数据捕获模块网络流量- 以太网 -过滤规则 图16-3 协议分析器整体结构图

2. 数据捕获模块

数据捕获模块的主要功能是进行数据包的采集,这是整个系统的基础和数据来源。程序使用Winpcap来捕获网络中原始数据包。Winpcap是Win32环境下数据包捕获的开放代码函数库,它可以完成以下主要功能:a)捕获网络中的原始数据包。b)使用用户自定义规则对数据包进行过滤。c)发送用户自己构造的数据包到网络中。d)统计网络流量。

6

具体使用方法参见其官方说明文档http://www.winpcap.org/docs/docs_40/index.html。

使用Winpcap捕获数据包的算法一般分为以下几步:

(1) 获取并列出当前网络设备列表。

(2) 由用户选择并打开指定网卡。

(3) 根据过滤规则设置过滤器。

(4) 捕获数据包并进行解析处理。

参考流程图如图16-4所示。

7

pcap_findalldevs()获取 网络设备列表pcap_open_live()打开设备,设为混杂模式pcap_compile()编译过滤器pcap_setfilter()设置过滤器pcap_next_ex()循环捕获数据包PacketHandler()处理数据包pcap_freealldevs()关闭网络设备 图16-4 数据包捕获流程图

下面给出各步骤的核心代码。需要注意的是,为了使程序流程更加清晰,下面的代码片段中去除了错误检查等保护性代码,读者在自己编程时应注意添加。

1. 获取并列出当前网络设备列表。

pcap_if_t* pAdaptersList = NULL; //网卡列表指针 char errbuf[PCAP_ERRBUF_SIZE]; //错误消息缓冲区 8

pcap_findalldevs(&pAdaptersList, errbuf); //获取本地网络设备列表 /*列出找到的网络设备*/ int iAdapterCount = 0; for (pcap_if_t* pAdapter= pAdaptersList; pAdapter!=NULL; pAdapter=pAdapter->next) { iAdapterCount++; if (pAdapter->description) //如果有描述则输出详细信息 …… /*输出当前序号和描述信息(pAdapter->description)*/ else //否则输出网卡名 …… /*输出当前序号和网卡名(pAdapter->name)*/ } 2. 由用户选择并打开指定网卡。

/*有些主机中有多个网卡,所以要列出所有网卡,让用户选择合适的网卡来捕获数据

9

包。

用户选择的网卡序号放在iAdapterSelected变量中*/

……

//跳到指定网卡

pAdapter = pAdaptersList;

for (int i=0; i< iAdapterSelected; i++)

pAdapter=pAdapter->next;

//打开网卡,之前要先设置bPromiscMode,为1表示混杂模式,为0表示直接模式

pcap_t* hAdapterHandle; //网卡句柄

hAdapterHandle = pcap_open_live(pAdapter->name, //网络设备名称

65535, //允许截获数据包的最大长度

bPromiscMode, //混杂模式标志

300, //读取数据超时时间

10

errbuf); //错误信息缓冲区 3. 根据过滤规则设置过滤器。

// pAdapter指向要设置的网卡,首先获取子网掩码 u_int uNetMask; if (pAdapter->addresses) uNetMask ((sockaddr_in*)(pAdapter->addresses->netmask))->sin_addr.s_addr; = else uNetMask = 0xffffff; //忽略子网掩码 char szFilterCode[1024]; …… /*根据规则构造过滤表达式,并存放在szFilterCode缓冲区中*/ //编译过滤器 bpf_program fpCode; if (pcap_compile(hAdapterHandle, &fpCode, szFilterCode, TRUE, uNetMask) <

11

0) { TRACE (\"Unable to compile the filter. Error Msg %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对子表达式进行组合,同时支

12

持使用小括号。

2) 基于协议的过滤要使用协议限定符,协议限定符可以为ip, arp, rarp, tcp 和 udp等。

3) 基于MAC地址的过滤要使用限定符ether(代表Ethernet网地址)。当该MAC地址仅作为源地址时过滤表达式为ether src mac_addr,仅作为目的地址时表达式为ether dst mac_addr,既作为源地址又作为目的地址时表达式为ether host mac_addr。此外应注意mac_addr应遵循由冒号分隔的十六进制格式,如00:E0:4C:E0:38:88,否则编译过滤器时会出错。

4) 基于IP地址的过滤应使用限定符host(代表主机地址)。当该IP地址仅作为源地址时过滤表达式为src host ip_addr,仅作为目的地址时表达式为dst host ip_addr,既作为源地址又作为目的地址时表达式为host ip_addr。

5) 基于端口的过滤应使用限定符port。例如仅接收80端口的数据报则表达式为port 80。

下面给出一些具体的例子:

例1:只捕获arp或icmp数据包 过滤表达式为:arp or (ip and icmp) ,或者简写为:arp or icmp 例2:捕获以192.168.1.23为源或目的地址的端口为80的tcp数据包 13

过滤表达式为:(ip and tcp) and (host 192.168.1.23) and (port 80) 例3:捕获主机192.168.1.23与192.168.1.28之间传递的所有udp数据包 过滤表达式为:(ip and udp) and ((src host 192.168.1.23 and dst host 192.168.1.28) or (dst host 192.168.1.23 and src host 192.168.1.28)) 例4:捕获从mac地址00-13-D3-A1-D2-F6到00-50-56-C0-00-01之间所有的arp包 过滤表达式为:arp and (ether src 00:13:D3:A1:D2:F6 and ether dst 00:50:56:C0:00:01) 4. 捕获数据包。

可以使用pcap_next_ex()从网络接口中读取一个数据包,该函数第一个参数是接口句柄,后两个参数由函数返回,分别为数据包的相关信息(包括时间戳、读取数据包长度和包的实际长度)和数据包本身。函数返回1表示正常接收一个数据包,返回0表示超时,-1表示发生错误。每捕获到一个数据包,就调用PacketHandler()函数对数据包进行后续解析处理。

while ((iResult=pcap_next_ex(hAdapterHandle, & pPktHdr, & pPktData)) >= 0) { 14

//接收超时继续循环 if (iResult == 0) continue; //处理收到的数据包 PacketHandler(); } 3. 协议解析模块

该模块的主要功能就是对捕获的数据包按照数据链路层(MAC)、网络层(IP、ARP/RARP)、传输层(TCP、UDP、ICMP)和应用层(HTTP等)的层次结构自底向上进行解析,最后将解析结果显示输出。协议解析的过程如图16-5所示。

15

开始解码EthernetV2.0第二层协议类型为?IEEE802.3ParseEtherPacket()Parse8023Packet()RARP第三层协议类型为?ARPIPParseRARPPacket()ParseIPPacket()ParseARPPacket()UDP第四层协议类型为?TCPICMPParseUDPPacket()ParseIPPacket()ParseICMPPacket()第五层协议类型为?HTTPParseHTTPPacket()解码结束 图16-5 协议解析流程图

该模块在编码时要注意各协议报头多字节整型字段从网络字节序到主机字节序转换。对于多字节的整型数据(如short、int、long等),由于不同计算机系统在内存中存放的

16

方式不尽相同,将低序字节存储在起始地址的称为小端点机,如Pentium,否则称为大端点机。在网络中数据的传输统一采用大端点方式,所以数据包头中表示长度或类型等的多字节数据字段应按需转换成本地主机的表示形式。可以用函数ntohs()或ntohl()将双字节或四字节整型由网络字节序转换为主机字节序。

1. 解析Ethernet帧。

Ethernet帧结构中,前导码、帧前定界符和帧校验和(CRC)三个字段均属于物理层内容,而我们使用的Winpcap抓包驱动程序工作在数据链路层,捕获的原始数据中不包含这些字段,因此只需解析目的地址、源地址、长度/类型和数据四个字段即可。

首先定义Mac头部数据结构。注意所有的头部结构定义均不包含数据部分。

//MAC头部结构 typedef struct { BYTE DesMacAddr[6]; //目的地址 BYTE SrcMacAddr[6]; //源地址 WORD LengthOrType; //数据长度或类型 } MAC_HEADER; 17

//MAC帧类型定义 const u_short MAC_TYPE_IP = 0x0800; const u_short MAC_TYPE_ARP = 0x0806; const u_short MAC_TYPE_RARP = 0x8035; 下面给出通过MAC_HEADER解析MAC头各个字段的代码。其中strItem是CString类型,用于描述协议分析信息,代码中省略了信息的输出。pPkt是数据包缓冲区指针,iLen指出数据包的长度。

MAC_HEADER* pMacHdr = (MAC_HEADER*) pPkt; // Mac目的地址 strItem.Format(\"Destination address: %02X:%02X:%02X:%02X:%02X:%02X\pMacHdr->DesMacAddr[0], pMacHdr->DesMacAddr[1], pMacHdr->DesMacAddr[2], pMacHdr->DesMacAddr[3], 18

pMacHdr->DesMacAddr[4],

pMacHdr->DesMacAddr[5]);

// Mac源地址

strItem.Format(\"Source address: %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) //类型字段(Ethernet V2.0)

{

19

//根据类型字段调用相应的上层协议处理函数

if (ntohs(pMacHdr->LengthOrType) == MAC_TYPE_IP) //IP协议

{

strItem = \"IP\";

ParseIPPacket((BYTE*)pMacHdr+sizeof(MAC_HEADER),

iLen-sizeof(MAC_HEADER));

}

else if (ntohs(pMacHdr->LengthOrType) == MAC_TYPE_ARP) //ARP协议

{

strItem = \"ARP\";

ParseARPPacket((BYTE*)pMacHdr+sizeof(MAC_HEADER),

iLen-sizeof(MAC_HEADER));

}

20

else if (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: %d bytes\

…… /*解析IEEE802数据帧,省略*/

21

} 对于Ethernet帧的封装,由于历史的原因,产生了两种不同的标准,一种是Ethernet V2.0标准(定义在RFC4中),该标准定义源地址之后的字段是类型字段。另一种是IEEE 802标准(在RFC1042中定义),它将前述字段定义为长度字段。由于Ethernet V2.0标准规定的有效类型值均大于Ethernet网帧的数据最大长度1500,因此可以通过该值区分类型/长度字段的具体含义,即该数值大于1500表示类型字段,否则为长度字段。这里只对Ethernet V2.0封装格式进行了解析,因为这是最常见的封装格式,读者有兴趣可以自己完成对IEEE802格式数据帧的解析。二者的不同封装格式如图16-6所示。

EthernetV2.0封装目的地址6IEEE802封装源地址类型62DSAPSSAPAAAA11Cntl031Org code003数据46~1500目的地址6源地址6长度2类型2数据38~1492

图16-6 Ethernet网帧的EthernetV2.0和IEEE802封装格式

2. 解析ARP数据包。

首先定义ARP头部数据结构:

//ARP头部结构 typedef struct 22

{

u_short hardware_type; //16位硬件类型

u_short proto_type; //16位协议类型

u_char hardware_addr_len; //8位硬件地址长度

u_char proto_addr_len; //8位协议地址长度

u_short operation_code; //16位操作码

u_char src_mac_addr[6]; //源Ethernet网地址

u_char scr_ip_addr[4]; //源IP地址

u_char dest_mac_addr[6]; //目的Ethernet网地址

u_char dest_ip_addr[4]; //目的IP地址

} ARP_HEADER;

//ARP报文操作码类型,1为请求,2位应答

const u_short ARP_OP_REQUEST = 1; //ARP请求 23

const u_short ARP_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)\"; else if (ntohs(pARPHdr->operation_code) == ARP_OP_REPLY) strItem = \" (Reply)\"; //源Mac地址 24

strItem.Format(\"Sender's

address: %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_addr ipAddr;

memcpy(&ipAddr, pARPHdr->scr_ip_addr, sizeof(in_addr));

inet_ntoa(ipAddr); //转化为点分十进制字符串

//目的Mac地址

strItem.Format(\"Target's

hardware

hardware

25

address: %02X:%02X:%02X:%02X:%02X:%02X\pARPHdr->dest_mac_addr[0], pARPHdr->dest_mac_addr[1], pARPHdr->dest_mac_addr[2], pARPHdr->dest_mac_addr[3], pARPHdr->dest_mac_addr[4], pARPHdr->dest_mac_addr[5]); //目的IP地址 memcpy(&ipAddr, pARPHdr->dest_ip_addr, sizeof(in_addr)); inet_ntoa(ipAddr); //转化为点分十进制字符串 //填充字段长度 strItem.Format(\"Frame padding: %d bytes\3. 解析IP数据包

26

IP头部数据结构定义如下:

//IP头部结构 typedef struct { unsigned char hdr_len :4; //4位头度 unsigned char version :4; //4位版本号 unsigned char tos; //8位服务类型 unsigned short total_len; //16位总长度 unsigned short identifier; //16位标识符 unsigned short frag_and_flags; //3位标志+13位片偏移unsigned char ttl; //8位生存时间 unsigned char protocol; //8位上层协议号 unsigned short checksum; //16位校验和 27

unsigned long source_ip; //32位源IP地址 unsigned long dest_ip; //32位目的IP地址 } IP_HEADER; 下面解析IP报文:

IP_HEADER* pIPHdr = (IP_HEADER*)pPkt; …… /*解析IP头部各字段并显示输出*/ //根据不同上层协议调用相应解析函数 switch (pIPHdr->protocol) { case IPPROTO_TCP: strItem = \"TCP\"; ParseTCPPacket((BYTE*)pIPHdr+iIPHdrLen, iLen-iIPHdrLen); break; 28

case IPPROTO_UDP: strItem = \"UDP\"; ParseUDPPacket((BYTE*)pIPHdr+iIPHdrLen, iLen-iIPHdrLen); break; case IPPROTO_ICMP: strItem = \"ICMP\"; ParseICMPPacket((BYTE*)pIPHdr+iIPHdrLen, iLen-iIPHdrLen); break; default: strItem = \"OTHERS\"; } 4. 解析ICMP、TCP和UDP数据包

解析ICMP、TCP和UDP数据包与解析IP报文类似,首先定义出各报文头部的数据结构类型,然后使用报头指针对结构成员进行访问。针对数据长度为8位的字段通过结构

29

成员直接存取;对于16位和32位长度的字段使用ntohs()或ntohl()转化为主机字节序后再做后续处理;对于长度小于8位的字段(如位标志或子字段)可以通过在结构体中定义位域(bit field)存取,如果不使用位域可以利用移位以及与、或操作完成。

ICMP协议的报头结构如下:

//ICMP基本头部 typedef struct { BYTE type; //8位类型 BYTE code; //8位代码 USHORT cksum; //16位校验和 } ICMP_BASE_HEADER; //---------------------------------------------------- //ICMP回显头部(回显请求、回显应答) typedef struct 30

{ ICMP_BASE_HEADER base_hdr; //基本头部 USHORT id; //16位标识符 USHORT seq; //16位序列号 } ICMP_ECHO_HEADER; //---------------------------------------------------- //ICMP差错报文头部(超时及目的不可达) typedef struct { ICMP_BASE_HEADER base_hdr; //基本头部 ULONG unused; //32位未用,必须为0 } ICMP_ERROR_HEADER; ICMP协议的解析处理:

31

pICMPHdr->type; //类型 pICMPHdr->code; //代码 ntohs(pICMPHdr->cksum); //校验和 //根据不同类型解析后续字段 switch (pICMPHdr->type) { case ICMP_ECHO_REQUEST: //回显请求报文 case ICMP_ECHO_REPLY: //回显应答报文 { ICMP_ECHO_HEADER* pICMPEchoHdr = (ICMP_ECHO_HEADER*)pPkt; ntohs(pICMPEchoHdr->id); //标识符 ntohs(pICMPEchoHdr->seq); //序列号 if (iLen > sizeof(ICMP_ECHO_HEADER)) //选项数据 32

{

for (int i=0; i…… /*打印选项数据*/

}

break;

}

case ICMP_TIMEOUT: //超时差错报文

case ICMP_HOST_UNREACHABLE: //目的不可达差错报文

{

ICMP_ERROR_HEADER* pICMPErrHdr = (ICMP_ERROR_HEADER*)pPkt;

ntohl(pICMPErrHdr->unused); //保留字段

//解析产生差错的数据报IP首部(含选项字段)及其载荷的前8个字节

ParseIPPacket((BYTE*)pICMPErrHdr+sizeof(ICMP_ERROR_HEADER),

33

iLen-sizeof(ICMP_ERROR_HEADER)); break; } } 对TCP和UDP报文的解析处理与前述代码类似,不同之处在于除了要定义相应头部数据结构以外,还要定义一个伪头部用于验证校验和的正确性,具体处理留给读者自行完成。

5. 解析HTTP数据包

HTTP协议是一个文本协议,协议采用了请求/响应模型。相应的,HTTP消息包括客户端向服务器的请求消息和服务器返回的应答消息。通常这两种类型的消息均由一个起始行,一个或者多个头部行,一个作为消息头结束的空行和可选的消息体组成。但在传递大块数据时,跟在第一个应答消息后面的HTTP应答有可能不带头部信息。因此,在解析HTTP消息时要判别是否含有有效的HTTP消息头,对于包含消息头的HTTP消息,只要将头部各行按文本信息输出即解析完毕。

如果HTTP消息包含消息头,则起始行不论是请求行还是状态行,二者都含有版本信息字段,即第一行都含有HTTP/Version ,另外头部的结束由最后一个空行指出,因此,同时判断这两个条件就可确定是否包含HTTP消息头。解析的关键代码如下:

34

/*HTTP消息的拷贝存放在pHttpPkt指向的缓冲区中 首先在HTTP消息中查找代表消息头结束的空行*/ char* pStopPos = strstr(pHttpPkt, \"\\r\\n\\r\\n\"); //结束指针指向最后的空行 if (pStopPos != NULL) //存在空行 { char* pBegPos = pHttpPkt; //每行的首指针 char* pCurPos = pHttpPkt; //每行的尾指针 int iLine = 1; while (pCurPos < pStopPos) { //查找消息头中每行的结束符 pCurPos = strstr(pBegPos, \"\\r\\n\"); if (pCurPos != NULL) 35

*pCurPos = '\\0'; //将该行变为以NULL结尾的字符串 /*如果第一行含有版本字段前缀“HTTP/”则认为是一个正确的HTTP头*/ if (iLine++ == 1) { if (strstr(pBegPos, \"HTTP/\") == NULL) break; } strItem.Format(“%s”, pBegPos); //输出该行信息 pCurPos += 2; //跳过回车换行符指向下一行开始 pBegPos = pCurPos; } } 36

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- baijiahaobaidu.com 版权所有 湘ICP备2023023988号-9

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务