diff --git a/src/xdplwf/generic.c b/src/xdplwf/generic.c index bc92bc41..4b28c205 100644 --- a/src/xdplwf/generic.c +++ b/src/xdplwf/generic.c @@ -622,6 +622,8 @@ XdpGenericCleanupInterface( XdpDeregisterInterface(Generic->Registration); Generic->Registration = NULL; } + + XdpPktMonUntrackInterface(Generic); } _IRQL_requires_max_(PASSIVE_LEVEL) @@ -665,6 +667,7 @@ XdpGenericAttachInterface( KeInitializeEvent(&Generic->Tx.Datapath.ReadyEvent, NotificationEvent, FALSE); KeInitializeEvent(&Generic->Rx.Datapath.ReadyEvent, NotificationEvent, FALSE); XdpInitializeReferenceCount(&Generic->ReferenceCount); + XdpPktMonInitializeInterface(Generic); Generic->Filter = Filter; Generic->NdisFilterHandle = NdisFilterHandle; Generic->IfIndex = IfIndex; @@ -693,6 +696,11 @@ XdpGenericAttachInterface( goto Exit; } + Status = XdpPktMonTrackInterface(Generic); + if (!NT_SUCCESS(Status)) { + goto Exit; + } + Status = XdpInitializeCapabilities( &Generic->Capabilities, &DriverApiVersion); @@ -714,8 +722,6 @@ XdpGenericAttachInterface( goto Exit; } - XdpPktMonRegisterInterface(&Generic->PktMonContext, Generic->IfIndex); - RtlZeroMemory(AddIf, sizeof(*AddIf)); AddIf->InterfaceCapabilities = &Generic->InternalCapabilities; AddIf->RemoveInterfaceComplete = XdpGenericRemoveInterfaceComplete; @@ -747,8 +753,6 @@ XdpGenericDetachInterface( // XdpIfRemoveInterfaces(&Generic->XdpIfInterfaceHandle, 1); } - - XdpPktMonUnregisterInterface(&Generic->PktMonContext); } VOID diff --git a/src/xdplwf/generic.h b/src/xdplwf/generic.h index a199eded..549b8f91 100644 --- a/src/xdplwf/generic.h +++ b/src/xdplwf/generic.h @@ -58,9 +58,15 @@ typedef struct _XDP_LWF_GENERIC { } Tx; // - // Protected by NDIS driver stack synchronization. + // Pktmon context. Protected by the pktmon rundown reference. // XDP_INTERFACE_PKTMON_CONTEXT *PktMonContext; + PEX_RUNDOWN_REF_CACHE_AWARE PktMonRundownRef; + + // + // Pktmon generic list entry. Protected by the pktmon generic list lock. + // + LIST_ENTRY PktMonLink; } XDP_LWF_GENERIC; typedef enum _XDP_LWF_GENERIC_INJECTION_TYPE { diff --git a/src/xdplwf/pktmon.c b/src/xdplwf/pktmon.c index 523c66a5..6b8baa42 100644 --- a/src/xdplwf/pktmon.c +++ b/src/xdplwf/pktmon.c @@ -16,27 +16,188 @@ static const NPI_MODULEID NPI_XDP_GENERIC_PKTMON_CLNT_MODULEID = { } }; +// +// List of all generic bindings. Used for dynamic pktmon registration. Protected by the pktmon +// generic list lock. +// +static LIST_ENTRY XdpPktMonGenericList; +static EX_PUSH_LOCK XdpPktMonGenericListLock; + +static +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +XdpPktMonRegisterInterface( + _Outptr_result_maybenull_ XDP_INTERFACE_PKTMON_CONTEXT **PktMonContext, + _In_ NET_IFINDEX IfIndex + ) +{ + + NTSTATUS Status; + XDP_INTERFACE_PKTMON_CONTEXT *Context = NULL; + PKTMON_COMPONENT_PROPERTY Property = {0}; + + TraceEnter(TRACE_LWF, "IfIndex=%d", IfIndex); + + *PktMonContext = NULL; + + DECLARE_CONST_UNICODE_STRING(DriverName, L"xdp.sys"); + DECLARE_CONST_UNICODE_STRING(Description, L"XDP GENERIC network inspection"); + + Context = ExAllocatePoolZero(NonPagedPoolNx, sizeof(*Context), POOLTAG_PKTMON); + if (Context == NULL) { + TraceError(TRACE_LWF, "IfIndex=%u PktMon allocation failed", IfIndex); + Status = STATUS_NO_MEMORY; + goto Exit; + } + + Status = + PktMonClntComponentRegister( + &Context->PktMonComp, + &DriverName, + &Description, + PktMonComp_Filter, + PktMonPayload_Ethernet); + if (!NT_SUCCESS(Status)) { + TraceError( + TRACE_LWF, "IfIndex=%u PktMonClntComponentRegister failed Status=%!STATUS!", + IfIndex, Status); + goto Exit; + } + + Property.Id = PktMonCompProp_MiniportIfIndex; + Property.MiniportIfIndex = IfIndex; + Status = + PktMonClntSetComponentProperty( + &Context->PktMonComp, + &Property); + if (!NT_SUCCESS(Status)) { + TraceError( + TRACE_LWF, "IfIndex=%u PktMonClntSetComponentProperty failed Status=%!STATUS!", + IfIndex, Status); + goto Exit; + } + + *PktMonContext = Context; + TraceInfo(TRACE_LWF, "PktMon context created IfIndex=%u, Context=%p", IfIndex, Context); + +Exit: + if (!NT_SUCCESS(Status)) { + if (Context != NULL) { + PktMonClntComponentUnregister(&Context->PktMonComp); + ExFreePoolWithTag(Context, POOLTAG_PKTMON); + } + } + + TraceExitStatus(TRACE_LWF); +} + +static +_IRQL_requires_max_(PASSIVE_LEVEL) +VOID +XdpPktMonUnregisterInterface( + _Inout_ XDP_INTERFACE_PKTMON_CONTEXT **PktMonContext + ) +{ + XDP_INTERFACE_PKTMON_CONTEXT *Context = *PktMonContext; + + TraceEnter(TRACE_LWF, "Context=%p", Context); + + if (Context != NULL) { + PktMonClntComponentUnregister(&Context->PktMonComp); + ExFreePoolWithTag(Context, POOLTAG_PKTMON); + } + + *PktMonContext = NULL; + + TraceExitSuccess(TRACE_LWF); +} + static +_IRQL_requires_max_(PASSIVE_LEVEL) VOID XdpPktMonRegistrationCallback( VOID ) { // - // We don't yet support pktmon tracing for existing XDP interfaces in dynamic - // pktmon load scenarios (pktmon loads after XDP interface is established). + // PktMon was loaded. + // + // Enumerate generic bindings and register them with pktmon. // + + TraceEnter(TRACE_LWF, "PktMonRegistrationCallback"); + + RtlAcquirePushLockExclusive(&XdpPktMonGenericListLock); + + LIST_ENTRY *Entry = XdpPktMonGenericList.Flink; + while (Entry != &XdpPktMonGenericList) { + XDP_LWF_GENERIC *Generic = CONTAINING_RECORD(Entry, XDP_LWF_GENERIC, PktMonLink); + Entry = Entry->Flink; + + ASSERT(Generic->PktMonContext == NULL); + + XdpPktMonRegisterInterface(&Generic->PktMonContext, Generic->IfIndex); + if (Generic->PktMonContext != NULL) { + ExReInitializeRundownProtectionCacheAware(Generic->PktMonRundownRef); + TraceInfo( + TRACE_LWF, "PktMon dynamic register IfIndex=%u Context=%p", + Generic->IfIndex, Generic->PktMonContext); + } + } + + RtlReleasePushLockExclusive(&XdpPktMonGenericListLock); + + TraceExitSuccess(TRACE_LWF); } static +_IRQL_requires_max_(PASSIVE_LEVEL) VOID XdpPktMonClientCleanupCallback( VOID ) { + // + // PktMon is unloading. + // + // Enumerate existing generic bindings and unregister them with pktmon. + // + // N.B. By this time, the PktMonClnt library shim has disallowed new calls across the PktMon + // NPI, so the unregistration attempts will effectively be no-ops. This is fine because + // neither the PktMon subsystem nor the PktMonClnt library shim require explicit + // unregistration of edges or components. However, attempting unregistration at this time + // frees context memory and provides symmetry and simplicity for this XDP pktmon module. + + TraceEnter(TRACE_LWF, "PktMonClientCleanupCallback"); + + RtlAcquirePushLockExclusive(&XdpPktMonGenericListLock); + + LIST_ENTRY *Entry = XdpPktMonGenericList.Flink; + while (Entry != &XdpPktMonGenericList) { + XDP_LWF_GENERIC *Generic = CONTAINING_RECORD(Entry, XDP_LWF_GENERIC, PktMonLink); + Entry = Entry->Flink; + + // + // Wait for any in-progress datapath references to drain, then free + // the pktmon context. The component is already unregistered by + // PktMonDetachProvider (which zeroed the component context). + // Only wait if PktMonContext is non-NULL (rundown is active). + // + if (Generic->PktMonContext != NULL) { + ExWaitForRundownProtectionReleaseCacheAware(Generic->PktMonRundownRef); + XdpPktMonUnregisterInterface(&Generic->PktMonContext); + } + + TraceInfo(TRACE_LWF, "PktMon dynamic unregister IfIndex=%u", Generic->IfIndex); + } + + RtlReleasePushLockExclusive(&XdpPktMonGenericListLock); + + TraceExitSuccess(TRACE_LWF); } static +_IRQL_requires_max_(PASSIVE_LEVEL) VOID XdpPktMonClientCompNotifyCallback( _In_ PKTMON_COMPONENT_CONTEXT *CompContext @@ -48,7 +209,7 @@ XdpPktMonClientCompNotifyCallback( _IRQL_requires_max_(DISPATCH_LEVEL) VOID XdpPktMonLogDrop( - _In_ XDP_INTERFACE_PKTMON_CONTEXT *PktMonContext, + _In_ XDP_LWF_GENERIC *Generic, _In_ NET_BUFFER_LIST *NetBufferLists, _In_ BOOLEAN UseOnlyFirstNbl, _In_ PKTMON_DIRECTION Direction, @@ -56,19 +217,17 @@ XdpPktMonLogDrop( _In_ XDP_PKTMON_DROP_LOCATION DropLocation ) { - // - // If PktMonContext is non-NULL, PktMon must be enabled. - // - ASSERT(!XdpDisablePktMon || (PktMonContext == NULL)); + if (XdpDisablePktMon) { + goto Exit; + } - // - // Drop reason must be in range [0x80000000 - 0xFFFFFFFF] per PktMon guidance. - // Drop location must be in range [0 - 0x7FFFFFFF] per PktMon guidance. - // + if (!ExAcquireRundownProtectionCacheAware(Generic->PktMonRundownRef)) { + goto Exit; + } - if ((PktMonContext != NULL) && PktMonContext->PktMonComp.DropEnabled) { + if (Generic->PktMonContext->PktMonComp.DropEnabled) { PktMonClntNblDrop( - &PktMonContext->PktMonComp, + &Generic->PktMonContext->PktMonComp, NetBufferLists, PktMonPayload_Ethernet, NULL, // PacketHeaderInformation @@ -77,102 +236,137 @@ XdpPktMonLogDrop( DropReason, DropLocation); } + + ExReleaseRundownProtectionCacheAware(Generic->PktMonRundownRef); + +Exit: + + return; } _IRQL_requires_max_(PASSIVE_LEVEL) VOID -XdpPktMonRegisterInterface( - _Outptr_result_maybenull_ XDP_INTERFACE_PKTMON_CONTEXT **PktMonContext, - _In_ NET_IFINDEX IfIndex +XdpPktMonInitializeInterface( + _Inout_ XDP_LWF_GENERIC *Generic ) { - NTSTATUS Status; - XDP_INTERFACE_PKTMON_CONTEXT *Context = NULL; - PKTMON_COMPONENT_PROPERTY Property = {0}; - TraceEnter(TRACE_LWF, "IfIndex=%d", IfIndex); - - *PktMonContext = NULL; + TraceEnter(TRACE_LWF, "IfIndex=%u", Generic->IfIndex); if (XdpDisablePktMon) { Status = STATUS_SUCCESS; goto Exit; } - DECLARE_CONST_UNICODE_STRING(DriverName, L"xdp.sys"); - DECLARE_CONST_UNICODE_STRING(Description, L"XDP GENERIC network inspection"); + InitializeListHead(&Generic->PktMonLink); - Context = ExAllocatePoolZero(NonPagedPoolNx, sizeof(*Context), POOLTAG_PKTMON); - if (Context == NULL) { - TraceError( - TRACE_LWF, "IfIndex=%u PktMon allocation failed", - IfIndex); - Status = STATUS_NO_MEMORY; +Exit: + + TraceExitSuccess(TRACE_LWF); +} + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +XdpPktMonTrackInterface( + _Inout_ XDP_LWF_GENERIC *Generic + ) +{ + NTSTATUS Status; + + TraceEnter(TRACE_LWF, "IfIndex=%u", Generic->IfIndex); + + if (XdpDisablePktMon) { + Status = STATUS_SUCCESS; goto Exit; } - Status = - PktMonClntComponentRegister( - &Context->PktMonComp, - &DriverName, - &Description, - PktMonComp_Filter, - PktMonPayload_Ethernet); - if (!NT_SUCCESS(Status)) { - TraceError( - TRACE_LWF, "IfIndex=%u PktMonClntComponentRegister failed Status=%!STATUS!", - IfIndex, Status); + // + // Initialize members that are necessary regardless of registration with PktMon service. + // Errors here are returned to caller. + // + + Generic->PktMonRundownRef = + ExAllocateCacheAwareRundownProtection(NonPagedPoolNx, POOLTAG_PKTMON); + if (Generic->PktMonRundownRef == NULL) { + TraceError(TRACE_LWF, "IfIndex=%u PktMonRundownRef allocation failed", Generic->IfIndex); + Status = STATUS_NO_MEMORY; goto Exit; } + ExWaitForRundownProtectionReleaseCacheAware(Generic->PktMonRundownRef); - Property.Id = PktMonCompProp_MiniportIfIndex; - Property.MiniportIfIndex = IfIndex; - Status = - PktMonClntSetComponentProperty( - &Context->PktMonComp, - &Property); - if (!NT_SUCCESS(Status)) { - TraceError( - TRACE_LWF, "IfIndex=%u PktMonClntSetComponentProperty failed Status=%!STATUS!", - IfIndex, Status); - goto Exit; + RtlAcquirePushLockExclusive(&XdpPktMonGenericListLock); + + // + // Add interface to global tracking. + // + InsertTailList(&XdpPktMonGenericList, &Generic->PktMonLink); + + // + // Attempt registration with PktMon service. + // Errors here are ignored, as PktMon service may not be currently running. + // + XdpPktMonRegisterInterface(&Generic->PktMonContext, Generic->IfIndex); + if (Generic->PktMonContext != NULL) { + ExReInitializeRundownProtectionCacheAware(Generic->PktMonRundownRef); } - *PktMonContext = Context; - TraceInfo(TRACE_LWF, "PktMon context created IfIndex=%u, Context=%p", IfIndex, Context); + RtlReleasePushLockExclusive(&XdpPktMonGenericListLock); + + Status = STATUS_SUCCESS; Exit: - if (!NT_SUCCESS(Status)) { - if (Context != NULL) { - PktMonClntComponentUnregister(&Context->PktMonComp); - ExFreePoolWithTag(Context, POOLTAG_PKTMON); - } - } TraceExitStatus(TRACE_LWF); + + return Status; } _IRQL_requires_max_(PASSIVE_LEVEL) VOID -XdpPktMonUnregisterInterface( - _Inout_ XDP_INTERFACE_PKTMON_CONTEXT **PktMonContext +XdpPktMonUntrackInterface( + _Inout_ XDP_LWF_GENERIC *Generic ) { - XDP_INTERFACE_PKTMON_CONTEXT *Context = *PktMonContext; - - TraceEnter(TRACE_LWF, "Context=%p", Context); + TraceEnter(TRACE_LWF, "IfIndex=%u", Generic->IfIndex); if (XdpDisablePktMon) { goto Exit; } - if (Context != NULL) { - PktMonClntComponentUnregister(&Context->PktMonComp); - ExFreePoolWithTag(Context, POOLTAG_PKTMON); + // + // Remove interface from global tracking. + // + RtlAcquirePushLockExclusive(&XdpPktMonGenericListLock); + if (!IsListEmpty(&Generic->PktMonLink)) { + RemoveEntryList(&Generic->PktMonLink); + InitializeListHead(&Generic->PktMonLink); } + RtlReleasePushLockExclusive(&XdpPktMonGenericListLock); - *PktMonContext = NULL; + // + // Wait for any in-progress datapath references to drain. + // Only wait if PktMonContext is non-NULL, which means the rundown was + // reinitialized after a successful registration. If PktMonContext is NULL + // (registration failed, or the cleanup callback already drained), the + // rundown is already in the "run down" state and waiting would deadlock. + // + if (Generic->PktMonRundownRef != NULL && Generic->PktMonContext != NULL) { + ExWaitForRundownProtectionReleaseCacheAware(Generic->PktMonRundownRef); + } + + // + // Unregister with PktMon service (if a registration exists). + // + XdpPktMonUnregisterInterface(&Generic->PktMonContext); + + // + // Free resources. + // + if (Generic->PktMonRundownRef != NULL) { + ExFreeCacheAwareRundownProtection(Generic->PktMonRundownRef); + Generic->PktMonRundownRef = NULL; + } Exit: @@ -189,6 +383,9 @@ XdpPktMonStart( TraceEnter(TRACE_LWF, "Start"); + InitializeListHead(&XdpPktMonGenericList); + ExInitializePushLock(&XdpPktMonGenericListLock); + Status = XdpRegQueryBoolean(XDP_LWF_PARAMETERS_KEY, L"XdpDisablePktMon", &XdpDisablePktMon); if (!NT_SUCCESS(Status)) { // @@ -241,6 +438,4 @@ XdpPktMonStop( Exit: TraceExitSuccess(TRACE_LWF); - - return; } \ No newline at end of file diff --git a/src/xdplwf/pktmon.h b/src/xdplwf/pktmon.h index 61ff1989..612e9070 100644 --- a/src/xdplwf/pktmon.h +++ b/src/xdplwf/pktmon.h @@ -38,14 +38,19 @@ typedef enum _XDP_PKTMON_DROP_LOCATION { PktMonDropLoc15, } XDP_PKTMON_DROP_LOCATION; +// +// Per-interface context that is only allocated when pktmon service is running. +// typedef struct _XDP_INTERFACE_PKTMON_CONTEXT { PKTMON_COMPONENT_CONTEXT PktMonComp; } XDP_INTERFACE_PKTMON_CONTEXT; +typedef struct _XDP_LWF_GENERIC XDP_LWF_GENERIC; + _IRQL_requires_max_(DISPATCH_LEVEL) VOID XdpPktMonLogDrop( - _In_ XDP_INTERFACE_PKTMON_CONTEXT *PktMonContext, + _In_ XDP_LWF_GENERIC *Generic, _In_ NET_BUFFER_LIST *NetBufferLists, _In_ BOOLEAN UseOnlyFirstNbl, _In_ PKTMON_DIRECTION Direction, @@ -55,15 +60,20 @@ XdpPktMonLogDrop( _IRQL_requires_max_(PASSIVE_LEVEL) VOID -XdpPktMonRegisterInterface( - _Outptr_result_maybenull_ XDP_INTERFACE_PKTMON_CONTEXT **PktMonContext, - _In_ NET_IFINDEX IfIndex +XdpPktMonInitializeInterface( + _Inout_ XDP_LWF_GENERIC *Generic + ); + +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +XdpPktMonTrackInterface( + _Inout_ XDP_LWF_GENERIC *Generic ); _IRQL_requires_max_(PASSIVE_LEVEL) VOID -XdpPktMonUnregisterInterface( - _Inout_ XDP_INTERFACE_PKTMON_CONTEXT **PktMonContext +XdpPktMonUntrackInterface( + _Inout_ XDP_LWF_GENERIC *Generic ); _IRQL_requires_max_(PASSIVE_LEVEL) diff --git a/src/xdplwf/recv.c b/src/xdplwf/recv.c index 2db4e287..a0f6883d 100644 --- a/src/xdplwf/recv.c +++ b/src/xdplwf/recv.c @@ -650,7 +650,7 @@ XdpGenericRxAllocateTxCloneNbl( if (TxNbl == NULL) { STAT_INC(&RxQueue->PcwStats, ForwardingFailuresAllocation); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingAllocation, PktMonDropLoc); return NULL; } @@ -662,7 +662,7 @@ XdpGenericRxAllocateTxCloneNbl( TxNbl = NULL; STAT_INC(&RxQueue->PcwStats, ForwardingFailuresAllocationLimit); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingAllocationLimit, PktMonDropLoc); return NULL; } @@ -741,7 +741,7 @@ XdpGenericRxCloneOrCopyTxNblData2( if (NdisStatus != NDIS_STATUS_SUCCESS) { STAT_INC(&RxQueue->PcwStats, ForwardingFailuresAllocation); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingAllocation, PktMonDropLoc); return FALSE; } @@ -1060,7 +1060,7 @@ XdpGenericRxSegmentRscToLso( CanPend, TxNbl, PktMonDropLoc13)) { XdpGenericRxReturnTxCloneNbl(RxQueue, TxNbl); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingAllocation, PktMonDropLoc13); goto Exit; } @@ -1162,7 +1162,7 @@ XdpGenericRxConvertRscToLso( if (MdlDataLength < sizeof(*Ethernet)) { STAT_INC(&RxQueue->PcwStats, ForwardingFailuresRscInvalidHeaders); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingRscInvalidHeaders, PktMonDropLoc2); goto Exit; } @@ -1179,7 +1179,7 @@ XdpGenericRxConvertRscToLso( if (MdlDataLength < TcpOffset + sizeof(*Tcp)) { STAT_INC(&RxQueue->PcwStats, ForwardingFailuresRscInvalidHeaders); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingRscInvalidHeaders, PktMonDropLoc3); goto Exit; } @@ -1188,7 +1188,7 @@ XdpGenericRxConvertRscToLso( if (IpPayloadLength < sizeof(*Ipv4)) { STAT_INC(&RxQueue->PcwStats, ForwardingFailuresRscInvalidHeaders); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingRscInvalidHeaders, PktMonDropLoc4); goto Exit; } @@ -1209,7 +1209,7 @@ XdpGenericRxConvertRscToLso( if (MdlDataLength < TcpOffset + sizeof(*Tcp)) { STAT_INC(&RxQueue->PcwStats, ForwardingFailuresRscInvalidHeaders); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingRscInvalidHeaders, PktMonDropLoc5); goto Exit; } @@ -1222,7 +1222,7 @@ XdpGenericRxConvertRscToLso( default: STAT_INC(&RxQueue->PcwStats, ForwardingFailuresRscInvalidHeaders); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingRscInvalidHeaders, PktMonDropLoc6); goto Exit; } @@ -1235,7 +1235,7 @@ XdpGenericRxConvertRscToLso( if (TcpOffset + (UINT32)IpPayloadLength > Nb->DataLength) { STAT_INC(&RxQueue->PcwStats, ForwardingFailuresRscInvalidHeaders); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingRscInvalidHeaders, PktMonDropLoc7); goto Exit; } @@ -1252,7 +1252,7 @@ XdpGenericRxConvertRscToLso( Nb->DataLength < TcpTotalHdrLen) { STAT_INC(&RxQueue->PcwStats, ForwardingFailuresRscInvalidHeaders); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingRscInvalidHeaders, PktMonDropLoc8); goto Exit; } @@ -1278,7 +1278,7 @@ XdpGenericRxConvertRscToLso( if (TcpUserDataLenOrPureAck < NumSeg) { STAT_INC(&RxQueue->PcwStats, ForwardingFailuresRscInvalidHeaders); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, Nbl, TRUE, PktMonDir_Out, + RxQueue->Generic, Nbl, TRUE, PktMonDir_Out, DropForwardingRscInvalidHeaders, PktMonDropLoc9); goto Exit; } @@ -1761,7 +1761,7 @@ XdpGenericReceivePostInspectNbs( case XDP_RX_ACTION_DROP: NdisAppendSingleNblToNblQueue(DropList, ActionNbl); XdpPktMonLogDrop( - RxQueue->Generic->PktMonContext, ActionNbl, TRUE, + RxQueue->Generic, ActionNbl, TRUE, RxQueue->Flags.TxInspect ? PktMonDir_Out : PktMonDir_In, DropProgramInspection, PktMonDropLoc1); break; diff --git a/test/functional/lib/tests.cpp b/test/functional/lib/tests.cpp index 7c84a492..1dd0d1b6 100644 --- a/test/functional/lib/tests.cpp +++ b/test/functional/lib/tests.cpp @@ -661,6 +661,30 @@ class TestInterface { PowershellPrefix, _IfDesc); return HRESULT_FROM_WIN32(InvokeSystem(CmdBuff)); } + + HRESULT + TryDisable() const + { + CHAR CmdBuff[256]; + RtlZeroMemory(CmdBuff, sizeof(CmdBuff)); + sprintf_s( + CmdBuff, + "%s /c \"Disable-NetAdapter -ifDesc '%s' -Confirm:$false\"", + PowershellPrefix, _IfDesc); + return HRESULT_FROM_WIN32(InvokeSystem(CmdBuff)); + } + + HRESULT + TryEnable() const + { + CHAR CmdBuff[256]; + RtlZeroMemory(CmdBuff, sizeof(CmdBuff)); + sprintf_s( + CmdBuff, + "%s /c \"Enable-NetAdapter -ifDesc '%s'\"", + PowershellPrefix, _IfDesc); + return HRESULT_FROM_WIN32(InvokeSystem(CmdBuff)); + } }; static TestInterface FnMpIf(FNMP_IF_DESC, FNMP_IPV4_ADDRESS, FNMP_IPV6_ADDRESS); @@ -10496,6 +10520,336 @@ GenericRxXskMapRedirectMiss() TEST_EQUAL(0, XskRingConsumerReserve(&Xsk.Rings.Rx, MAXUINT32, &ConsumerIndex)); } +static +HRESULT +StartPktMonDropCapture( + _In_z_ const CHAR *EtlPath + ) +{ + CHAR Cmd[512]; + + // + // Stop any existing trace session and clear filters before starting a + // fresh drop-only capture. + // + InvokeSystem("pktmon stop >nul 2>&1"); + InvokeSystem("pktmon filter remove >nul 2>&1"); + sprintf_s( + Cmd, sizeof(Cmd), + "pktmon start -c --type drop --pkt-size 256 --file-name %s >nul 2>&1", + EtlPath); + if (InvokeSystem(Cmd) != 0) { + TraceError("StartPktMonDropCapture: pktmon start failed for '%s'", EtlPath); + return E_FAIL; + } + + return S_OK; +} + +static +HRESULT +StopPktMonCapture() +{ + if (InvokeSystem("pktmon stop >nul 2>&1") != 0) { + TraceError("StopPktMonCapture: pktmon stop failed"); + return E_FAIL; + } + + return S_OK; +} + +static +HRESULT +FormatPktMonTrace( + _In_z_ const CHAR *EtlPath, + _In_z_ const CHAR *TxtPath + ) +{ + CHAR Cmd[512]; + + sprintf_s(Cmd, sizeof(Cmd), "pktmon format %s -o %s >nul 2>&1", EtlPath, TxtPath); + if (InvokeSystem(Cmd) != 0) { + TraceError("FormatPktMonTrace: pktmon format failed for '%s'", EtlPath); + return E_FAIL; + } + + return S_OK; +} + +static +HRESULT +ReadNumberFromFile( + _In_z_ const CHAR *FilePath, + _Out_ UINT32 *Value + ) +{ + *Value = 0; + + FILE *f = NULL; + fopen_s(&f, FilePath, "r"); + if (f == NULL) { + TraceError("ReadNumberFromFile failed to open '%s'", FilePath); + return E_FAIL; + } + + CHAR Buf[16] = {}; + fgets(Buf, sizeof(Buf), f); + fclose(f); + + *Value = (UINT32)atoi(Buf); + return S_OK; +} + +static +HRESULT +GetPktMonComponentId( + _In_z_ const CHAR *TxtPath, + _In_z_ const CHAR *ComponentName, + _In_ UINT32 IfIndex, + _Out_ UINT32 *CompId + ) +{ + CHAR Cmd[1024]; + CHAR IdFilePath[MAX_PATH]; + + *CompId = 0; + + // + // Find the pktmon component ID for the given driver name and interface + // index. The formatted trace contains registration lines of the form: + // "Component , Type Filter , Name , " + // followed by property lines: + // "Property: Component , MiniportIfIndex = " + // We match both to identify the correct component. Writes 0 if not found. + // + sprintf_s(IdFilePath, sizeof(IdFilePath), "%s.compid", TxtPath); + auto CleanupIdFile = wil::scope_exit([&] { DeleteFileA(IdFilePath); }); + + sprintf_s( + Cmd, sizeof(Cmd), + "%s /c \"" + "$txt = Get-Content '%s'; " + "$ids = $txt | Select-String 'Component (\\d+), Type Filter.*%s.*XDP GENERIC' " + " | ForEach-Object { $_.Matches[0].Groups[1].Value }; " + "$id = $ids | Where-Object { " + " $txt | Select-String -Pattern ('Property: Component ' + $_ + ', MiniportIfIndex\\s+=\\s+%u') -Quiet " + "} | Select-Object -First 1; " + "if ($id) { Set-Content '%s' $id } else { Set-Content '%s' 0 }\"", + PowershellPrefix, TxtPath, ComponentName, IfIndex, IdFilePath, IdFilePath); + + if (InvokeSystem(Cmd) != 0) { + TraceError( + "GetPktMonComponentId: script execution failed for '%s' IfIndex=%u", + ComponentName, IfIndex); + return E_FAIL; + } + + return ReadNumberFromFile(IdFilePath, CompId); +} + +static +HRESULT +GetPktMonComponentDropCount( + _In_z_ const CHAR *TxtPath, + _In_ UINT32 CompId, + _Out_ UINT32 *DropCount + ) +{ + CHAR Cmd[512]; + CHAR CountFilePath[MAX_PATH]; + + *DropCount = 0; + + // + // Count the number of drop events referencing the given component ID. + // + sprintf_s(CountFilePath, sizeof(CountFilePath), "%s.dropcount", TxtPath); + auto CleanupCountFile = wil::scope_exit([&] { DeleteFileA(CountFilePath); }); + + sprintf_s( + Cmd, sizeof(Cmd), + "%s /c \"" + "$n = (Select-String -Path '%s' -Pattern 'Drop:.*Component %u,').Count; " + "Set-Content '%s' $n\"", + PowershellPrefix, TxtPath, CompId, CountFilePath); + + if (InvokeSystem(Cmd) != 0) { + TraceError("GetPktMonComponentDropCount: query failed for CompId=%u", CompId); + return E_FAIL; + } + + return ReadNumberFromFile(CountFilePath, DropCount); +} + +static +HRESULT +ExercisePktMonDrop( + _In_ const TestInterface &If, + _In_z_ const CHAR *EtlPath, + _In_z_ const CHAR *TxtPath + ) +{ + auto GenericMp = MpOpenGeneric(If.GetIfIndex()); + const UCHAR Payload[] = "PktMonDropVerify"; + + // + // Create a program that drops all RX traffic. + // + XDP_RULE Rule = {}; + Rule.Match = XDP_MATCH_ALL; + Rule.Action = XDP_PROGRAM_ACTION_DROP; + wil::unique_handle ProgramHandle = + CreateXdpProg( + If.GetIfIndex(), &XdpInspectRxL2, If.GetQueueId(), XDP_GENERIC, &Rule, 1); + + // + // Start a pktmon drop capture. + // + HRESULT Hr = StartPktMonDropCapture(EtlPath); + if (FAILED(Hr)) { + return Hr; + } + + // + // Inject a packet. + // + RX_FRAME Frame; + RxInitializeFrame(&Frame, If.GetQueueId(), Payload, sizeof(Payload)); + Hr = MpRxEnqueueFrame(GenericMp, &Frame); + if (FAILED(Hr)) { + TraceError("ExercisePktMonDrop: MpRxEnqueueFrame failed Hr=%!HRESULT!", Hr); + return Hr; + } + MpRxFlush(GenericMp); + + CxPlatSleep(TEST_TIMEOUT_ASYNC_MS); + + // + // Stop the trace. + // + Hr = StopPktMonCapture(); + if (FAILED(Hr)) { + return Hr; + } + + // + // Format the trace to text for analysis. + // + return FormatPktMonTrace(EtlPath, TxtPath); +} + +VOID +GenericPktMonRegistration() +{ + const CHAR *EtlPath = "C:\\pktmon_xdp_test.etl"; + const CHAR *TxtPath = "C:\\pktmon_xdp_test.etl.txt"; + UINT32 CompId; + UINT32 DropCount; + + // + // Disable any Mellanox ConnectX-5 Virtual Adapters for the duration of this + // test case. These adapters bugcheck when subjected to Disable/Enable-NetAdapter + // cycles combined with pktmon tracing in certain CI environments and are not + // needed for the test case. + // + static const CHAR *MellanoxFilter = + "Where-Object { $_.InterfaceDescription -like 'Mellanox ConnectX-5 Virtual Adapter*' }"; + CHAR CmdBuff[512]; + sprintf_s( + CmdBuff, sizeof(CmdBuff), + "%s /c \"Get-NetAdapter | %s | Disable-NetAdapter -Confirm:$false -ErrorAction SilentlyContinue\"", + PowershellPrefix, MellanoxFilter); + InvokeSystem(CmdBuff); + auto RestoreMellanox = wil::scope_exit([&] { + CHAR Cmd[512]; + sprintf_s( + Cmd, sizeof(Cmd), + "%s /c \"Get-NetAdapter -IncludeHidden | %s | Enable-NetAdapter -ErrorAction SilentlyContinue\"", + PowershellPrefix, MellanoxFilter); + InvokeSystem(Cmd); + }); + + // + // Capture the original pktmon service state and restore it on exit. + // + UINT32 OriginalServiceState; + TEST_HRESULT(GetServiceState(&OriginalServiceState, "pktmon")); + auto RestorePktMon = wil::scope_exit([&] { + InvokeSystem("pktmon stop >nul 2>&1"); + if (OriginalServiceState == SERVICE_RUNNING) { + (VOID)TryStartService("pktmon"); + } else { + (VOID)TryStopService("pktmon"); + } + }); + + auto CleanupFiles = wil::scope_exit([&] { + DeleteFileA(TxtPath); + DeleteFileA(EtlPath); + }); + + // + // Disable the test adapter and stop pktmon to start from a known state. + // + TEST_HRESULT(FnMpIf.TryDisable()); + auto RestoreAdapter = wil::scope_exit([&] { + (VOID)FnMpIf.TryEnable(); + }); + (VOID)TryStopService("pktmon"); + + // + // Enable the adapter while pktmon is already loaded. + // Verify XDP drops are visible in pktmon traces. + // + TEST_HRESULT(TryStartService("pktmon")); + TEST_HRESULT(FnMpIf.TryEnable()); + + TEST_HRESULT(ExercisePktMonDrop(FnMpIf, EtlPath, TxtPath)); + TEST_HRESULT(GetPktMonComponentId(TxtPath, "xdp.sys", FnMpIf.GetIfIndex(), &CompId)); + TEST_NOT_EQUAL(0, CompId); + TEST_HRESULT(GetPktMonComponentDropCount(TxtPath, CompId, &DropCount)); + TEST_TRUE(DropCount > 0); + + // + // Disable the adapter. This deregisters the XDP pktmon component. + // Verify the component is no longer registered. + // + TEST_HRESULT(FnMpIf.TryDisable()); + + TEST_HRESULT(StartPktMonDropCapture(EtlPath)); + TEST_HRESULT(StopPktMonCapture()); + TEST_HRESULT(FormatPktMonTrace(EtlPath, TxtPath)); + TEST_HRESULT(GetPktMonComponentId(TxtPath, "xdp.sys", FnMpIf.GetIfIndex(), &CompId)); + TEST_EQUAL(0, CompId); + + // + // Load pktmon after the adapter is already enabled. + // The registration callback should register the pre-existing binding, + // so drops should appear in pktmon traces. + // + TEST_HRESULT(TryStopService("pktmon")); + TEST_HRESULT(FnMpIf.TryEnable()); + TEST_HRESULT(TryStartService("pktmon")); + + TEST_HRESULT(ExercisePktMonDrop(FnMpIf, EtlPath, TxtPath)); + TEST_HRESULT(GetPktMonComponentId(TxtPath, "xdp.sys", FnMpIf.GetIfIndex(), &CompId)); + TEST_NOT_EQUAL(0, CompId); + TEST_HRESULT(GetPktMonComponentDropCount(TxtPath, CompId, &DropCount)); + TEST_TRUE(DropCount > 0); + + // + // Restart pktmon while the adapter is enabled. + // The registration callback should re-register the binding. + // + TEST_HRESULT(TryRestartService("pktmon")); + + TEST_HRESULT(ExercisePktMonDrop(FnMpIf, EtlPath, TxtPath)); + TEST_HRESULT(GetPktMonComponentId(TxtPath, "xdp.sys", FnMpIf.GetIfIndex(), &CompId)); + TEST_NOT_EQUAL(0, CompId); + TEST_HRESULT(GetPktMonComponentDropCount(TxtPath, CompId, &DropCount)); + TEST_TRUE(DropCount > 0); +} + /** * TODO: * diff --git a/test/functional/lib/tests.h b/test/functional/lib/tests.h index 4ac87585..8b1e4a85 100644 --- a/test/functional/lib/tests.h +++ b/test/functional/lib/tests.h @@ -346,3 +346,6 @@ GenericRxXskMapRedirectMiss(); VOID XskMapCreateInsertDelete(); + +VOID +GenericPktMonRegistration(); diff --git a/test/functional/taef/tests.cpp b/test/functional/taef/tests.cpp index f57dc536..ae2ba11e 100644 --- a/test/functional/taef/tests.cpp +++ b/test/functional/taef/tests.cpp @@ -735,4 +735,8 @@ TEST_CLASS(xdpfunctionaltests) TEST_METHOD(GenericRxXskMapRedirectMiss) { ::GenericRxXskMapRedirectMiss(); } + + TEST_METHOD(GenericPktMonRegistration) { + ::GenericPktMonRegistration(); + } };