不知到现在hack小将们还有多少知道ipman这个东西,当时2000年左右在学校的内网大家玩的不亦乐乎。
年龄大了就开始怀旧,在我尝试了n种搜索方法之后,终于找到了当年我翻译的ipman的网卡驱动的说明。可惜的是nankai bbs几经变迁,在上面已经找不到我发的原文。只有一个转载了n层的版本:
http://nkbbs.org/cgi-bin/bbs/bbstcon?board=SysSafe&file=M.947127744.A
在这个叫做“烟雨漓江BBS站”的BBS以及四川大学“蓝色星空“站,居然今天也仍旧能找到别人转载的我写的关于vpacket.vxd的翻译(或者直接google "vpacket.vxd jxj"):
http://bbs.gxnu.cn/bbstcon.php?board=Programming&gid=1286
http://anphia.blog.lsxk.org/wForum/boardcon.php?bid=16&id=202&ftype=6
那时的人还是有节操,转载时不忘注明出处。
也贴在这里博大家一笑:
———————-我是原文分割线—————————————————-
这个,vxd有个说明文档,试着翻译了一下,不当之处请指出….
Windows 95下的直接网卡读写
澳大利亚堪培拉大学
信息科学和工程系
1997.8.6
概论
这个文档是VPACKET的说明.VPACKET是Windows 95下的一个虚拟设备驱动程序,它可以通过WIN32程序对安装在PC机上的任何网卡进行直接读写操作。直接网卡读写对编写网络管理程序和那些想实现自己的意图的编程者是十分有用的。这个驱动程序是P32编程环境[1]的一部分。P32是一个堪培拉大学用于操作系统和协议设计课程的WIN32程序包。
1.介绍
WIN32程序平台不支持低层次的直接的网卡操作.需要这种操作的程序(由于种种原因)必须用一个自定制的虚拟设备驱动程序(VXD).VXD提供一个在底层网络控制接口(NICS)和高层的WIN32程序间的一个服务接口.它的基本结构见图一.
_________________
| 应用程序 |
|_________________|
/|
|
———|——————————————–
|
_____|/___________
| VPACKET |
| VXD |
|____________ ___|
/|
/|
|
_____|/___________________________________________
| |
| NDIS 3.10 |
|___________________________________________________|
/| /| /|
| | |
___|/___ ___|/___ ___|/___
| NIC 0 | | NIC 0 | …………… | NIC N |
|_______| |_______| |_______|
图一: 结构
一个程序必须首先用一个WIN32_API函数:CreateFile将此VXD装入内存,然后才能调用WIN32设备I/O控制函数来实现此VXD提供的功能。
2.关于接口抽象层
正像在图一中所看到的那样,这个虚拟设备驱动程序并没有直接面对已安装好的底层网络控制接口.在网络硬件和VXD之间有一个叫做NDIS 3.10的接口抽象层,使用这种接口抽象层的意图在于保护需要NIC接口的软件不受底层网络适配器特殊硬件细节的影响。因此这个VAPCKET VXD可以方便的同安装在不同机器上的任何NIC接口进行通讯,但这台机器上的网卡必须是支持NDIS的.注意,不同版本的NDIS对网卡的支持有些不同.尤其是微软的Dialup网卡(PPPMAC)不支持NDIS.因为一个普通的NDIS Send函数在这种网卡上传送不了任何数据。此外,这种网卡也不支持NDSI所支持的网卡所具有的数字统计硬件。
3.怎样装入一个VXD
一个WIN32程序使用一个特定的形式调用WIN32_API函数:CreateFile来装入VXD.下面的代码演示了如何装入VAPCKET VXD.
#include <windows.h> HANDLE hVxD; hVxD = CreateFile("\\.\VPACKET.VXD", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED | FILE_FLAG_DELETE_ON_CLOSE, NULL); NULL); if (hVxD == INVALID_HANDLE_VALUE) return SYSERR;
第一个参数说明将要装入的VPACKET.VXD所在的目录.第六个参数应特别注意:它指明此驱动程序支持异步操作(FILE_FLAG_OVERLAPPED),同时也指出当VXD关闭时应当从内存中被释放(FILE_FLAG_DELETE_ON_CLOSE).
此函数要求异步操作立即返回到它的调用者那里,而不必非要等到操作被完成.应用程序必须用另外的方法(下面将要提到)来断定操作是否已经完成.
调用CreateFile函数所返回的句柄不是一个普通的文件句柄.实际上,程序就是通过它来完成设备驱动程序所提供的功能.
VPACKET VxD能被"打开"无数次,每次调用CreateFile函数将返回一个与其它各次不同的句柄.仅仅在第一次调用CreateFile函数时是真正的装入和执行此VxD,其它时刻调用CreateFile函数仅仅是返回一个新句柄而已.
VPACKET VxD的一个显著特征就是不需要安装或者设置,因此没有相应的inf文件.所有的设置工作在这个VxD被执行并被确定的绑定到一个或多个网络接口时被自动完成.
4.怎样从内存中卸载VxD
这个VxD能够被WIN32_API函数CloseHandle所卸载,释放从CreateFile函数所获得的句柄.假如此驱动程序被打开多次,则必须当所有的句柄都被释放时此VxD才被卸载.
5.怎样绑定到网络接口层
当VPACKET VxD被装入和执行时,它必须与一个特定的网络接口控制器发生联系,即绑定.绑定可以通过下面的Bind函数来完成.
int Bind(HANDLE hVxD, BYTE* inBuffer) { HANDLE hEvent; DWORD cbRet; OVERLAPPED ovlp = {0,0,0,0,0}; int result; int cbIn = 5; hEvent = CreateEvent(0, TRUE, 0, NULL); if (!hEvent) return SYSERR; ovlp.hEvent = hEvent; result = DeviceIoControl(hVxD, IOCTL_PROTOCOL_BIND, IOCTL_PROTOCOL_BIND, inBuffer, cbIn, inBuffer, cbIn, &cbRet, &ovlp); if (!result) GetOverlappedResult(hVxD, &ovlp, &cbRet, TRUE); CloseHandle(hEvent); return OK; }
第一个参数是先前调用的CreateFile函数所返回的句柄.第二个参数是命名句柄所将要绑定的适配器的字符串.这个字符串可以从Windows95的注册表的如下目录找到:HKEY_LOCAL_MACHINESystemCurrentControlSetServicesClassNet
注意:对于每一个CreateFile函数所返回的句柄,应用程序再进行任何其他操作之前必须被绑定.
6.设备驱动API函数
一个WIN32程序可以用DeviceIoControl函数来调用设备驱动程序所提供的服务功能.上面所列出的Bind函数,第一个参数是
CreateFile函数所返回的句柄,第二个参数是下列函数代码之一:
IOCTL_PROTOCOL_QUERY_OID 得到详细的目标Object的ID
IOCTL_PROTOCOL_SET_OID 设置详细的目标ObjectID
IOCTL_PROTOCOL_STATISTICS 得到特定网卡(适配器)的状态
IOCTL_PROTOCOL_RESET 复位网卡(适配器)
IOCTL_PROTOCOL_READ 从网络上接受一个包
IOCTL_PROTOCOL_WRITE 在网上发送一个包
IOCTL_PROTOCOL_MACNAME 得到网卡驱动的名称
IOCTL_PROTOCOL_BIND 绑定VPACKET VXD到特定网卡(适配器)
使用以上操作的例子在附录中给出.
7.异步操作
Bind函数说明了异步操作是怎样在WIN32程序中实现的.WIN32_API函数CreateEvent被调用后的返回值存入OVERLAPPED结构的成员hEvent句柄.OVERLAPPED结构中剩下的成员被赋值为0.在调用DevIoControl函数时OVERLAPPED结构体的地址指针被作为最后一个参数传递给设备驱动程序.然后驱动程序便开始进行操作并返回一个值.当驱动程序完成所要求的操作时将发给一动程序便开始进行操作并返回一个值.当驱动程序完成所要求的操作时将发给一个特定的事件一个信号.与此同时WIN32程序可以完成一些其他事情.在绑定结束之前,Bind函数干不了更多的事情.因此在Bind的线程中仅仅调用了WIN32_API函数GetOverlappedResult.这个函数会阻止程序运行,直到特定事件收到操作完成的信号.(因为此函数最后一个参数恒为真实值TRUE).
参数3: 包含指定操作所需要的输入数据的缓冲区的地址
参数4: 上面提到的缓冲区大小
参数5: 保留指定操作的返回信息的缓冲区的地址
参数6: 上面提到的缓冲区大小
参数7: 一个双字(DWORD)变量的地址.这个变量表示驱动程序所返回的字节数.注意:这个变量也被用作GetOverlappedResult函数的参数.
当应用程序需要读出网卡所接受到的数据时,异步输入输出机制的强大优势将会更明显.应用程序(往往如此)不可能预先知道何时数据包将会从网上到达.因此程序可以完成一些其他的处理(如:处理Windows 95的消息)和通过调用GetOverlappedResult函数来检查是否有数据包到达.假如GetOverlappedResult函数返回值为FALSE,同时调用GetLastError函数返回ERROR_IO_PENDING,应用程序就可以知道没有数据包到达.假如GetOverlappedResult函数返回值为TRUE,则应用程序便知道有数据包到达,因此可以进行一些操作.
9.结论
这个VPACKET虚拟设备驱动程序提供给运行于Windows 95下的WIN32应用程序一种简单且有效的直接进行网络接口控制的机制.
在P32编程环境中,利用这个驱动程序,在Comer和Stevens[2]编写的代码基础之上实现了完全的TCP/IP协议.这个程序支持复合网络接口,完全的IP协议和入口功能.
作者的版本中还有一些附加特征:支持IP地址和端口列表.一个局域网中的主机可以使用另一个局域网中的一个可用的IP地址.所有局域网中的主机可以使用端对端(PPP)协议的IP地址连接到互联网(Internet)服务商.
10.参考及附注
[1] 有关信息可在以下网址获得:
http://willow.canberra.edu.au/~chrisc/p32.html
[2] 有关信息在<<用TCP/IP进行网络互连>>第一卷,第二本,第二版查到,由
Prentice-Hall出版.1994.
[3] 关于VPACKET VXD的源代码,可以写信给作者.
chrisc@fir.canberra.edu.au
[4] NAT32是Windows 95下的一个地址翻译包.可从下列网址查到有关信息:
http://willow.canberra.edu.au/~chrisc/nat32.html
http://willow.canberra.edu.au/~chrisc/nat32.html
附录:
IOCTL_PROTOCOL_QUERY_OID
这个操作返回一个特殊目标Object ID.接下来的例子可以实现对当前数据包的过滤.
BYTE iBuf[sizeof(PACKET_OID_DATA) + 128];PPACKET_OID_DATA pOidData = (PPACKET_OID_DATA) iBuf;int result;memset(iBuf, 0, sizeof(iBuf));pOidData->Oid = OID_GEN_CURRENT_PACKET_FILTER;pOidData->Length = 4;result = ControlPacket((HANDLE) etptr->handle, IOCTL_PROTOCOL_QUERY_OID, iBuf, sizeof(PACKET_OID_DATA) + 4, iBuf, sizeof(PACKET_OID_DATA) + 4);if (result == 12) { memcpy(arg1, pOidData->Data, 4); /* arg1 is an int * */ return OK;}return SYSERR;The function ControlPacket is listed at the end of this Appendix.
IOCTL_PROTOCOL_STATISTICS
这个操作返回一个特定的适配器状态,它的使用方法与IOCTL_PROTOCOL_QUERY_OID非常相似,它必须有一个隔离操作,因为底层的NIC所用的机制不同.
IOCTL_PROTOCOL_RESET
这个操作可以复位底层的适配器(网卡).一般不需要.
IOCTL_PROTOCOL_READ
下面的例子将演示这个操作可以返回一个从网上接受的数据包.
int RcvPacket(HANDLE hVxD, BYTE* Buffer, DWORD cbIn){ HANDLE hEvent; HANDLE hEvent; DWORD cbRet = 0; OVERLAPPED ovlp = {0,0,0,0,0}; int result; hEvent = CreateEvent(0, TRUE, 0, NULL); if (!hEvent) return SYSERR; ovlp.hEvent = hEvent; result = DeviceIoControl(hVxD, IOCTL_PROTOCOL_READ, Buffer, cbIn, Buffer, cbIn, &cbRet, &ovlp); if (!result) GetOverlappedResult(hVxD, &ovlp, &cbRet, TRUE); CloseHandle(hEvent); return cbRet;}
注意:cbIn参数对于以太网必须为1514.
IOCTL_PROTOCOL_WRITE
这个操作可以向网上发送一个数据包,它和IOCTL_PROTOCOL_READ的用法非常类似
注意:这个包必须包含一个完整的包头.
IOCTL_PROTOCOL_MACNAME
这个操作返回一个包含网卡驱动程序名字的字符串.
result = ControlPacket((HANDLE) etptr->handle, IOCTL_PROTOCOL_MACNAME, iBuf, 32, iBuf, 32);if (result > 0) { memcpy(arg1, iBuf, result); /* arg1 must be a char * */ return OK; return OK;}return SYSERR;
IOCTL_PROTOCOL_BIND
这个操作将VPACKET绑定到一个特定适配器(网卡),参考前面第五点.
ControlPacketint ControlPacket(HANDLE hVxD, ULONG ioctl, BYTE* inBuffer, DWORD cbIn, BYTE* outBuffer, DWORD cbOut){ HANDLE hEvent; DWORD cbRet = 0; OVERLAPPED ovlp = {0,0,0,0,0}; int result; hEvent = CreateEvent(0, TRUE, 0, NULL); if (!hEvent) return SYSERR; ovlp.hEvent = hEvent; result = DeviceIoControl(hVxD, ioctl, inBuffer, cbIn, outBuffer, cbOut, &cbRet, &ovlp); if (!result) GetOverlappedResult(hVxD, &ovlp, &cbRet, TRUE); CloseHandle(hEvent); return cbRet;}
一个异步从网络上读取数据包的例子
同步输入输出(I/O)一个很大的缺点就是,数据输入和数据处理是连续的.例如:从网上收到一个数据包,就要在下一个数据包到达之前交给应用程序处理.这种工作模式在低速设备上可以很好工作,但在高速设备上就会丢失数据包.
VPACKET VXD支持异步读写操作.例如:应用程序可以使得一个操作在实际的输入输出(I/O)操作结束前就返回.应用程序可以通过一个检查函数在另一个独立的线程中检查实际输入输出(I/O)是否完成.即使这样,仍然会产生数据包丢失问题.除非设备有足够大的读入数据缓冲区来防止数据溢出的情况.换句话说,数据包X被接收下来之后,设备必须能够在数据包X被处理时接收随后而来的数据包
下面给出的例子代码是按照下面所说的方式工作的.
netin函数工作在你的软件初始化时产生的一个独立线程中.netin函数通过调用几次来首先初始化VPACKET驱动程序.当前版本的VPACKET允许拖延64个为完成操作,但是这个限制能够通过改变VPACKET.H中的一个常量来很容易的增加.然后重新编译生成此驱动程序.注意:RcvStart通过调用DeviceIoControl来开始读操作然后立即返回.
netin然后就会进入一段等待代码,这段代码只有在驱动程序被关闭时才会退出.同时,这段代码调用WIN32_API函数WaitForMultipleObjectsEx来等待网上数据包的到来.当数据包真的到来时,就会向更高层的函数传递这个包的指针来供处理(当arp数据包,rarp数据包,ip数据包到来时).这个高层的函数将这个包的指针加入队列当中供以后的处理,然后立即返回.紧跟着就调用RcvStart来补充急待处理的读操作.显然,这种方式很少导致数据包丢失,因为读操作几乎总是一个接一个.
以下就是例子代码:
/* netin.c - netin */#include <windows.h>#include "conf.h"#include "kernel.h"#include "network.h"#include "wether.h"/*------------------------------------------------------------------------
* netin - read packets from an Eth device (non-blocking VPACKET version)
*------------------------------------------------------------------------
*/COMMAND netin(int nargs, char **args){ struct etblk *etptr; struct ep *pep; struct ip *pip; struct ip *pip; int ps, dev, len, i, j, k; struct rcvblk rcvtab[RTAB_SIZE]; struct netif *pni; HANDLE hList[RTAB_SIZE]; Eaddr zero = {0,0,0,0,0,0}; dev = atoi(args[0]); etptr = ð[devtab[dev].dvminor]; if (!etptr->isopen) return SYSERR; /* NOTE: RTAB_SIZE must not be greater than 64 (a WIN32 restriction) */ for (i=0; i<RTAB_SIZE; i++) { /* prime the driver for packet reception */ pep = (struct ep *) getbuf(Net.netpool); rcvtab[i].pep = pep; rcvtab[i].len = 1514; rcvtab[i].active = FALSE; rcvtab[i].intf = etptr->ifnum; RcvStart(etptr->handle, &rcvtab[i]); hList[i] = rcvtab[i].ovlp.hEvent; } Net.netin_pid = getpid(); /* save pid of this thread */