请见<电脑编程技巧与维护>杂志2001年1期.

解决方案 »

  1.   

    转贴...这个文档是VPACKET的说明.VPACKET是Windows 95下的一个虚拟设备驱动
    程序,它可以通过WIN32程序对安装在PC机上的任何网卡进行直接读写操作.
    直接网卡读写对编写网络管理程序和那些想实现自己的意图的编程者是十分有
    用的.这个驱动程序是P32编程环境[1]的一部分.P32是一个堪培拉大学用于操
    作系统和协议设计课程的WIN32程序包.1.介绍
        WIN32程序平台不支持低层次的直接的网卡操作.需要这种操作的程序(由于
    种种原因)必须用一个自定制的虚拟设备驱动程序(VXD).VXD提供一个在底层网
    络控制接口(NICS)和高层的WIN32程序间的一个服务接口.它的基本结构见图一.    _________________
        &brvbar;    应用程序    &brvbar;
        &brvbar;_________________&brvbar;
              /&brvbar;          &brvbar;
    -----------&brvbar;--------------------------------------------
              &brvbar;
        _____\&brvbar;/___________
        &brvbar;    VPACKET      &brvbar;
        &brvbar;      VXD        &brvbar;
        &brvbar;_________________&brvbar;
              /&brvbar;
              /&brvbar;          &brvbar;
        _____\&brvbar;/___________________________________________
        &brvbar;                                                  &brvbar;
        &brvbar;                    NDIS 3.10                    &brvbar;
        &brvbar;___________________________________________________&brvbar;
            /&brvbar;\        /&brvbar;\                        /&brvbar;        &brvbar;          &brvbar;                          &brvbar;
        ___\&brvbar;/___  ___\&brvbar;/___                  ___\&brvbar;/___
        &brvbar; NIC 0 &brvbar;  &brvbar; NIC 0 &brvbar; ...............  &brvbar; NIC N &brvbar;
        &brvbar;_______&brvbar;  &brvbar;_______&brvbar;                  &brvbar;_______&brvbar;
                        图一:  结构
        一个程序必须首先用一个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 &brvbar; GENERIC_WRITE,
                          0,
                          NULL,
                          OPEN_EXISTING,
                          FILE_ATTRIBUTE_NORMAL &brvbar; FILE_FLAG_OVERLAPPED &brvbar;
                          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_MACHINE\System\CurrentControlSet\Services\Class\Net
        注意:对于每一个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的源代码,可以写信给作者.
          [email protected]
    [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绑定到一个特定适配器(网卡),参考前面第五点.
    ControlPacket
    int 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 = &eth;[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 */
        signal(Net.sema);                      /* signal that we're running */    while (etptr->isopen) {                /* netin main loop */
            i = WaitForMultipleObjectsEx(RTAB_SIZE, hList, FALSE, INFINITE, FALSE);
    if (i == WAIT_FAILED)  break;
            for (j=0; j<RTAB_SIZE; j++)
                if (hList[i] == rcvtab[j].ovlp.hEvent) break;
            k = j;
            if (!etptr->isopen) break;  /* exit, device has been closed */
            GetOverlappedResult((HANDLE) etptr->handle,
                                        &rcvtab[k].ovlp,
                                        &rcvtab[k].cbRet,                                    &rcvtab[k].cbRet,
                                        FALSE);
            pep = rcvtab[k].pep;
            pep->ep_len = rcvtab[k].cbRet;      /* record packet length */
            pep->ep_ifn = rcvtab[k].intf;      /* record interface # */
            pni = &nif[pep->ep_ifn];// The following check for reflected packets really belongs in the VPACKET
    // device driver. For most (but not all) NDIS3 drivers the reflected packet
    // contains the addr placed in the src addr field by ethwrite, while the
    // packet actually transmitted contains the real physical adapter address.
    // The packet reflected to OTHER bound protocols also contains the real
    // adapter address.
            if (net2hs(pep->ep_type) == EPT_IP) {
                pip = (struct ip *) pep->ep_data;
                if (blkequ(&pip->ip_src, &pni->ni_ip, IP_ALEN)) {
                    disable(ps);
                    freebuf(pep);
                    goto reinit;
                }
                if (blkequ(&pip->ip_dst, &pni->ni_other, IP_ALEN)) {
                    disable(ps);
                    freebuf(pep);
                    goto reinit;
                }
                if (!pni->ni_ovalid) {
                    if (blkequ(&pip->ip_dst, &pni->ni_ip, IP_ALEN))
                        if (blkequ(pep->ep_src, pni->ni_hwa.ha_addr, EP_ALEN)) {
                            pni->ni_other = pip->ip_src;
                            pni->ni_ovalid = 1;
                            p32send(Net.netstart_pid, OK);
    }
                }
            }
            pni->ni_ioctets += pep->ep_len;
            if (blkequ(pni->ni_hwa.ha_addr, pep->ep_dst, EP_ALEN))        if (blkequ(pni->ni_hwa.ha_addr, pep->ep_dst, EP_ALEN))
                pni->ni_iucast++;
            else
                pni->ni_inucast++;
            pep->ep_type = net2hs(pep->ep_type);
            pep->ep_order = EPO_NET;
            disable(ps);
            switch (pep->ep_type) {
                case EPT_ARP:  arp_in(&nif[etptr->ifnum], pep);  break;
                case EPT_RARP:  rarp_in(&nif[etptr->ifnum], pep); break;
                case EPT_IP:    ip_in(&nif[etptr->ifnum], pep);  break;
                default:
            pni->ni_iunkproto++;
            freebuf(pep);
            }reinit: CloseHandle((HANDLE) rcvtab[k].ovlp.hEvent);
            rcvtab[k].active = FALSE;
            restore(ps);
            pep = (struct ep *) getbuf(Net.netpool);  /* use getbufi */
            disable(ps);
            /* move all handles up by one to make space at the end for a new one */
            for (j=i; j<RTAB_SIZE-1; i++)
                hList[i] = hList[++j];        rcvtab[k].pep = pep;
            rcvtab[k].len = 1514;
            RcvStart(etptr->handle, &rcvtab[k]);
            hList[RTAB_SIZE-1] = rcvtab[k].ovlp.hEvent;  // add to end of list
            restore(ps);
        }
        /* if we ever get here, someone has closed the device */
        for (i=0; i<RTAB_SIZE; i++)            /* delete events and buffers */
            if (rcvtab[i].active) {
                CloseHandle((HANDLE) rcvtab[i].ovlp.hEvent);
                freebuf(rcvtab[i].pep);            freebuf(rcvtab[i].pep);
                rcvtab[i].active = FALSE;
            }
        return SYSERR;                          /* netin will terminate */
    }
    LOCAL RcvStart(HANDLE hVxD,
                  struct rcvblk *prb)
    {
        HANDLE      hEvent;
        int        result;
        hEvent = CreateEvent(0, TRUE, 0, NULL);    /* manual reset */
        prb->ovlp.Internal = 0;
        prb->ovlp.InternalHigh = 0;
        prb->ovlp.Offset = 0;
        prb->ovlp.OffsetHigh = 0;
        prb->ovlp.hEvent = hEvent;
        prb->cbRet = 0;
        if (!hEvent)
            return SYSERR;    result = DeviceIoControl(hVxD,
                                IOCTL_PROTOCOL_READ,
                                &prb->pep->ep_eh,
                                prb->len,
                                &prb->pep->ep_eh,
                                prb->len,
                                &prb->cbRet,
                                &prb->ovlp);
        if (result)
            return SYSERR;  /* operation completed or something went wrong */
        prb->active = TRUE;
        return OK;          /* operation is pending */
    }
    --