现在的位置: 首页 > 综合 > 正文

NDIS_PACKET结构讨论

2013年03月09日 ⁄ 综合 ⁄ 共 7243字 ⁄ 字号 评论关闭

http://feikoo.bokee.com/viewdiary.10774705.html


译者:
feikoo

作者:
NDIS.com

日期:
2006-4-3

 

这篇文章的目的是探讨一下在网络上截取的包(如
IP

包)与在
NDIS

驱动中代表相同内容的
NDIS_PACKET

之间的关系。

 

标准化组织:

我们经常在新闻组上看到如下的内容:

        
1.


微软的开发文档是如何描述
Window2000

网络包的?

        
2.


有谁知道在哪可以找到
Window2000 IP

包的详细描述?

        
3.


我想知道
NDIS_PACKET

的基本结构,例如:我想知道哪部分是源
IP

地址,哪部分是端口号与数据等

 

这些问题可以分以下两个独立问题:

1.

在哪儿可以找到网络协议的详细说明?如
IP

协议

2.

网络数据是怎样封装在
NDIS_PACKET

中的?

第一个问题的答案是:广泛使用的协议是标准化组织制定的,不是微软。以下列出一些标准化组织(这些组织读者自己去找中文翻译吧):

        
ETSI - European Telecommunications Standards Institute


 
      
IEEE - Institute of Electrical and Electronics Engineers


 
      
IETF - Internet Engineering Task Force


 
      
IPv6 - IPv6 Forum


 
      
ISO - International Standards Organization


 
      
OMG - Object Management Group Open Group


 
      
World Wide Web Consortium 


IP

协议是世界上广泛使用的网络协议。
IP

的详细说明由
IETF

来维护。
IETF

是由网络设计者,使用者,厂商,研究人员组成的一个开放组织,他们共同关注网络的结构以及网络的顺畅运行,它对所有感兴趣的个人开放。

        


关于
IP

协议的相关信息可以在
IETF

的网站上找到:
RFC791

你应该访问该网站来获取其它协议的相关信息。

 

当然,
IP

不是网络的唯一协议,
IETF

也不是唯一的标准化机构。那么,我们还能在哪儿找到相关信息呢?

 

就近的网站有
Protocols.com

,它们发布了一个协议目录,其中包括了各种协议及它们的标准化机构。

 

接下来我们来看看数据是怎样封装在
NDIS_PACKET

结构中的。

 

网络上的数据包:

接下来我们来真正理解一下在网络上观测到的数据包。下面是一个
ICMP

包的
HEX Dump


000000: 
00 A0 CC 63 08 1B 00 40 : 95 49 03 5F 08 00

45 00

 ...c...@.I._..E.


000010: 
00 3C 82 47 00 00 20 01 : 94 C9 C0 A8 01 20 C0 A8

 .<.G.. ...... ..


000020: 
01 40

08 00 48 5C 01 00 : 04 00 61 62 63 64 65 66

 .@..H/....abcdef


000030:
67 68 69 6A 6B 6C 6D 6E : 6F 70 71 72 73 74 75 76

 ghijklmnopqrstuv


000040: 
77 61 62 63 64 65 66 67 : 68 69

                   
wabcdefghi......


Ping

是由下面的命令发起的:

C:> ping 192.168.1.64


发送
ICMP

回显请求,带
32

字节的数据。
Ping

的总长度是
74

字节,上面的数据中没包含帧前导(用于同步的部分)以及
FCS

(帧校验和)。

 

以上是用
PCAUSA Rawether for Windows HookPeek
程序捕获的,
HOOKPEER
不是网络监控程序,但它有这方面的功能。

 

PING

包的解包在下面的链接中有详细说明,可以作为参考:

        

http://www.ndis.com/papers/ndispacket/ndispacket_decode.htm


 

数据包的
NDIS

表示:

 

当然,我们感兴趣的信息是包数据。即包含有
74

字节
VM

(虚拟内存)的地方,它表示的是在网络上观察到的
74

字节的数据。首先,
NDIS

用来管理包数据的机制看起来似乎有点复杂而且不必要。但是,这种基础的机制是经常深思熟虑的,它给编写协议的作者提供了许多灵活性。

 

NDIS_PACKET

的简单表示

 

通常,最好将
NDIS_PACKET

结构(与之关联的
NDIS_BUFFER

结构也一样)认为是“透明”的
-

除了一些在以后将要讨论的特殊保留域。这意味着在结构中定义的域不能直接访问,并且这些结构可能随着
NDIS

的版本不同而不同。

 

然而,对这些结构以及它们与包数据是如何关联的有一个大概的了解是很有用的。

如下的图表向我们展示了
NDIS_PACKET

封装包数据的最简单的方法:

        



1.

一个简单
NDIS_PACKET

展示

 

在这种简单情形下,所有的
74

字节的包数据均位于连续的
74

字节的数组中。

NDIS_PACKET

包描述:

NDIS_PACKET

包有一个链接的
NDIS_BUFFER


NDIS_BUFFER

描述了一个包含了所有包数据的
74

字节的虚拟内存范围。

 

NDIS_PACKET

更复杂的表示

 

如下的图表向展示了
NDIS_PACKET

封装包数据的另一种方法。



2

:多缓冲区的
NDIS_PACKET

展示

 

在这种情形下,包数据分布在两个分开的虚拟内存范围中。

以下是第二种
NDIS_PACKET

包的描述:

NDIS_PACKET

包拥有两个链接的
NDIS_BUFFER

,第一个
NDIS_BUFFER

描述了一个
14

字节的虚拟内存范围,它包含了
Ethernet

头。第二个
NDIS_BUFFER

描述了一个
60

字节范围的虚拟内存,它包含了
Ethernet

的负载(净荷域)。

 

很明显,没一种关于
NDIS_PACKET

包构成的安全假设。包结构首先是由构建包的软件来决定的。

 

理解
NDIS_PACKET


 

尽管以上的图表给我们提供了一个
NDIS_PACKET

包是如何使用的画面,但是你必须记住,这些结构是透明的(即你最好别改它)。你决不能直接访问这些结构的某些域。相反,你应该使用
NDIS

包提供的访问函数。

 

在接下来的话题中,我们讨论部分
NDIS

库函数。我们的目光主要集中在那一小部分函数,如果给你一个包让你检查,你就可以使用这些函数来检查和解释包数据。

 

你用来检查
NDIS_PACKET

和与之相链接的
NDIS_BUFFER

的函数仅有一小部分,它们是:

NdisQueryPacket-


 


返回给定的一个包描述符的信息。

BufferCount –

包描述符上的缓冲区描述符个数

FirstBuffer –

指向链接在包描述上的第一个缓冲区描述符。

TotalPacketLength –

所有链接的缓冲区描述符所映射的包数据总数。

NdisQueryBuffer



返回所给的缓冲区描述符的相关信息

VirtualAddress –

缓冲区描述符所描述的地址范围的基地址指针

Length –

缓冲区描述符所描述的地址范围中所包含的字节数。

NdisGetNextBuffer –



提供当前缓冲区描述符指针,返回链上的下一个缓冲区描述

NDIS 5.1

注:
NDIS 5.1


Windows XP

)引入几个
NDIS

函数的安全版本。这些函数的安全版本只能用在
NDIS 5.1

驱动中。

NdisQueryBufferSafe



NdisQueryBuffer

的安全版本

 

以下是如何用
NdisQueryPacket

函数来检查
pNdisPacket

指针所指向的
NDIS_PACKET

          
PNDIS_BUFFER   
pCurrentBuffer;

UINT           
nBufferCount, TotalPacketLength;

//

// Query Packet
 
//
 
NdisQueryPacket(
    
(PNDIS_PACKET )pNdisPacket,
    
(PUINT )NULL,          
// Physical Buffer Count
    
(PUINT )&nBufferCount, 
// Buffer Count
    
&pCurrentBuffer,       
// First Buffer
    
&TotalPacketLength     
// TotalPacketLength
    
);


这段代码先获取链在包上的
NDIS_BUFFER

的个数和通过缓冲区描述符映射的包数据长度。同时,它还取得了指向第一个
NDIS_BUFFER

的指针。

NdisQueryBuffer

函数可以用来从
NDIS_BUFFER

中提取信息,如下所示:

          
PUCHAR


   
VirtualAddress;

  

UINT

      
CurrentLength, CurrentOffset;

  

//

  

// Query The First Buffer

  

//

  

NdisQueryBuffer(

     

CurrentBuffer,

     

&VirtualAddress,

     

&CurrentLength

     

);

以上代码获取缓冲区描述符所描述的虚拟内存的长度和虚拟地址。

 

可以通过
VirtualAddress

指针,像操作普通无符号字符数组那样来修改内存的内容(
VirtualAddress

所指)。

 

但是,我们必须明白,有可能第一个缓冲区指针并不包含所有的包数据。如果
NDIS_PACKET

是如上图一所示的简单情形,那么
CurrentLength

应为
74

,所有的数据都在
VirtualAddress

所指的内存中。

如果
pNdisPacket

指向的是如图二所示的包,那情况将有所不同。
NdisQueryPacket

返回的
TotalPacketLength

就该为
74

;但是,当用
NdisQueryPacket

查询第一个缓冲区时返回的应该是
14


这就意味着我们必须检查链接在包描述符上的其它缓冲区,以便查看余下的包数据。这时我们可以使用
NdisGetNextBuffer



来获取下一个缓冲区描述符。这种迭代方法(有时也叫做遍历缓冲区链)是检查包数据所必须的。

 

UTIL_ReadOnPacket


函数展示了通过任一包描述符指针来遍历缓冲区链表读数据的方法。
PCAUSA

这个函数来安全“
PEEK
”示知结构的包描述符。它可能不是所有情况下的最优的方法;例如,如果你试图拷贝一大片的网络数据(如:在
MTU
较大的网络上的
IP
包),那么需要一个更有效的函数来完成。



以下是
UTIL_ReadOnPacket



函数的代码:

 

/////////////////////////////////////////////////////////////////////////////

//// UTILReadOnPacket

//

// Purpose

// Logical read on the packet data in a NDIS_PACKET.

//

// Parameters

//

// Return Value

//

// Remarks

// The purpose of this function is to provide a convenient mechanism to

// read packet data from an NDIS_PACKET that may have multiple chained

// NDIS_BUFFERs.

//

VOID

UTILReadOnPacket(

   
PNDIS_PACKET Packet,

   
PUCHAR lpBuffer,

   
ULONG nNumberOfBytesToRead,

   
ULONG nOffset,               
// Byte Offset, Starting With MAC Header

   
PULONG lpNumberOfBytesRead

   
)

{

   
PNDIS_BUFFER   
CurrentBuffer;

   
UINT           
nBufferCount, TotalPacketLength;

   
PUCHAR         
VirtualAddress;

   
UINT           
CurrentLength, CurrentOffset;

   
UINT           
AmountToMove;

   
*lpNumberOfBytesRead = 0;

   
if (!nNumberOfBytesToRead)

      
return;

   
//

   
// Query Packet

   
//

   
NdisQueryPacket(

      
(PNDIS_PACKET )Packet,

      
(PUINT )NULL,          
// Physical Buffer Count

      
(PUINT )&nBufferCount, 
// Buffer Count

      
&CurrentBuffer,        
// First Buffer

      
&TotalPacketLength     
// TotalPacketLength

      
);

   
//

   
// Query The First Buffer

   
//

   
NdisQueryBuffer(

      
CurrentBuffer,

      
&VirtualAddress,

      
&CurrentLength

      
);

   
CurrentOffset = 0;

   
while( nOffset || nNumberOfBytesToRead )

   
{

      
while( !CurrentLength )

      
{

         
NdisGetNextBuffer(

            
CurrentBuffer,

            
&CurrentBuffer

            
);

         
// If we've reached the end of the packet. 
We return with what

         
// we've done so far (which must be shorter than requested).

         
if (!CurrentBuffer)

            
return;

         
NdisQueryBuffer(

            
CurrentBuffer,

            
&VirtualAddress,

            
&CurrentLength

            
);

         
CurrentOffset = 0;

      
}

      
if( nOffset )

      
{

         
// Compute how much data to move from this fragment

         
if( CurrentLength > nOffset )

            
CurrentOffset = nOffset;

         
else

            
CurrentOffset = CurrentLength;

         
nOffset -= CurrentOffset;

         
CurrentLength -= CurrentOffset;

   
   
}

      
if( nOffset )

      
{

         
CurrentLength = 0;

         
continue;

      
}

      
if( !CurrentLength )

      
{

         
continue;

      
}

      
// Compute how much data to move from this fragment

      
if (CurrentLength > nNumberOfBytesToRead)

         
AmountToMove = nNumberOfBytesToRead;

      
else

         
AmountToMove = CurrentLength;

      
// Copy the data.

      
NdisMoveMemory(

         
lpBuffer,

         
&VirtualAddress[ CurrentOffset ],

         
AmountToMove

         
);

      
// Update destination pointer

      
lpBuffer += AmountToMove;

      
// Update counters

      
*lpNumberOfBytesRead +=AmountToMove;

      
nNumberOfBytesToRead -=AmountToMove;

      
CurrentLength = 0;

   
}

}

PCASIM_SEND_FILTER_ACTION

IPBlock_FilterSendPacket(

   
IN PADAPTER      
Adapter,

   
IN PNDIS_PACKET  
pOriginalPacket

   
)

{

   
USHORT                 
nEtherType;

   
ULONG                  
nNumberOfBytesRead = 0;

   
<snip...>

   
//

   
// Ignore Non-IP Packets

   
// ---------------------

   
// Packets that are presented to IPBlock_FilterRcvIndication include

   
// non-IP packets. These should, of course, be ignored for TCP.

   
//

   
// The switch could be extended to to ARP, REVARP, etc.

   
//

   
UTILReadOnPacket(

      
pOriginalPacket,

   

抱歉!评论已关闭.