作者:James Antognini and Thomas F. Divine
Copyright ? 2003 by Printing Communications Assoc., Inc. (PCAUSA). All rights reserved
译:feikoo 时间:2006年3月1日
本文是扩展Microsoft? Windows? Driver Development Kit (DDK) PassThru NDIS Intermediate (IM) 驱动示例系列文章的第二部分,将讲述如何扩展PassThru驱动,使其能对发送到或接收自某一IP的数据包进行过滤。
在文章的第一部分中,讲述了扩展PassThru驱动的一些基础知识,包括:
1.基本的DeviceIoControl接口:提供Win32应用程序与PassThru驱动进行通信的一种基本方法。2.绑定枚举函数:允许Win32应用程序查询PassThru驱动的绑定信息;
3. ADAPT结构引用计数:添加对ADAPT结构的逻辑计数;
4.适配器名柄(Adapter Handle)打开/关闭功能:建立与某一具体命名的PassThru绑定的用户模式句柄的方法。该句柄可以用来在某一适配器上请求,读写I/O和进行其它的操作。
5 处理事件通知:在这节我们将处理已经打开的驱动句柄变为无效的情况(如Pnp)。
6. 在一个打开的适配器句柄上查询信息:增加一种用Win32初始化的NDIS请求在一个打开的适配器句柄上查询信息的方法。 接下来的一系列文章将介绍对PassThru的另一些扩展。
在接下来的文字中,主要讲述如何在PassThru基础上实现根据被阻止的IP列表过滤发送或接收的数据包。实现此功能的基本要求则包括:
1.Filter Driver – 在PassThru基础上开发NDIS IM驱动,用于过滤
2.Control Application – 用来从从文本文件中读要阻止的IP地址并生成IP列表。
3.Statistics Viewer – 提供一种机制,用于统计被过滤驱动程序阻止的数据包。
两位作者为这篇文章每人写了一个NDIS IM驱动,尽管它们在功能上是一致的:传递给驱动的命令相同,驱动作出的回应也相同。但是,二者还是在实现方式上有很大的不同。现列举如下表:
| Thomas | James |
Driver API | DeviceIoControl (IOCTL) | Windows Management |
Packet Header Definitions | 取自 FreeBSD 5.0 | 取自不同的 RFC文档 |
每一个驱动程序均提供了满足要求的方法供大家探讨。读者仔细阅读这两个驱动程序代码是非常有益的,他向我们展示了如何在同一起点上却能产生不同的实现方法和设计思路。
文章包括:设计问题的总述,Thomas和James 对他们自己的程序的看法。而文章的精华在于这两个驱动程序的源代码。
一.设计时需要考虑的问题:
1.弃包:
在驱动程序中,有三处可以对包进行操作,这三处就是我们进行数据包转发或弃包(过滤)处理的代码位置。其中弃包是通过对数据包不进行转发来实现的。
(1)MPSendPackets – 这里可以看见向外发送的一个或多个数据包。该函数通过将数据包递交或不递交给下一层来达到发送或弃包的目的。
(2)PTReceive – 看见单个的接收数据包。一般情况下,包可能不整,而只是包头和数据的大部分,该函数在以前的设计方式中或发生资源短缺时由其下层驱动调用(在PassThru中,其下层为miniport驱动层)。PTReceive要么向上层驱动指示有包到来,要么将包丢弃不作处理。
(3) PTReceivePacket – 如果驱动程序以较新的思路设计或缓冲资源充分的话,它可接收一个完整的数据包。PTReceivePacket同样是要么向上层驱动指示有包到来,要么将包丢弃不作处理。
需要说明的一点是:弃包仅仅是不向其下一层驱动转发其收到的数据包,NDIS函数库中是没有弃包函数的。无论数据包是向下一层驱动递交或是弃包,都需要最终调用函数NdisMSendComplete 来完成操作,以便IM驱动上层的发送者能够回收资源。
在这个示例中,我们没有使用MPSend,而是使用了MPSendPackets。这两个函数只实现其一就可以了(由在注册minports回调函数时决定使用哪一个)。注册miniport回调函数充许在编译时选择old-style 或the new-style Send 回调函数。
2. 网络配置变化的影响
当驱动程序安装好后,又可能因后来的NDIS操作导致需要卸载驱动。如用户通过网络控制面板删除或取消了NDIS IM PassThru驱动。
我们要保证整个P过滤驱动的设计不会影响驱动程序的正常卸载。如果一个用户创建了一个驱动程序的句柄,则(1)仅让该句柄短暂存在(2)为了使用NDIS操作能继续进行,需要一个通知上层应用程序的方法,以便让句柄关闭。
3.序列化问题
这里存在这样的问题:一些运行在PASSIVE_LEVEL的函数可能被运行在DISPATCH_LEVEL的函数中断。进一步考虑MP(多处理器系统)系统,两个驱动程序的实例可能同时运行在两个不相关的IRQL上。因此,在这种情况下要使用旋转锁(Spin lock);在其它情况下,使用Interlocked函数族就足够保持cache的一致性了(it suffices for purposes of copying statistics counters merely to employ code that preserve cache coherency ("interlocked" family of functions,不知首如何译).。
4.设计选项
以下是驱动开发人员作出的该驱动程序应具体的功能:
1.管理员权限问题:有或者没有管理员权限的用户均能设置IP地址列表。对于更进一步的控制,留给有兴趣的读者作为练习。
2.仅过滤Ipv4的数据包:只有以太网包头指明是IPV4的数据包才会被过滤,ARP和RARP数据包均不会被过滤掉。
3.IP地址列表顺序:为了简起见,应用程序提供的过滤IP地址列表就是一个事先排好序的列表,驱动程序会拒绝没有排序的IP地址列表。
4.用户态/核心态API方法:传统的DeviceIoControl 函数和Windows Management Instrumentation (WMI) API函数均可用于配置IP过滤功能。
二.IOCTL和WMI作为配置程序接口的优缺点:
用户程序与驱动程序的交互被设计成暂时的。应用程序与驱动程序仅仅短暂地通信以传递命令;然后,驱动程序自动运行直到下一交互操作开始。应用程序也可能不时向驱动程序查询相关统计信息。
IOCTL与WMI均适合在应用程序与驱动程序之间传递命令与统计信息。但是,二者各有优缺点,列举如下:
1.IOCTL接口:
(1)优点:A.这是一种常用的用户与核心通信的方法,许多人都熟悉。还有,用户区的代码会很简单(CreateFile,CloseHandle,DeviceIoControl)。B.可以被扩展用来提供连续,高速的驱动接口。例如,异步I/O使用IOCTL就是一个好的包收集接口(公认的)。
(2)缺点:目前还没实现到更细化的打开驱动对象授权。NdisMRegisterDevice还没有像IoCreateDeviceSecure 那样的授权机制。操作还不支持远程打开一个设备对象。当然,用户可在本地写一个远程服务器,用于远程用户在本地打开一个设备驱动,但这会增加工作量。
2.WMI接口
(1)优点:
A)WMI本身具有远程访问功能(公认的),基于WMI的配置程序可以像控制本地驱动实例一样来配置一个远程驱动实例。
B)PerMon和WbemTest与WMI提供者一起工作。因此,你可以使用PerMon从PassThru中获取统计信息。
C)相对于IOCTL,微软推荐使用WMI来进行简单的配置操作。
(2)缺点:
A)这种技术对我们来说较陌生,并且大部分在用户态工作。本文示例可以清楚地说明这一点。
B)不能被扩展来提供连续,高速的接口。例如,它就不适合作包收集接口。
三.弃包的多方面考虑
以下是NDIS IM驱动弃包处理的一些考虑:
1.对于发送操作来说,驱动程序在处理完数据包(转发或者丢弃)后应该通过NdisMSendComplete 函数来告诉发送数据包一方。
2.发送数据包的程序或者是TCP/IP协议会重新发送丢弃的包,因为对于TCP/IP协议来说,数据包仅仅是因为未知原因丢失了,它会尝试重新发送该数据包。
3.数据包的内容,即TCP包的净荷域(除掉包头的数据部分)是不会被PassThru驱动程序检查的,它仅仅检查相关的包头部分。对于加密的数据包,驱动程序也会同样的方式处理它。但是,对于VPN协议来说,加密的数据包是不同的:所有的驱动程序只能看到未加密的IP地址等信息。对于其它在净荷域中加密过的地址等是无法被看到的。
4.当驱动程序丢发送的数据包时,网络临控器,嗅控器(如Microsft NetMon)同样会认为数据包已经发送出去。这是因为这些程序所使用的NDIS协议驱动在NDIS栈中比NDIS IM驱动高,是NDIS IM驱动的上层。同样,对NDIS IM驱动丢弃的接收数据包,这些应用程序将无法看到。
文章的其余部分主要是Thomas和James对各自的驱动程序和测试程序的见解。文章的精华则在于这两个驱动程序的源代码。
四.NDIS IM IP过滤驱动(Thomas)
1.驱动概述
本文从前一篇文章中提及的驱动程序框架着手,我们先修改原代码,加入新模块(filter.c),将过滤功能独立出来。这样做的目的主要是便于以后修改过滤模块来用不同方法实现过滤功能。
Filter.c中的主要函数有:
(1)FltFilterSendPacket – 过滤传递给 MPSend and MPSendPackets函数的每一个数据包。
(2)FltFilterReceivedPacket – 过滤每一个传递给PtReccievePacket.的数据包。
(3)FltFilterReceive – 过滤每一个传递给 PtReceive的数据包。
TestIOCTL程序读取含有适配名和要过滤的IP列表的文本文件,排好序的IP地址列表通过命令IOCTL_PTUSERIO_SET_IPv4_BLOCK_FILTER发送给驱动程序。在FltDevIoControl
函数,IP地址列表被复制到ADAPT结构中的FilterReserved域中。当数据包进行FltFilterXYZ函数时被检查,然后提取包中源地址和目的地址。本示例中使用从FreeBSD 5.0拷贝过来的与网络的IP头定义 。例如,程序引用/B2Winet/ip.h作为IP头的定义。在同一个目录,还有许多的包头定义,为了能在WINDOWS环境中使用,头文件被作了稍微的修改。如果你对其它的头感兴趣(如IPV6),你可以在网上下载相关的H文件。
2.测试程序概述
要查看在NDIS IM驱动程序上绑定的适配器信息,可以带参数运行TestIOCTL程序:TestIOCTL /enum 你将会看类似以下的信息:
TestIOCTL /enum
You will see information similar to this example output:
PassThru User I/O Test Application
Copyright (c) 2003 Printing Communications Assoc., Inc. (PCAUSA)
All rights reserved.
Driver Bindings:
"/Device/{B3B985AD-EB56-4F6A-8D16-131118E52131}"
"/DEVICE/{9C9770B5-CFBC-41DF-BE1D-510CEC826190}" <-- Lower Adapter Name
Description: " National Semiconductor Corp. DP83820 10/100/1000 GigPhyter PCI Adapter"
Medium: 802.3
Mac address = 00-40-F4-00-07-B5
Media Connect Status: Connected
"/Device/{03BB2564-4AA2-4E9B-B251-79D6A69B461F}"
"/DEVICE/NDISWANIP"
Description: " NdisWan Adapter"
Medium: 802.3
Mac address = A6-3E-20-52-41-53
Media Connect Status: Connected
要设置过滤IP列表,这样使用:TestIOCTL /set filename 文件的格式如下所示。
#
# Sample IP Address Blocking List File
#
# Lines beginning with '#' are comments. Leading and trailing whitespace
# is ignored. Empty lines are ignored.
#
# The first non-comment line must be the NDIS name of the lower adapter
# that the list of IP addresses to block is intended.
#
/Device/{B3B985AD-EB56-4F6A-8D16-131118E52131}
#
# Then comes the list of IP addresses to block. The IP addresses can
# have a comment, if desirable.
#
172.16.1.10
192.168.2.1
192.168.2.2
192.168.2.3
172.16.100.5
...
192.168.2.4 ; Can have comments per IP address, if desirable...
如果要重置所有的适配器到初始状态,执行:TestIOCTL /setdflt
如果要查看统计信息:TestIOCTL /stats
3.编译环境:
测试程序是用visual Studio.net 编译的,驱动程序是用XP DDK编译的。用高版本的DDK也可编译。
五.NDIS IM IP过滤驱动(James)
1.驱动程序概述:
文章以Windows2003 Server DDK的PassThru为起点,我们有意识地对程序作了一些改进,这些改动过的代码用以下这种形式来区分:
// This is a comment. ja, 31 Oct 2003.
过滤代码主要放在新的内核模块文件FilterRtns.cpp,UtilRtns.c and WMIRtns.cpp中,用户程序的代码放在SetIPAddrArray.cpp,PassThruStats.cpp, SetSecurity.cpp and ReportError.cpp中。
注:FilterRtns.cpp and WMIRtns.cpp是一般的C++文件(由文件扩展名可看出),但其中的实现却是完全意义上的C语言。用户应用程序是用真正的C++语言写的,主要是因为作者有关于COM/DCOM/WMI APIs.的示例代码。
设置过滤器的值和提供数据统计这两项功能是基于WMI的,你可以在miniport.c中看到相关的技术,而且集中在MPQueryInformation and MPSetInformation. 中。但是,注意,在miniport.c的开头,有一个包括两个PassThru支持的GUID。检查数据包,在需要弃包以及持续记录统计信息主要是在MPSendPackets, PTReceive and PTReceivePacket 三个函数中完成。转发或者丢弃数据包是在函数FilterPacket(在FilterRtns.cpp中定义)中决定的,这个函数由MPSendPackets, PTReceive and PTReceivePacket 函数调用。
FilterPacket 函数首先确定数据包是否IPV4数据包,接着找到一个过滤IP列表的拷贝,最后它地该列表进行二分搜索,对于接收的包,如果它的源IP与过滤IP列表的IP匹配,对于发送的包,如果它的目的IP与过滤IP列表的IP匹配,则丢弃。
为了得到与相关包相联系的适配器上的IP过滤列表,FilterPacket 调用PassthruWMIGetAddrArray (在WMIRtns.cpp中定义);为了设置列表,MPSetInformation 调用PassthruWMISetAddrArray。PassthruWMIGetAddrArray 会对过滤器上的ADAPT结构的一部分加一个读锁,PassthruWMISetAddrArray 会加一个更新锁,因此,对一个适配器来说IP地址列表一致性得到保证。
注意:在适配器上的IP地址列表读和更新操作不是串行化的,作者认为并不认为这有什么意义。有可能在同一时刻,一个应用程序有两个实例在运行,并且每一个实例可能更新所有适配器,并且为每一个不同的适配器指定不同的列表。但是,对于某一个适配器来说,更新或者复制IP列表都是原语操作。
2.测试程序概述
1. SetIPAddrArray- 过滤控制程序
SetIPAddrArray是一个用户态程序,用于获取或设置过滤地址。PassThruStats获取统计信息(如接收到的包,丢弃的包等)。
对于设置过滤器,SetIPAddrArray使用严格的格式来读取c:/Temp/passthru/AddrArray.txt文件,第一行必须包含点式IP地址,并且地址必须为升序排列。通过编辑该文件来输入你想添加的IP地址。如果你想使你的改动对系统上所有的适配均有效,则需在命令窗口输入:
SetIPAddrArray set
如果你想看你当前系统上的活动IP过滤器,则输入:
SetIPAddrArray get
如果你想设置特定的适配器信息而不是其它的,则需拷贝你调用SetIPAddrArray get 时返回的信息,并将此信息用双引号引起来提供给SetIPAddrArray。如:
SetIPAddrArray set a="Linksys LNE100TX(v5) Fast Ethernet Adapter - Passthru Miniport"
当你再次调用SetIPAddrArray get时,你就会发现你所设的已在该适配器上生效了。
如果你想重置所有的适配器到没有IP列表和过滤器状态,执行:
SetIPAddrArray setdflt
2. PassThruStats – 统计信息查看器
要得到统计信息,使用以下的命令:
PassThruStats get
这个命令会报告MPSendPackets, PTReceive and PTReceivePacket. 所接收和丢弃的包的数量。
SetIPAddrArray and PassThruStats均接收除动词以外的其它参数。这些参数指定系统(当远程调用时),Userid和密码,对SetIPAddrArray还可以指定设置某一适配器的名字。例如:
PassThruStats get MyFavoriteSystem MyUserid MyPassword
像我们以前说明的,PerfMon and WbemTest会与PassThru一道工作,因为它使用WMI作为接口。用户可以通过启动PerfMon,右击数据窗口,可以添中读数器,选择统计功能和其它一些待定的功能。
3.WMI and MOF
WMI和MOF才是问题的实质。对驱动程序,WMI循环出现在.MOF文件中来描述数据。PassThru.Mof展示了怎样设置由ULONG组成的变量。PassThru.mof文件是PassThru_WMI.h文件基础,它会作为编译驱动程序的一部分而建立。
PassThru.rc(驱动程序的资源文件)指向PassThru.mof文件。注意的是,MOF由操作符NdisMofResource指定,必须使用这个名字,而不是作为WDM驱动标准名字的MofResource。
So far as this writer is aware, the need to employ this different form is nowhere documented.。
用户程序用类名(资源信息)与MOF文件中的值相对应。
4.编译环境
可执行文件—驱动程序和两个用户程序是通过DDK编译的,但值得注意的是,后两程序的源程序引用了SDK文件,所以得在源程序作相应的改变。如果你这样做了,请确保你没有使用含空格的路径的名而是用标准的8.3文件名形式。这里提供了VS6工程文件,以便读者有VS编译用户程序(User-Space Programs)。
原文版权:
Copyright ? 2003 Printing Communications Associates, Inc. (PCAUSA). All rights reserved.
PCAUSA does not grant the right to redistribute or publish this article without written permission
版权信息:
由于时间关系,关于版权的信息就不再翻译,本文的目的是为了方便广大不习惯使用英文的驱动及NDIS爱好者学习和研究使用,请尊重作者的劳动成果。未经授权而在商业上使用原作者的文章属侵权行为,后果由使用者自负,本人不承担任何法律责任。
下载本文所提及的源程序:
http://www.wd-3.com/downloads/PassThru2.zip