我正在开发一个NDISfilter驱动程序,并且在某些情况下(例如打开Wireshark或单击它的“Interface List”button),我从不调用FilterReceiveNetBufferLists
(networking被阻止)。 但是当我开始捕获, FilterReceiveNetBufferLists
变得正常(networking恢复),这是如此奇怪。
我发现,当我在WinPcap驱动程序的OID原始位置(NPF_IoControl的BIOCQUERYOID&BIOCSETOID开关分支)的NdisFOidRequest
函数中手动返回NDIS_STATUS_FAILURE
时,驱动程序不会阻塞networking(winpcap无法工作)。
NdisFOidRequest
调用有问题吗?
Packet.c中产生OID请求的DeviceIO例程:
case BIOCQUERYOID: case BIOCSETOID: TRACE_MESSAGE(PACKET_DEBUG_LOUD, "BIOCSETOID - BIOCQUERYOID"); // // gain ownership of the Ndis Handle // if (NPF_StartUsingBinding(Open) == FALSE) { // // MAC unbindind or unbound // SET_FAILURE_INVALID_REQUEST(); break; } // Extract a request from the list of free ones RequestListEntry = ExInterlockedRemoveHeadList(&Open->RequestList, &Open->RequestSpinLock); if (RequestListEntry == NULL) { // // Release ownership of the Ndis Handle // NPF_StopUsingBinding(Open); SET_FAILURE_NOMEM(); break; } pRequest = CONTAINING_RECORD(RequestListEntry, INTERNAL_REQUEST, ListElement); // // See if it is an Ndis request // OidData = Irp->AssociatedIrp.SystemBuffer; if ((IrpSp->Parameters.DeviceIoControl.InputBufferLength == IrpSp->Parameters.DeviceIoControl.OutputBufferLength) && (IrpSp->Parameters.DeviceIoControl.InputBufferLength >= sizeof(PACKET_OID_DATA)) && (IrpSp->Parameters.DeviceIoControl.InputBufferLength >= sizeof(PACKET_OID_DATA) - 1 + OidData->Length)) { TRACE_MESSAGE2(PACKET_DEBUG_LOUD, "BIOCSETOID|BIOCQUERYOID Request: Oid=%08lx, Length=%08lx", OidData->Oid, OidData->Length); // // The buffer is valid // NdisZeroMemory(&pRequest->Request, sizeof(NDIS_OID_REQUEST)); pRequest->Request.Header.Type = NDIS_OBJECT_TYPE_OID_REQUEST; pRequest->Request.Header.Revision = NDIS_OID_REQUEST_REVISION_1; pRequest->Request.Header.Size = NDIS_SIZEOF_OID_REQUEST_REVISION_1; if (FunctionCode == BIOCSETOID) { pRequest->Request.RequestType = NdisRequestSetInformation; pRequest->Request.DATA.SET_INFORMATION.Oid = OidData->Oid; pRequest->Request.DATA.SET_INFORMATION.InformationBuffer = OidData->Data; pRequest->Request.DATA.SET_INFORMATION.InformationBufferLength = OidData->Length; } else { pRequest->Request.RequestType = NdisRequestQueryInformation; pRequest->Request.DATA.QUERY_INFORMATION.Oid = OidData->Oid; pRequest->Request.DATA.QUERY_INFORMATION.InformationBuffer = OidData->Data; pRequest->Request.DATA.QUERY_INFORMATION.InformationBufferLength = OidData->Length; } NdisResetEvent(&pRequest->InternalRequestCompletedEvent); if (*((PVOID *) pRequest->Request.SourceReserved) != NULL) { *((PVOID *) pRequest->Request.SourceReserved) = NULL; } // // submit the request // pRequest->Request.RequestId = (PVOID) NPF6X_REQUEST_ID; ASSERT(Open->AdapterHandle != NULL); Status = NdisFOidRequest(Open->AdapterHandle, &pRequest->Request); //Status = NDIS_STATUS_FAILURE; } else { // // Release ownership of the Ndis Handle // NPF_StopUsingBinding(Open); // // buffer too small // SET_FAILURE_BUFFER_SMALL(); break; } if (Status == NDIS_STATUS_PENDING) { NdisWaitEvent(&pRequest->InternalRequestCompletedEvent, 1000); Status = pRequest->RequestStatus; } // // Release ownership of the Ndis Handle // NPF_StopUsingBinding(Open); // // Complete the request // if (FunctionCode == BIOCSETOID) { OidData->Length = pRequest->Request.DATA.SET_INFORMATION.BytesRead; TRACE_MESSAGE1(PACKET_DEBUG_LOUD, "BIOCSETOID completed, BytesRead = %u", OidData->Length); } else { if (FunctionCode == BIOCQUERYOID) { OidData->Length = pRequest->Request.DATA.QUERY_INFORMATION.BytesWritten; if (Status == NDIS_STATUS_SUCCESS) { // // check for the stupid bug of the Nortel driver ipsecw2k.sys v. 4.10.0.0 that doesn't set the BytesWritten correctly // The driver is the one shipped with Nortel client Contivity VPN Client V04_65.18, and the MD5 for the buggy (unsigned) driver // is 3c2ff8886976214959db7d7ffaefe724 *ipsecw2k.sys (there are multiple copies of this binary with the same exact version info!) // // The (certified) driver shipped with Nortel client Contivity VPN Client V04_65.320 doesn't seem affected by the bug. // if (pRequest->Request.DATA.QUERY_INFORMATION.BytesWritten > pRequest->Request.DATA.QUERY_INFORMATION.InformationBufferLength) { TRACE_MESSAGE2(PACKET_DEBUG_LOUD, "Bogus return from NdisRequest (query): Bytes Written (%u) > InfoBufferLength (%u)!!", pRequest->Request.DATA.QUERY_INFORMATION.BytesWritten, pRequest->Request.DATA.QUERY_INFORMATION.InformationBufferLength); Status = NDIS_STATUS_INVALID_DATA; } } TRACE_MESSAGE1(PACKET_DEBUG_LOUD, "BIOCQUERYOID completed, BytesWritten = %u", OidData->Length); } } ExInterlockedInsertTailList(&Open->RequestList, &pRequest->ListElement, &Open->RequestSpinLock); if (Status == NDIS_STATUS_SUCCESS) { SET_RESULT_SUCCESS(sizeof(PACKET_OID_DATA) - 1 + OidData->Length); } else { SET_FAILURE_INVALID_REQUEST(); } break;
三个filterOID例程:
_Use_decl_annotations_ NDIS_STATUS NPF_OidRequest( NDIS_HANDLE FilterModuleContext, PNDIS_OID_REQUEST Request ) { POPEN_INSTANCE Open = (POPEN_INSTANCE) FilterModuleContext; NDIS_STATUS Status; PNDIS_OID_REQUEST ClonedRequest=NULL; BOOLEAN bSubmitted = FALSE; PFILTER_REQUEST_CONTEXT Context; BOOLEAN bFalse = FALSE; TRACE_ENTER(); do { Status = NdisAllocateCloneOidRequest(Open->AdapterHandle, Request, NPF6X_ALLOC_TAG, &ClonedRequest); if (Status != NDIS_STATUS_SUCCESS) { TRACE_MESSAGE(PACKET_DEBUG_LOUD, "FilerOidRequest: Cannot Clone Request\n"); break; } Context = (PFILTER_REQUEST_CONTEXT)(&ClonedRequest->SourceReserved[0]); *Context = Request; bSubmitted = TRUE; // // Use same request ID // ClonedRequest->RequestId = Request->RequestId; Open->PendingOidRequest = ClonedRequest; Status = NdisFOidRequest(Open->AdapterHandle, ClonedRequest); if (Status != NDIS_STATUS_PENDING) { NPF_OidRequestComplete(Open, ClonedRequest, Status); Status = NDIS_STATUS_PENDING; } }while (bFalse); if (bSubmitted == FALSE) { switch(Request->RequestType) { case NdisRequestMethod: Request->DATA.METHOD_INFORMATION.BytesRead = 0; Request->DATA.METHOD_INFORMATION.BytesNeeded = 0; Request->DATA.METHOD_INFORMATION.BytesWritten = 0; break; case NdisRequestSetInformation: Request->DATA.SET_INFORMATION.BytesRead = 0; Request->DATA.SET_INFORMATION.BytesNeeded = 0; break; case NdisRequestQueryInformation: case NdisRequestQueryStatistics: default: Request->DATA.QUERY_INFORMATION.BytesWritten = 0; Request->DATA.QUERY_INFORMATION.BytesNeeded = 0; break; } } TRACE_EXIT(); return Status; } //------------------------------------------------------------------- _Use_decl_annotations_ VOID NPF_CancelOidRequest( NDIS_HANDLE FilterModuleContext, PVOID RequestId ) { POPEN_INSTANCE Open = (POPEN_INSTANCE) FilterModuleContext; PNDIS_OID_REQUEST Request = NULL; PFILTER_REQUEST_CONTEXT Context; PNDIS_OID_REQUEST OriginalRequest = NULL; BOOLEAN bFalse = FALSE; FILTER_ACQUIRE_LOCK(&Open->OIDLock, bFalse); Request = Open->PendingOidRequest; if (Request != NULL) { Context = (PFILTER_REQUEST_CONTEXT)(&Request->SourceReserved[0]); OriginalRequest = (*Context); } if ((OriginalRequest != NULL) && (OriginalRequest->RequestId == RequestId)) { FILTER_RELEASE_LOCK(&Open->OIDLock, bFalse); NdisFCancelOidRequest(Open->AdapterHandle, RequestId); } else { FILTER_RELEASE_LOCK(&Open->OIDLock, bFalse); } } //------------------------------------------------------------------- _Use_decl_annotations_ VOID NPF_OidRequestComplete( NDIS_HANDLE FilterModuleContext, PNDIS_OID_REQUEST Request, NDIS_STATUS Status ) { POPEN_INSTANCE Open = (POPEN_INSTANCE) FilterModuleContext; PNDIS_OID_REQUEST OriginalRequest; PFILTER_REQUEST_CONTEXT Context; BOOLEAN bFalse = FALSE; TRACE_ENTER(); Context = (PFILTER_REQUEST_CONTEXT)(&Request->SourceReserved[0]); OriginalRequest = (*Context); // // This is an internal request // if (OriginalRequest == NULL) { TRACE_MESSAGE1(PACKET_DEBUG_LOUD, "Status= %p", Status); NPF_InternalRequestComplete(Open, Request, Status); TRACE_EXIT(); return; } FILTER_ACQUIRE_LOCK(&Open->OIDLock, bFalse); ASSERT(Open->PendingOidRequest == Request); Open->PendingOidRequest = NULL; FILTER_RELEASE_LOCK(&Open->OIDLock, bFalse); // // Copy the information from the returned request to the original request // switch(Request->RequestType) { case NdisRequestMethod: OriginalRequest->DATA.METHOD_INFORMATION.OutputBufferLength = Request->DATA.METHOD_INFORMATION.OutputBufferLength; OriginalRequest->DATA.METHOD_INFORMATION.BytesRead = Request->DATA.METHOD_INFORMATION.BytesRead; OriginalRequest->DATA.METHOD_INFORMATION.BytesNeeded = Request->DATA.METHOD_INFORMATION.BytesNeeded; OriginalRequest->DATA.METHOD_INFORMATION.BytesWritten = Request->DATA.METHOD_INFORMATION.BytesWritten; break; case NdisRequestSetInformation: OriginalRequest->DATA.SET_INFORMATION.BytesRead = Request->DATA.SET_INFORMATION.BytesRead; OriginalRequest->DATA.SET_INFORMATION.BytesNeeded = Request->DATA.SET_INFORMATION.BytesNeeded; break; case NdisRequestQueryInformation: case NdisRequestQueryStatistics: default: OriginalRequest->DATA.QUERY_INFORMATION.BytesWritten = Request->DATA.QUERY_INFORMATION.BytesWritten; OriginalRequest->DATA.QUERY_INFORMATION.BytesNeeded = Request->DATA.QUERY_INFORMATION.BytesNeeded; break; } (*Context) = NULL; NdisFreeCloneOidRequest(Open->AdapterHandle, Request); NdisFOidRequestComplete(Open->AdapterHandle, OriginalRequest, Status); TRACE_EXIT(); }
下面是我从Jeffrey收到的邮件,我认为这是这个问题的最佳答案:)
数据包过滤器对于LWF和协议的工作方式不同。 让我给你一些背景。 我确定,您已经了解了其中的一些情况,但是查看基本信息总是有帮助的,所以我们可以确定我们都在同一页面上。 NDIS数据路径像树一样组织:
数据包过滤发生在这个堆栈的两个地方:
(a)一次在微型端口硬件中,
(b)在堆栈顶部,正好在协议下面。
NDIS将分别跟踪每个协议的数据包过滤器,以提高效率。 如果一个协议要求查看所有的数据包(混杂模式),那么不是所有的协议都必须对所有的数据流进行排序。 所以实际上,系统中有( P + 1)个不同的数据包过滤器,其中P是协议的数量:
现在如果有所有这些不同的数据包过滤器,OID_GEN_CURRENT_PACKET_FILTER实际上是如何工作的? NDIS所做的是NDIS跟踪每个协议的数据包过滤器,但是也会合并微型堆栈顶部的过滤器。 所以假设protocol0请求A + B的包过滤器,protocol1请求C的包过滤器,protocol2请求B + D的包过滤器:
然后在堆栈顶部,NDIS将数据包过滤器合并到A + B + C + D。 这是什么被发送到过滤器堆栈,并最终到微型端口。
由于这个合并过程,无论协议2设置为数据包过滤器,协议2都不能影响其他协议。 所以协议不必担心“共享”数据包过滤器。 但是,LWF也是如此。 如果LWF1决定设置新的数据包过滤器,则不会合并:
在上图中,LWF1决定将包过滤器更改为C + E。 这覆盖了A + B + C + D的协议的数据包过滤器,意味着标志A,B和D永远不会使它进入硬件。 如果协议依赖于标志A,B或D,则协议的功能将被打破。
这是设计的 – LWF有很大的权力,他们可以做任何事情的堆栈。 它们被设计为有权否决所有其他协议的数据包过滤器。 但在你的情况下,你不想乱用其他协议; 你希望你的过滤器对系统的其他部分有最小的影响。
所以你想要做的是始终跟踪包过滤器是什么,并且永远不要从当前包过滤器中删除标志。 这意味着您应该在过滤器附加时查询数据包过滤器,并且每当看到OID_GEN_CURRENT_PACKET_FILTER从上往下时更新缓存的值。
如果您的用户模式应用程序需要比当前数据包过滤器更多的标志,则可以发出OID并添加其他标志。 这意味着硬件的数据包过滤器会有更多的标志。 但是没有协议的包过滤器会改变,所以协议仍然会看到相同的东西。
在上面的例子中,过滤器LWF1打得不错。 尽管LWF1只关心标志E,但LWF1仍然传递了所有的标志A,B,C和D,因为LWF1知道它上面的协议需要设置这些标志。
管理这个代码并不算太坏,一旦你了解了需要做什么来管理数据包过滤器:
始终跟踪上述协议的最新数据包过滤器。
千万不要让NIC看到比协议包过滤器标记更少的包过滤器。
根据需要添加您自己的标志。
好的,希望能给你一个关于数据包过滤器以及如何管理的好主意。 下一个问题是如何将“混杂模式”和“非混杂模式”映射到实际的标志? 我们仔细的定义这两种模式:
非混杂模式 :捕获工具只能看到操作系统通常收到的接收流量。 如果硬件过滤流量,那么我们不希望看到流量。 用户想要在正常状态下诊断本地操作系统。
混杂模式 :给捕获工具尽可能多的接收数据包 – 理想情况下,每一个在网络上传输的位。 数据包是否指向本地主机并不重要。 用户想要诊断网络,所以希望看到网络上发生的一切。
我认为当你这样看的时候,包过滤标志的后果是非常简单的。 对于非混杂模式,请勿更改数据包过滤器。 只要让硬件包过滤器是操作系统想要的就可以了。 然后,对于混杂模式,添加NDIS_PACKET_TYPE_PROMISCUOUS标志,NIC硬件将给你所有可能的东西。
所以如果LWF这么简单,为什么旧的基于协议的NPF驱动程序需要更多的标志呢? 旧的基于协议的驱动程序有几个问题:
它不能得到“非混杂模式”是完全正确的
它不能轻易捕获其他协议的发送包
NPF协议的第一个问题是不能很好地实现我们对“非混杂模式”的定义。 如果NPF协议希望看到接收流量,就像操作系统看到的一样,那么它应该使用什么样的数据包过滤器? 如果它设置零包过滤器,那么NPF将不会看到任何流量。 所以NPF可以设置Directed | Broadcast | Multicast的包过滤。 但是这只是TCPIP和其他协议设置的假设。 如果TCPIP决定设置一个Promiscuous标志(某些套接字标志导致这种情况发生),那么NPF实际上会看到比TCPIP看到的更少的数据包,这是错误的。 但是,如果NPF设置混杂的标志,那么它会看到比TCPIP更多的流量,这也是错误的。 所以捕获协议很难决定设置哪些标记,以便看到与OS剩余部分完全相同的数据包。 LWFs没有这个问题,因为在所有协议的过滤器被合并之后,LWF可以看到组合的OID。
NPF协议的第二个问题是它需要回送模式来捕获发送的数据包。 LWF不需要回送 – 事实上,这将是有害的。 让我们使用相同的图来看看为什么。 NPF在混杂模式下捕获接收路径:
现在让我们看看收到单播数据包时会发生什么:
由于数据包与硬件的过滤器匹配,数据包出现在堆栈中。 然后,当数据包到达协议层时,NDIS将数据包分配给协议tcpip和npf,因为两个协议的数据包过滤器都与数据包匹配。 所以这工作得很好。
但现在发送路径是棘手的:
tcpip发送了一个包,但npf从来没有机会看到它! 为了解决这个问题,NDIS增加了“回环”包过滤标志的概念。 这个标志有点特别,因为它不会去硬件。 相反,环回数据包过滤器告诉NDIS将所有发送流量反弹回接收路径,以便像npf这样的诊断工具可以看到数据包。 它看起来像这样:
现在环回路径实际上只用于诊断工具,所以我们没有花太多的时间来优化它。 而且,由于这意味着所有的发送数据包都会在堆栈上传输两次(一次是正常的发送路径,另一次是接收路径),所以至少会使CPU成本增加一倍。 这就是为什么我说NDIS LWF能够以比协议更高的吞吐量捕获,因为LWF不需要回送路径。
为什么不? 为什么LWF不需要回送? 那么如果你回头看看最后几张图,你会发现我们所有的LWF都看到了所有的流量 – 发送和接收 – 没有任何回环。 所以LWF可以满足所有流量的需求,而不需要麻烦回环。 这就是为什么LWF通常不应该设置任何回送标志。
好的,那封电子邮件比我想要的要长,但是我希望清除包过滤器,回送路径以及LWF与协议不同的一些问题。 如果有什么不清楚的地方,或者图表没有通过,请告诉我。