Windows 10中基于IP Helper API统计特定UDP连接收发字节数的方法
Great question! You’re spot-on that IP Helper API mirrors most TCP functionality with UDP equivalents (like GetTcpTable()/GetUdpTable()), but there’s no public GetPerUdpConnectionEStats() function—and that’s not an oversight, it’s tied to how UDP works at the network level.
GetPerUdpConnectionEStats()? UDP is a connectionless protocol, meaning the Windows TCP/IP stack doesn’t maintain persistent "connection" contexts the way it does for TCP (which tracks state like SYN, ESTABLISHED, FIN). Since UDP has no formal connection lifecycle, there’s no built-in system-level structure to store per-connection byte stats—hence no dedicated API to retrieve them directly.
You’ll need to implement custom tracking using Windows’ built-in tracing or filtering frameworks. Here are the most reliable approaches:
1. Event Tracing for Windows (ETW) – Non-Intrusive Stats
ETW lets you capture UDP send/receive events directly from the TCP/IP stack, then aggregate byte counts by logical "connection" (defined by the 5-tuple: source IP, source port, destination IP, destination port, protocol).
Key Steps:
- Enable the
Microsoft-Windows-TCPIPETW provider - Listen for two critical events:
UdpSend(Event ID 1000): Captures outgoing UDP packet details, including byte count and 5-tupleUdpReceive(Event ID 1001): Captures incoming UDP packet details
- Maintain a dictionary (keyed by the 5-tuple) to accumulate send/receive bytes over time
Simplified C++ Example Snippet:
#include <windows.h> #include <evntrace.h> #include <unordered_map> // Define a struct to represent the UDP 5-tuple struct UdpConnectionKey { UINT32 srcIp; UINT16 srcPort; UINT32 dstIp; UINT16 dstPort; // Hash function for use as a dictionary key bool operator==(const UdpConnectionKey& other) const { return srcIp == other.srcIp && srcPort == other.srcPort && dstIp == other.dstIp && dstPort == other.dstPort; } }; namespace std { template<> struct hash<UdpConnectionKey> { size_t operator()(const UdpConnectionKey& k) const { return hash<UINT32>()(k.srcIp) ^ hash<UINT16>()(k.srcPort) ^ hash<UINT32>()(k.dstIp) ^ hash<UINT16>()(k.dstPort); } }; } std::unordered_map<UdpConnectionKey, ULONG64> sendStats; std::unordered_map<UdpConnectionKey, ULONG64> recvStats; void WINAPI EventCallback(PEVENT_RECORD pEventRecord) { if (pEventRecord->EventHeader.EventDescriptor.Id == 1000) { // Parse UdpSend event data (schema documented by Microsoft) UdpConnectionKey key = {/* extract 5-tuple from event data */}; ULONG bytesSent = {/* extract byte count from event data */}; sendStats[key] += bytesSent; } else if (pEventRecord->EventHeader.EventDescriptor.Id == 1001) { // Parse UdpReceive event data UdpConnectionKey key = {/* extract 5-tuple from event data */}; ULONG bytesReceived = {/* extract byte count from event data */}; recvStats[key] += bytesReceived; } } int main() { EVENT_TRACE_PROPERTIES* traceProps = (EVENT_TRACE_PROPERTIES*)malloc(sizeof(EVENT_TRACE_PROPERTIES) + 1024); ZeroMemory(traceProps, sizeof(EVENT_TRACE_PROPERTIES) + 1024); traceProps->Wnode.BufferSize = sizeof(EVENT_TRACE_PROPERTIES) + 1024; traceProps->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); wcscpy_s((WCHAR*)((BYTE*)traceProps + traceProps->LoggerNameOffset), 512, L"UdpStatsSession"); TRACEHANDLE sessionHandle; StartTrace(&sessionHandle, L"UdpStatsSession", traceProps); EnableTraceEx2(sessionHandle, &GUID_MICROSOFT_WINDOWS_TCPIP, EVENT_CONTROL_CODE_ENABLE_PROVIDER, TRACE_LEVEL_INFORMATION, 0, 0, 0, NULL); // Process events until stopped (e.g., user input) ProcessTrace(&sessionHandle, 1, NULL, NULL); // Cleanup DisableTraceEx2(sessionHandle, &GUID_MICROSOFT_WINDOWS_TCPIP, EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0); StopTrace(sessionHandle, L"UdpStatsSession", traceProps); free(traceProps); return 0; }
2. Windows Filtering Platform (WFP) – Intrusive but Flexible
WFP lets you intercept UDP packets at multiple layers of the network stack, making it ideal if you need real-time tracking or want to filter traffic alongside stats collection.
Key Steps:
- Initialize a WFP filtering engine
- Register a filter rule targeting UDP traffic (e.g., at the
FWPM_LAYER_INBOUND_TRANSPORT_V4/OUTBOUND_TRANSPORT_V4layers) - In the filter callback, extract the 5-tuple and packet byte count, then update your stats
Simplified C++ Example Snippet:
#include <windows.h> #include <fwpsk.h> #include <fwpmu.h> std::unordered_map<UdpConnectionKey, ULONG64> sendStats; std::unordered_map<UdpConnectionKey, ULONG64> recvStats; NTSTATUS CALLBACK UdpStatsClassifyFn( IN const FWPS_INCOMING_VALUES* inFixedValues, IN const FWPS_INCOMING_METADATA_VALUES* inMetaValues, IN OUT VOID* layerData, IN const FWPS_FILTER* filter, IN UINT64 flowContext, OUT FWPS_CLASSIFY_OUT* classifyOut ) { UNREFERENCED_PARAMETER(filter); UNREFERENCED_PARAMETER(flowContext); // Extract packet byte count UINT32 byteCount = 0; if (inMetaValues->layerId == FWPM_LAYER_OUTBOUND_TRANSPORT_V4) { FWPS_TRANSPORT_SEND_PARAMS* sendParams = (FWPS_TRANSPORT_SEND_PARAMS*)layerData; byteCount = sendParams->dataLength; // Extract 5-tuple from inFixedValues UdpConnectionKey key = { inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_LOCAL_ADDRESS].value.uint32, inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_LOCAL_PORT].value.uint16, inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_ADDRESS].value.uint32, inFixedValues->incomingValue[FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_REMOTE_PORT].value.uint16 }; sendStats[key] += byteCount; } else if (inMetaValues->layerId == FWPM_LAYER_INBOUND_TRANSPORT_V4) { FWPS_TRANSPORT_RECEIVE_PARAMS* recvParams = (FWPS_TRANSPORT_RECEIVE_PARAMS*)layerData; byteCount = recvParams->dataLength; // Extract 5-tuple from inFixedValues UdpConnectionKey key = { inFixedValues->incomingValue[FWPS_FIELD_INBOUND_TRANSPORT_V4_IP_REMOTE_ADDRESS].value.uint32, inFixedValues->incomingValue[FWPS_FIELD_INBOUND_TRANSPORT_V4_IP_REMOTE_PORT].value.uint16, inFixedValues->incomingValue[FWPS_FIELD_INBOUND_TRANSPORT_V4_IP_LOCAL_ADDRESS].value.uint32, inFixedValues->incomingValue[FWPS_FIELD_INBOUND_TRANSPORT_V4_IP_LOCAL_PORT].value.uint16 }; recvStats[key] += byteCount; } // Allow the packet to proceed classifyOut->actionType = FWP_ACTION_PERMIT; return STATUS_SUCCESS; } // Helper function to register the filter HRESULT RegisterUdpStatsFilter() { FWPM_SESSION session = {0}; session.flags = FWPM_SESSION_FLAG_DYNAMIC; HANDLE engineHandle; HRESULT hr = FwpmEngineOpen(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engineHandle); if (FAILED(hr)) return hr; // Register callout FWPM_CALLOUT callout = {0}; callout.calloutKey = {/* unique GUID for your callout */}; callout.displayData.name = L"UDP Stats Callout"; callout.classifyFn = UdpStatsClassifyFn; hr = FwpmCalloutAdd(engineHandle, &callout, NULL, NULL); if (FAILED(hr)) { FwpmEngineClose(engineHandle); return hr; } // Create filter rule targeting UDP FWPM_FILTER filter = {0}; filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V4; filter.action.type = FWP_ACTION_CALLOUT_TERMINATING; filter.action.calloutKey = callout.calloutKey; filter.filterCondition[0].fieldKey = FWPS_FIELD_OUTBOUND_TRANSPORT_V4_IP_PROTOCOL; filter.filterCondition[0].matchType = FWP_MATCH_EQUAL; filter.filterCondition[0].conditionValue.type = FWP_UINT8; filter.filterCondition[0].conditionValue.uint8 = IPPROTO_UDP; hr = FwpmFilterAdd(engineHandle, &filter, NULL, NULL); // Repeat filter setup for inbound layer if needed FwpmEngineClose(engineHandle); return hr; }
- ETW is the best choice for non-intrusive, low-overhead stats collection.
- WFP is better if you need to interact with traffic (e.g., block packets) alongside tracking.
- Both approaches require admin privileges to run, as they access low-level network stack data.
内容的提问来源于stack exchange,提问作者u17

