From 0020948f559bec78582ec15ee7224078077dff5b Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 14 May 2026 17:46:16 -0700 Subject: [PATCH 01/41] add tools --- src/tools/CMakeLists.txt | 2 + src/tools/sample/sample.c | 76 ++++- src/tools/xdpmapconsumer/CMakeLists.txt | 9 + src/tools/xdpmapconsumer/xdpmapconsumer.c | 323 ++++++++++++++++++++++ src/tools/xdpmapcreator/CMakeLists.txt | 9 + src/tools/xdpmapcreator/xdpmapcreator.c | 227 +++++++++++++++ 6 files changed, 643 insertions(+), 3 deletions(-) create mode 100644 src/tools/xdpmapconsumer/CMakeLists.txt create mode 100644 src/tools/xdpmapconsumer/xdpmapconsumer.c create mode 100644 src/tools/xdpmapcreator/CMakeLists.txt create mode 100644 src/tools/xdpmapcreator/xdpmapcreator.c diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 8535e99ff6..ff9de619f4 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -38,4 +38,6 @@ if(WIN32 AND (NOT QUIC_UWP_BUILD AND NOT QUIC_GAMECORE_BUILD)) add_subdirectory(execution) add_subdirectory(etw) add_subdirectory(recvfuzz) + add_subdirectory(xdpmapcreator) + add_subdirectory(xdpmapconsumer) endif() diff --git a/src/tools/sample/sample.c b/src/tools/sample/sample.c index d13993fe7e..1f1f519d61 100644 --- a/src/tools/sample/sample.c +++ b/src/tools/sample/sample.c @@ -125,12 +125,16 @@ void PrintUsage() "\n" "Usage:\n" "\n" - " quicsample.exe -client -unsecure -target:{IPAddress|Hostname} [-ticket:]\n" + " quicsample.exe -client -unsecure -target:{IPAddress|Hostname} [-ticket:] [-cibir_id:]\n" #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES " quicsample.exe -multiclient -count: -unsecure -target:{IPAddress|Hostname}\n" #endif - " quicsample.exe -server -cert_hash:<...>\n" - " quicsample.exe -server -cert_file:<...> -key_file:<...> [-password:<...>]\n" + " quicsample.exe -server -cert_hash:<...> [-xdp] [-cibir_id:]\n" + " quicsample.exe -server -cert_file:<...> -key_file:<...> [-password:<...>] [-xdp] [-cibir_id:]\n" + "\n" + " -xdp Enable XDP datapath (server only, requires XDP driver)\n" + " -cibir_id: Set CIBIR ID (hex: first byte is offset, rest is CID prefix)\n" + " Example: -cibir_id:00AABBCCDD (offset=0, CID=AABBCCDD)\n" ); } @@ -555,6 +559,23 @@ ServerLoadConfiguration( Settings.PeerBidiStreamCount = 1; Settings.IsSet.PeerBidiStreamCount = TRUE; + // + // Optionally enable XDP datapath for high-performance packet processing. + // XDP must be set at the global library level (MsQuicLib.Settings) because + // the listener socket creation checks the global setting, not per-config. + // + if (GetFlag(argc, argv, "xdp")) { + QUIC_SETTINGS XdpSettings = {0}; + XdpSettings.XdpEnabled = TRUE; + XdpSettings.IsSet.XdpEnabled = TRUE; + QUIC_STATUS XdpStatus = MsQuic->SetParam(NULL, QUIC_PARAM_GLOBAL_SETTINGS, sizeof(XdpSettings), &XdpSettings); + if (QUIC_FAILED(XdpStatus)) { + printf("Failed to enable XDP globally, 0x%x!\n", XdpStatus); + return FALSE; + } + printf("XDP datapath enabled (global).\n"); + } + QUIC_CREDENTIAL_CONFIG_HELPER Config; memset(&Config, 0, sizeof(Config)); Config.CredConfig.Flags = QUIC_CREDENTIAL_FLAG_NONE; @@ -657,6 +678,26 @@ RunServer( goto Error; } + // + // Optionally set the CIBIR ID on the listener for CID-based XDP steering. + // + { + const char* CibirIdHex = GetValue(argc, argv, "cibir_id"); + if (CibirIdHex != NULL) { + uint8_t CibirId[7]; // offset (1 byte) + max 6 bytes CID + uint32_t CibirIdLen = DecodeHexBuffer(CibirIdHex, sizeof(CibirId), CibirId); + if (CibirIdLen < 2) { + printf("CIBIR ID too short (need at least offset + 1 byte CID)!\n"); + goto Error; + } + if (QUIC_FAILED(Status = MsQuic->SetParam(Listener, QUIC_PARAM_LISTENER_CIBIR_ID, CibirIdLen, CibirId))) { + printf("SetParam(QUIC_PARAM_LISTENER_CIBIR_ID) failed, 0x%x!\n", Status); + goto Error; + } + printf("CIBIR ID set on listener (offset=%u, %u bytes CID).\n", CibirId[0], CibirIdLen - 1); + } + } + // // Starts listening for incoming connections. // @@ -959,6 +1000,35 @@ RunClient( goto Error; } + // + // Optionally set the CIBIR ID on the connection for CID-based steering. + // + { + const char* CibirIdHex = GetValue(argc, argv, "cibir_id"); + if (CibirIdHex != NULL) { + // + // CIBIR requires shared binding (so the connection uses source CIDs). + // + BOOLEAN ShareBinding = TRUE; + if (QUIC_FAILED(Status = MsQuic->SetParam(Connection, QUIC_PARAM_CONN_SHARE_UDP_BINDING, sizeof(ShareBinding), &ShareBinding))) { + printf("SetParam(QUIC_PARAM_CONN_SHARE_UDP_BINDING) failed, 0x%x!\n", Status); + goto Error; + } + + uint8_t CibirId[7]; // offset (1 byte) + max 6 bytes CID + uint32_t CibirIdLen = DecodeHexBuffer(CibirIdHex, sizeof(CibirId), CibirId); + if (CibirIdLen < 2) { + printf("CIBIR ID too short (need at least offset + 1 byte CID)!\n"); + goto Error; + } + if (QUIC_FAILED(Status = MsQuic->SetParam(Connection, QUIC_PARAM_CONN_CIBIR_ID, CibirIdLen, CibirId))) { + printf("SetParam(QUIC_PARAM_CONN_CIBIR_ID) failed, 0x%x!\n", Status); + goto Error; + } + printf("CIBIR ID set on connection (offset=%u, %u bytes CID).\n", CibirId[0], CibirIdLen - 1); + } + } + if ((ResumptionTicketString = GetValue(argc, argv, "ticket")) != NULL) { // // If provided at the command line, set the resumption ticket that can diff --git a/src/tools/xdpmapconsumer/CMakeLists.txt b/src/tools/xdpmapconsumer/CMakeLists.txt new file mode 100644 index 0000000000..a99aaa569a --- /dev/null +++ b/src/tools/xdpmapconsumer/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +add_quic_tool(xdpmapconsumer xdpmapconsumer.c) +quic_tool_warnings(xdpmapconsumer) + +target_compile_definitions(xdpmapconsumer PRIVATE XDP_API_VERSION=3) +target_include_directories(xdpmapconsumer SYSTEM PRIVATE + ${PROJECT_SOURCE_DIR}/submodules/xdp-for-windows/published/external) diff --git a/src/tools/xdpmapconsumer/xdpmapconsumer.c b/src/tools/xdpmapconsumer/xdpmapconsumer.c new file mode 100644 index 0000000000..37ba474b65 --- /dev/null +++ b/src/tools/xdpmapconsumer/xdpmapconsumer.c @@ -0,0 +1,323 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + Consumes an XSKMAP handle duplicated from another process. Creates XSK + sockets, inserts them into the map, and receives packets. + + Usage: + 1. Run: xdpmapconsumer.exe -IfIndex -QueueCount + 2. Note the printed PID + 3. In another terminal, run: xdpmapcreator.exe -TargetPid + 4. Paste the remote handle value printed by the creator into this console + 5. The consumer creates per-queue XSKs, inserts them into the map, + and starts receiving packets. + + The creator owns the map and the XDP program; the consumer only inserts + its XSK sockets and drains received frames. + +--*/ + +#define _CRT_SECURE_NO_WARNINGS 1 +#define XDP_API_VERSION 3 +#define XDP_INCLUDE_WINCOMMON + +#pragma warning(disable:5105) +#include +#include +#include + +#include +#include +#include + +#define LOGERR(...) \ + fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") + +#define RX_RING_SIZE 64 +#define FRAME_SIZE 2048 + +typedef struct _QUEUE_CONTEXT { + HANDLE Socket; + XSK_RING RxRing; + XSK_RING RxFillRing; + UCHAR *Umem; + UINT64 PacketsReceived; + UINT64 BytesReceived; +} QUEUE_CONTEXT; + +static UINT32 IfIndex; +static UINT32 QueueCount; + +static void +PrintUsage(void) +{ + printf( + "xdpmapconsumer.exe -IfIndex -QueueCount \n" + "\n" + "Receives an XSKMAP handle from xdpmapcreator, creates per-queue\n" + "XSK sockets, inserts them into the map, and receives packets.\n" + "\n" + "OPTIONS:\n" + " -IfIndex Network interface index to bind XSKs to (required)\n" + " -QueueCount Number of RSS queues to cover (required, 1-128)\n" + ); +} + +static BOOLEAN +ParseArgs( + int ArgC, + char** ArgV + ) +{ + int i = 1; + IfIndex = MAXUINT32; + QueueCount = 0; + + while (i < ArgC) { + if (!_stricmp(ArgV[i], "-IfIndex")) { + if (++i >= ArgC) { + LOGERR("Missing IfIndex value"); + return FALSE; + } + IfIndex = (UINT32)atoi(ArgV[i]); + } else if (!_stricmp(ArgV[i], "-QueueCount")) { + if (++i >= ArgC) { + LOGERR("Missing QueueCount value"); + return FALSE; + } + QueueCount = (UINT32)atoi(ArgV[i]); + if (QueueCount == 0 || QueueCount > 128) { + LOGERR("QueueCount must be between 1 and 128"); + return FALSE; + } + } else { + LOGERR("Unexpected parameter \"%s\"", ArgV[i]); + return FALSE; + } + ++i; + } + + if (IfIndex == MAXUINT32) { + LOGERR("-IfIndex is required"); + return FALSE; + } + if (QueueCount == 0) { + LOGERR("-QueueCount is required"); + return FALSE; + } + return TRUE; +} + +static XDP_STATUS +SetupQueueSocket( + _In_ UINT32 QueueId, + _Inout_ QUEUE_CONTEXT *Ctx + ) +{ + XDP_STATUS XdpStatus; + XSK_UMEM_REG UmemReg = {0}; + UINT32 RingSize = RX_RING_SIZE; + XSK_RING_INFO_SET RingInfo; + UINT32 OptionLength; + UINT32 FillIndex; + + Ctx->PacketsReceived = 0; + Ctx->BytesReceived = 0; + + Ctx->Umem = (UCHAR *)calloc(RX_RING_SIZE, FRAME_SIZE); + if (Ctx->Umem == NULL) { + LOGERR("Failed to allocate UMEM for queue %u", QueueId); + return E_OUTOFMEMORY; + } + + XdpStatus = XskCreate(&Ctx->Socket); + if (FAILED(XdpStatus)) { + LOGERR("XskCreate failed for queue %u: %x", QueueId, XdpStatus); + return XdpStatus; + } + + UmemReg.TotalSize = (UINT32)RX_RING_SIZE * FRAME_SIZE; + UmemReg.ChunkSize = FRAME_SIZE; + UmemReg.Address = Ctx->Umem; + + XdpStatus = XskSetSockopt(Ctx->Socket, XSK_SOCKOPT_UMEM_REG, &UmemReg, sizeof(UmemReg)); + if (FAILED(XdpStatus)) { + LOGERR("XSK_SOCKOPT_UMEM_REG failed for queue %u: %x", QueueId, XdpStatus); + return XdpStatus; + } + + XdpStatus = XskBind(Ctx->Socket, IfIndex, QueueId, XSK_BIND_FLAG_RX); + if (FAILED(XdpStatus)) { + LOGERR("XskBind failed for queue %u: %x", QueueId, XdpStatus); + return XdpStatus; + } + + XdpStatus = XskSetSockopt(Ctx->Socket, XSK_SOCKOPT_RX_RING_SIZE, &RingSize, sizeof(RingSize)); + if (FAILED(XdpStatus)) { + LOGERR("XSK_SOCKOPT_RX_RING_SIZE failed for queue %u: %x", QueueId, XdpStatus); + return XdpStatus; + } + + XdpStatus = XskSetSockopt(Ctx->Socket, XSK_SOCKOPT_RX_FILL_RING_SIZE, &RingSize, sizeof(RingSize)); + if (FAILED(XdpStatus)) { + LOGERR("XSK_SOCKOPT_RX_FILL_RING_SIZE failed for queue %u: %x", QueueId, XdpStatus); + return XdpStatus; + } + + XdpStatus = XskActivate(Ctx->Socket, XSK_ACTIVATE_FLAG_NONE); + if (FAILED(XdpStatus)) { + LOGERR("XskActivate failed for queue %u: %x", QueueId, XdpStatus); + return XdpStatus; + } + + OptionLength = sizeof(RingInfo); + XdpStatus = XskGetSockopt(Ctx->Socket, XSK_SOCKOPT_RING_INFO, &RingInfo, &OptionLength); + if (FAILED(XdpStatus)) { + LOGERR("XSK_SOCKOPT_RING_INFO failed for queue %u: %x", QueueId, XdpStatus); + return XdpStatus; + } + + XskRingInitialize(&Ctx->RxRing, &RingInfo.Rx); + XskRingInitialize(&Ctx->RxFillRing, &RingInfo.Fill); + + XskRingProducerReserve(&Ctx->RxFillRing, RX_RING_SIZE, &FillIndex); + for (UINT32 j = 0; j < RX_RING_SIZE; j++) { + *(UINT64 *)XskRingGetElement(&Ctx->RxFillRing, FillIndex + j) = + (UINT64)j * FRAME_SIZE; + } + XskRingProducerSubmit(&Ctx->RxFillRing, RX_RING_SIZE); + + return S_OK; +} + +static void +PrintStats( + _In_ QUEUE_CONTEXT *Queues, + _In_ UINT32 Count + ) +{ + UINT64 TotalPackets = 0; + UINT64 TotalBytes = 0; + + printf("\n--- RX Stats ---\n"); + for (UINT32 i = 0; i < Count; i++) { + printf(" Queue %2u: %8llu pkts %12llu bytes\n", + i, Queues[i].PacketsReceived, Queues[i].BytesReceived); + TotalPackets += Queues[i].PacketsReceived; + TotalBytes += Queues[i].BytesReceived; + } + printf(" Total: %8llu pkts %12llu bytes\n", TotalPackets, TotalBytes); + printf("----------------\n"); +} + +int +__cdecl +main( + int argc, + char** argv + ) +{ + UINT_PTR HandleValue; + HANDLE XskMap; + QUEUE_CONTEXT *Queues = NULL; + XDP_STATUS XdpStatus; + char InputBuf[64]; + + if (!ParseArgs(argc, argv)) { + PrintUsage(); + return 1; + } + + printf("=== XSKMAP Consumer ===\n"); + printf("My PID: %u\n", GetCurrentProcessId()); + printf("IfIndex: %u, QueueCount: %u\n", IfIndex, QueueCount); + printf("\n"); + printf("Start xdpmapcreator.exe -TargetPid %u in another terminal,\n", + GetCurrentProcessId()); + printf("then paste the remote handle value here.\n"); + printf("\n"); + printf("Handle value (hex): "); + fflush(stdout); + + if (fgets(InputBuf, sizeof(InputBuf), stdin) == NULL) { + LOGERR("Failed to read input"); + return 1; + } + + HandleValue = (UINT_PTR)_strtoui64(InputBuf, NULL, 16); + if (HandleValue == 0 || HandleValue == (UINT_PTR)INVALID_HANDLE_VALUE) { + LOGERR("Invalid handle value: %s", InputBuf); + return 1; + } + + XskMap = (HANDLE)HandleValue; + printf("Received XSKMAP handle: 0x%IX\n", (UINT_PTR)XskMap); + + // + // Create per-queue XSK sockets and insert them into the map. + // + Queues = (QUEUE_CONTEXT *)calloc(QueueCount, sizeof(QUEUE_CONTEXT)); + if (Queues == NULL) { + LOGERR("Failed to allocate queue context array"); + return 1; + } + + for (UINT32 i = 0; i < QueueCount; i++) { + XdpStatus = SetupQueueSocket(i, &Queues[i]); + if (FAILED(XdpStatus)) { + return 1; + } + + XdpStatus = XdpMapInsert(XskMap, &i, &Queues[i].Socket); + if (FAILED(XdpStatus)) { + LOGERR("XdpMapInsert failed for queue %u: %x", i, XdpStatus); + return 1; + } + + printf("Queue %u: XSK created, bound, activated, inserted into map\n", i); + } + + printf("\nReceiving packets... (Ctrl+C to stop)\n"); + + // + // Poll all queues for received packets, print stats every second. + // + ULONGLONG LastPrintTick = GetTickCount64(); + + for (;;) { + ULONGLONG Now = GetTickCount64(); + if (Now - LastPrintTick >= 1000) { + PrintStats(Queues, QueueCount); + LastPrintTick = Now; + } + + for (UINT32 q = 0; q < QueueCount; q++) { + QUEUE_CONTEXT *Ctx = &Queues[q]; + UINT32 RxIndex; + UINT32 Available = XskRingConsumerReserve(&Ctx->RxRing, RX_RING_SIZE, &RxIndex); + + for (UINT32 j = 0; j < Available; j++) { + XSK_BUFFER_DESCRIPTOR *Desc = + (XSK_BUFFER_DESCRIPTOR *)XskRingGetElement(&Ctx->RxRing, RxIndex + j); + Ctx->PacketsReceived++; + Ctx->BytesReceived += Desc->Length; + } + + if (Available > 0) { + XskRingConsumerRelease(&Ctx->RxRing, Available); + + UINT32 FillIndex; + UINT32 Filled = XskRingProducerReserve(&Ctx->RxFillRing, Available, &FillIndex); + for (UINT32 j = 0; j < Filled; j++) { + *(UINT64 *)XskRingGetElement(&Ctx->RxFillRing, FillIndex + j) = + (UINT64)((RxIndex + j) % RX_RING_SIZE) * FRAME_SIZE; + } + XskRingProducerSubmit(&Ctx->RxFillRing, Filled); + } + } + } +} diff --git a/src/tools/xdpmapcreator/CMakeLists.txt b/src/tools/xdpmapcreator/CMakeLists.txt new file mode 100644 index 0000000000..1b30054265 --- /dev/null +++ b/src/tools/xdpmapcreator/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +add_quic_tool(xdpmapcreator xdpmapcreator.c) +quic_tool_warnings(xdpmapcreator) + +target_compile_definitions(xdpmapcreator PRIVATE XDP_API_VERSION=3) +target_include_directories(xdpmapcreator SYSTEM PRIVATE + ${PROJECT_SOURCE_DIR}/submodules/xdp-for-windows/published/external) diff --git a/src/tools/xdpmapcreator/xdpmapcreator.c b/src/tools/xdpmapcreator/xdpmapcreator.c new file mode 100644 index 0000000000..405cb0668d --- /dev/null +++ b/src/tools/xdpmapcreator/xdpmapcreator.c @@ -0,0 +1,227 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + Creates an XSKMAP, duplicates the handle into a target process, and + attaches an XDP program once the consumer signals it has inserted its XSKs. + + Usage: + 1. Start xdpmapconsumer.exe -IfIndex -QueueCount (prints PID) + 2. Run: xdpmapcreator.exe -TargetPid -IfIndex [-IcmpOnly] + 3. Paste the printed handle value into the consumer's stdin + 4. Once the consumer has inserted its XSKs, press Enter here to + attach the XDP program and start steering traffic. + + The creator owns the map and the XDP program lifetime. + +--*/ + +#define _CRT_SECURE_NO_WARNINGS 1 +#define XDP_API_VERSION 3 +#define XDP_INCLUDE_WINCOMMON + +#pragma warning(disable:5105) +#include +#include +#include + +#include + +#define LOGERR(...) \ + fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") + +static UINT32 TargetPid; +static UINT32 IfIndex; +static BOOLEAN UseIcmpMatch; + +static void +PrintUsage(void) +{ + printf( + "xdpmapcreator.exe -TargetPid -IfIndex [OPTIONS]\n" + "\n" + "Creates an XSKMAP, duplicates it into the target process, and attaches\n" + "an XDP program once you signal readiness.\n" + "\n" + "OPTIONS:\n" + " -TargetPid PID of the consumer process (required)\n" + " -IfIndex Network interface index (required)\n" + " -IcmpOnly Match only ICMPv4 traffic (default: match all)\n" + ); +} + +static BOOLEAN +ParseArgs( + int ArgC, + char** ArgV + ) +{ + int i = 1; + TargetPid = 0; + IfIndex = MAXUINT32; + UseIcmpMatch = FALSE; + + while (i < ArgC) { + if (!_stricmp(ArgV[i], "-TargetPid") || !_stricmp(ArgV[i], "-pid")) { + if (++i >= ArgC) { + LOGERR("Missing TargetPid value"); + return FALSE; + } + TargetPid = (UINT32)atoi(ArgV[i]); + } else if (!_stricmp(ArgV[i], "-IfIndex")) { + if (++i >= ArgC) { + LOGERR("Missing IfIndex value"); + return FALSE; + } + IfIndex = (UINT32)atoi(ArgV[i]); + } else if (!_stricmp(ArgV[i], "-IcmpOnly")) { + UseIcmpMatch = TRUE; + } else { + LOGERR("Unexpected parameter \"%s\"", ArgV[i]); + return FALSE; + } + ++i; + } + + if (TargetPid == 0) { + LOGERR("-TargetPid is required"); + return FALSE; + } + if (IfIndex == MAXUINT32) { + LOGERR("-IfIndex is required"); + return FALSE; + } + + return TRUE; +} + +int +__cdecl +main( + int argc, + char** argv + ) +{ + XDP_STATUS XdpStatus; + HANDLE XskMap = NULL; + HANDLE TargetProcess = NULL; + HANDLE RemoteHandle = NULL; + HANDLE Program = NULL; + + if (!ParseArgs(argc, argv)) { + PrintUsage(); + return 1; + } + + // + // Stage 1: Create the XSKMAP. + // + XdpStatus = XdpMapCreate(&XskMap, XDP_MAP_TYPE_XSKMAP); + if (FAILED(XdpStatus)) { + LOGERR("XdpMapCreate failed: 0x%x", XdpStatus); + return 1; + } + + printf("[Stage 1] Created XSKMAP (local handle: 0x%IX)\n", (UINT_PTR)XskMap); + + // + // Stage 2: Duplicate the map handle into the consumer process. + // + TargetProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, TargetPid); + if (TargetProcess == NULL) { + LOGERR("OpenProcess(%u) failed: %u. Ensure the consumer is running.", + TargetPid, GetLastError()); + CloseHandle(XskMap); + return 1; + } + + if (!DuplicateHandle( + GetCurrentProcess(), + XskMap, + TargetProcess, + &RemoteHandle, + 0, + FALSE, + DUPLICATE_SAME_ACCESS)) { + LOGERR("DuplicateHandle failed: %u", GetLastError()); + CloseHandle(TargetProcess); + CloseHandle(XskMap); + return 1; + } + + CloseHandle(TargetProcess); + + printf("[Stage 2] Duplicated XSKMAP into PID %u. Remote handle: 0x%IX\n", + TargetPid, (UINT_PTR)RemoteHandle); + printf("\n"); + printf(" Paste 0x%IX into the consumer, let it create and insert its XSKs.\n", + (UINT_PTR)RemoteHandle); + printf("\n"); + + // + // Stage 3: Wait for user signal that the consumer is ready. + // + printf("[Stage 3] Press ENTER when the consumer has inserted its XSKs...\n"); + fflush(stdout); + (void)getchar(); + + // + // Stage 4: Attach the XDP program with the now-populated map. + // + { + const XDP_HOOK_ID XdpInspectRxL2 = { + XDP_HOOK_L2, + XDP_HOOK_RX, + XDP_HOOK_INSPECT, + }; + + XDP_RULE Rule; + ZeroMemory(&Rule, sizeof(Rule)); + + if (UseIcmpMatch) { + Rule.Match = XDP_MATCH_IP_NEXT_HEADER; + Rule.Pattern.NextHeader = 1; // IPPROTO_ICMP + } else { + Rule.Match = XDP_MATCH_ALL; + } + + Rule.Action = XDP_PROGRAM_ACTION_REDIRECT; + Rule.Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rule.Redirect.Target = XskMap; + + XdpStatus = + XdpCreateProgram( + IfIndex, + &XdpInspectRxL2, + 0, + XDP_CREATE_PROGRAM_FLAG_ALL_QUEUES, + &Rule, + 1, + &Program); + if (FAILED(XdpStatus)) { + LOGERR("XdpCreateProgram failed: 0x%x", XdpStatus); + CloseHandle(XskMap); + return 1; + } + } + + printf("[Stage 4] XDP program attached on IfIndex %u (%s). Traffic is flowing!\n", + IfIndex, UseIcmpMatch ? "ICMPv4 only" : "all traffic"); + printf("\n"); + printf("Press ENTER to detach the program and exit.\n"); + fflush(stdout); + (void)getchar(); + + // + // Cleanup: closing the program handle detaches XDP. Closing the map + // handle releases our reference (consumer may still hold one). + // + printf("Detaching XDP program and cleaning up.\n"); + CloseHandle(Program); + CloseHandle(XskMap); + + return 0; +} From 98302508342c9651d345b10c496ab1ab7f4f860c Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 15 May 2026 15:07:59 -0700 Subject: [PATCH 02/41] phase 2 looks good so far --- src/core/library.c | 9 + src/inc/quic_datapath.h | 17 + src/platform/datapath_raw_dummy.c | 13 + src/platform/datapath_raw_xdp_win.c | 55 +++ src/platform/datapath_xplat.c | 13 + src/platform/platform_internal.h | 10 + src/tools/CMakeLists.txt | 1 + src/tools/quicxskmapcreator/CMakeLists.txt | 9 + .../quicxskmapcreator/quicxskmapcreator.c | 325 ++++++++++++++++++ src/tools/sample/sample.c | 134 +++++++- 10 files changed, 583 insertions(+), 3 deletions(-) create mode 100644 src/tools/quicxskmapcreator/CMakeLists.txt create mode 100644 src/tools/quicxskmapcreator/quicxskmapcreator.c diff --git a/src/core/library.c b/src/core/library.c index f7a938cc2c..a96f3cf37d 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -935,6 +935,15 @@ QuicLibraryLazyInitialize( MsQuicLib.Datapath, MsQuicLib.ExecutionConfig->PollingIdleTimeoutUs); } + // + // Apply any pre-configured XDP map configs to the raw datapath. + // + if (MsQuicLib.XdpMapConfigCount > 0 && MsQuicLib.XdpMapConfigs != NULL) { + CxPlatDataPathSetXdpMapConfigs( + MsQuicLib.Datapath, + MsQuicLib.XdpMapConfigs, + MsQuicLib.XdpMapConfigCount); + } } else { MsQuicLibraryFreePartitions(); #ifndef _KERNEL_MODE diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index f98bef52ec..25ed3af898 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -511,6 +511,23 @@ CxPlatDataPathUpdatePollingIdleTimeout( _In_ uint32_t PollingIdleTimeoutUs ); +// +// Sets external XDP map configurations on the datapath. Must be called after +// the datapath is initialized but before sockets start plumbing rules. +// The Configs buffer must remain valid (i.e., point to QUIC_XDP_MAP_CONFIG[]) +// for the lifetime of the datapath. +// +#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES +struct QUIC_XDP_MAP_CONFIG; // Forward declaration +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatDataPathSetXdpMapConfigs( + _In_ CXPLAT_DATAPATH* Datapath, + _In_reads_(Count) const struct QUIC_XDP_MAP_CONFIG* Configs, + _In_ uint32_t Count + ); +#endif + // // Queries the currently supported features of the datapath for the given type // of socket. diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index 90e2a78c49..33d6d8db0d 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -85,6 +85,19 @@ RawDataPathUpdatePollingIdleTimeout( UNREFERENCED_PARAMETER(PollingIdleTimeoutUs); } +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatDpRawSetXdpMapConfigs( + _In_ CXPLAT_DATAPATH_RAW* RawDataPath, + _In_reads_(Count) const QUIC_XDP_MAP_CONFIG* Configs, + _In_ uint32_t Count + ) +{ + UNREFERENCED_PARAMETER(RawDataPath); + UNREFERENCED_PARAMETER(Configs); + UNREFERENCED_PARAMETER(Count); +} + _IRQL_requires_max_(DISPATCH_LEVEL) CXPLAT_DATAPATH_FEATURES RawDataPathGetSupportedFeatures( diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 2ebb36d456..c392b13fa2 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -46,12 +46,19 @@ typedef struct XDP_DATAPATH { BOOLEAN TxAlwaysPoke; BOOLEAN Running; // Signal to stop partitions. + // + // External XDP map configurations (set via QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG). + // + const QUIC_XDP_MAP_CONFIG* XdpMapConfigs; + uint32_t XdpMapConfigCount; + XDP_PARTITION Partitions[0]; } XDP_DATAPATH; typedef struct XDP_INTERFACE { XDP_INTERFACE_COMMON; HANDLE XdpHandle; + HANDLE XskMap; // External XSKMAP handle (map mode). NULL = self-managed. uint8_t RuleCount; CXPLAT_LOCK RuleLock; XDP_RULE* Rules; @@ -858,6 +865,20 @@ CxPlatDpRawInterfaceUpdateRules( _In_ XDP_INTERFACE* Interface ) { + // + // Map mode: an external process owns the XDP program and XSKMAP. + // Skip self-managed rule plumbing. + // TODO Phase 3: Insert per-queue XSK sockets into the map via XdpMapInsert. + // + if (Interface->XskMap != NULL) { + QuicTraceLogVerbose( + XdpMapModeSkipRules, + "[ixdp][%p] Map mode: skipping self-managed XDP rules (IfIndex=%u)", + Interface, + Interface->ActualIfIndex); + return QUIC_STATUS_SUCCESS; + } + static const XDP_HOOK_ID RxHook = { .Layer = XDP_HOOK_L2, .Direction = XDP_HOOK_RX, @@ -2263,3 +2284,37 @@ CxPlatDataPathRssConfigFree( { CXPLAT_FREE(RssConfig, QUIC_POOL_DATAPATH_RSS_CONFIG); } + +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatDpRawSetXdpMapConfigs( + _In_ CXPLAT_DATAPATH_RAW* RawDataPath, + _In_reads_(Count) const QUIC_XDP_MAP_CONFIG* Configs, + _In_ uint32_t Count + ) +{ + XDP_DATAPATH* Xdp = (XDP_DATAPATH*)RawDataPath; + Xdp->XdpMapConfigs = Configs; + Xdp->XdpMapConfigCount = Count; + + // + // Walk all interfaces and set the XskMap handle for matching ones. + // + CXPLAT_LIST_ENTRY* Entry; + for (Entry = Xdp->Interfaces.Flink; Entry != &Xdp->Interfaces; Entry = Entry->Flink) { + XDP_INTERFACE* Interface = CONTAINING_RECORD(Entry, XDP_INTERFACE, Link); + for (uint32_t i = 0; i < Count; i++) { + if (Configs[i].InterfaceIndex == Interface->ActualIfIndex || + Configs[i].InterfaceIndex == Interface->IfIndex) { + Interface->XskMap = (HANDLE)Configs[i].MapHandle; + QuicTraceLogVerbose( + XdpMapModeConfigured, + "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", + Interface, + Interface->ActualIfIndex, + Interface->XskMap); + break; + } + } + } +} diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index f87cff50c6..9bf5b519d4 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -87,6 +87,19 @@ CxPlatDataPathUpdatePollingIdleTimeout( } } +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatDataPathSetXdpMapConfigs( + _In_ CXPLAT_DATAPATH* Datapath, + _In_reads_(Count) const QUIC_XDP_MAP_CONFIG* Configs, + _In_ uint32_t Count + ) +{ + if (Datapath->RawDataPath) { + CxPlatDpRawSetXdpMapConfigs(Datapath->RawDataPath, Configs, Count); + } +} + _IRQL_requires_max_(DISPATCH_LEVEL) CXPLAT_DATAPATH_FEATURES CxPlatDataPathGetSupportedFeatures( diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index f3e9e527d2..f0c4979328 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -1219,6 +1219,16 @@ RawDataPathUpdatePollingIdleTimeout( _In_ uint32_t PollingIdleTimeoutUs ); +#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES +_IRQL_requires_max_(PASSIVE_LEVEL) +void +CxPlatDpRawSetXdpMapConfigs( + _In_ CXPLAT_DATAPATH_RAW* RawDataPath, + _In_reads_(Count) const QUIC_XDP_MAP_CONFIG* Configs, + _In_ uint32_t Count + ); +#endif + _IRQL_requires_max_(DISPATCH_LEVEL) CXPLAT_DATAPATH_FEATURES RawDataPathGetSupportedFeatures( diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index ff9de619f4..314c3eda45 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -40,4 +40,5 @@ if(WIN32 AND (NOT QUIC_UWP_BUILD AND NOT QUIC_GAMECORE_BUILD)) add_subdirectory(recvfuzz) add_subdirectory(xdpmapcreator) add_subdirectory(xdpmapconsumer) + add_subdirectory(quicxskmapcreator) endif() diff --git a/src/tools/quicxskmapcreator/CMakeLists.txt b/src/tools/quicxskmapcreator/CMakeLists.txt new file mode 100644 index 0000000000..5e6836e39a --- /dev/null +++ b/src/tools/quicxskmapcreator/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +add_quic_tool(quicxskmapcreator quicxskmapcreator.c) +quic_tool_warnings(quicxskmapcreator) + +target_compile_definitions(quicxskmapcreator PRIVATE XDP_API_VERSION=3) +target_include_directories(quicxskmapcreator SYSTEM PRIVATE + ${PROJECT_SOURCE_DIR}/submodules/xdp-for-windows/published/external) diff --git a/src/tools/quicxskmapcreator/quicxskmapcreator.c b/src/tools/quicxskmapcreator/quicxskmapcreator.c new file mode 100644 index 0000000000..c89857bca6 --- /dev/null +++ b/src/tools/quicxskmapcreator/quicxskmapcreator.c @@ -0,0 +1,325 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + Creates an XSKMAP, duplicates the handle into a QUIC server process, and + attaches an XDP program with QUIC-aware match rules once the consumer + signals it has inserted its XSKs. + + This tool is the "creator" half of the XSKMAP creator/consumer pattern + for MsQuic. The consumer is typically quicsample running with + -xdp -xdp_map_ifindex (Phase 2) or the MsQuic datapath itself (Phase 3). + + Usage: + 1. Start the QUIC server: + quicsample -server -cert_hash: -xdp -xdp_map_ifindex: + 2. Run this tool: + quicxskmapcreator -TargetPid -IfIndex -UdpPort + 3. Paste the printed handle value into the consumer's stdin + 4. Press Enter here to attach the XDP program + + The creator owns the map and the XDP program lifetime. + +--*/ + +#define _CRT_SECURE_NO_WARNINGS 1 +#define XDP_API_VERSION 3 +#define XDP_INCLUDE_WINCOMMON + +#pragma warning(disable:5105) +#include +#include +#include +#include +#include + +#include + +#define LOGERR(...) \ + fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") + +static UINT32 TargetPid; +static UINT32 IfIndex; +static UINT16 UdpPort; +static BOOLEAN HasCibirId; +static UINT8 CibirIdOffset; +static UINT8 CibirIdData[6]; +static UINT8 CibirIdLength; + +static UINT8 +DecodeHexChar( + char c + ) +{ + if (c >= '0' && c <= '9') return (UINT8)(c - '0'); + if (c >= 'A' && c <= 'F') return (UINT8)(10 + c - 'A'); + if (c >= 'a' && c <= 'f') return (UINT8)(10 + c - 'a'); + return 0; +} + +static UINT32 +DecodeHexBuffer( + const char* Hex, + UINT32 OutLen, + UINT8* Out + ) +{ + UINT32 Len = (UINT32)strlen(Hex) / 2; + if (Len > OutLen) { + Len = OutLen; + } + for (UINT32 i = 0; i < Len; i++) { + Out[i] = (DecodeHexChar(Hex[i * 2]) << 4) | DecodeHexChar(Hex[i * 2 + 1]); + } + return Len; +} + +static void +PrintUsage(void) +{ + printf( + "quicxskmapcreator.exe -TargetPid -IfIndex -UdpPort [OPTIONS]\n" + "\n" + "Creates an XSKMAP, duplicates it into the target QUIC server process, and\n" + "attaches an XDP program with QUIC-aware match rules.\n" + "\n" + "OPTIONS:\n" + " -TargetPid PID of the consumer process (required)\n" + " -IfIndex Network interface index (required)\n" + " -UdpPort QUIC server UDP port (required)\n" + " -CibirId CIBIR ID (hex: first byte is offset, rest is CID prefix)\n" + " Example: -CibirId 00AABBCCDD\n" + ); +} + +static BOOLEAN +ParseArgs( + int ArgC, + char** ArgV + ) +{ + int i = 1; + TargetPid = 0; + IfIndex = MAXUINT32; + UdpPort = 0; + HasCibirId = FALSE; + + while (i < ArgC) { + if (!_stricmp(ArgV[i], "-TargetPid") || !_stricmp(ArgV[i], "-pid")) { + if (++i >= ArgC) { + LOGERR("Missing TargetPid value"); + return FALSE; + } + TargetPid = (UINT32)atoi(ArgV[i]); + } else if (!_stricmp(ArgV[i], "-IfIndex")) { + if (++i >= ArgC) { + LOGERR("Missing IfIndex value"); + return FALSE; + } + IfIndex = (UINT32)atoi(ArgV[i]); + } else if (!_stricmp(ArgV[i], "-UdpPort")) { + if (++i >= ArgC) { + LOGERR("Missing UdpPort value"); + return FALSE; + } + UdpPort = (UINT16)atoi(ArgV[i]); + } else if (!_stricmp(ArgV[i], "-CibirId")) { + if (++i >= ArgC) { + LOGERR("Missing CibirId value"); + return FALSE; + } + UINT8 CibirRaw[7]; // offset (1 byte) + max 6 bytes CID + UINT32 CibirRawLen = DecodeHexBuffer(ArgV[i], sizeof(CibirRaw), CibirRaw); + if (CibirRawLen < 2) { + LOGERR("CIBIR ID too short (need at least offset + 1 byte CID)"); + return FALSE; + } + CibirIdOffset = CibirRaw[0]; + CibirIdLength = (UINT8)(CibirRawLen - 1); + memcpy(CibirIdData, &CibirRaw[1], CibirIdLength); + HasCibirId = TRUE; + } else { + LOGERR("Unexpected parameter \"%s\"", ArgV[i]); + return FALSE; + } + ++i; + } + + if (TargetPid == 0) { + LOGERR("-TargetPid is required"); + return FALSE; + } + if (IfIndex == MAXUINT32) { + LOGERR("-IfIndex is required"); + return FALSE; + } + if (UdpPort == 0) { + LOGERR("-UdpPort is required"); + return FALSE; + } + + return TRUE; +} + +int +__cdecl +main( + int argc, + char** argv + ) +{ + XDP_STATUS XdpStatus; + HANDLE XskMap = NULL; + HANDLE TargetProcess = NULL; + HANDLE RemoteHandle = NULL; + HANDLE Program = NULL; + + if (!ParseArgs(argc, argv)) { + PrintUsage(); + return 1; + } + + // + // Stage 1: Create the XSKMAP. + // + XdpStatus = XdpMapCreate(&XskMap, XDP_MAP_TYPE_XSKMAP); + if (FAILED(XdpStatus)) { + LOGERR("XdpMapCreate failed: 0x%x", XdpStatus); + return 1; + } + + printf("[Stage 1] Created XSKMAP (local handle: 0x%IX)\n", (UINT_PTR)XskMap); + + // + // Stage 2: Duplicate the map handle into the consumer process. + // + TargetProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, TargetPid); + if (TargetProcess == NULL) { + LOGERR("OpenProcess(%u) failed: %u. Ensure the consumer is running.", + TargetPid, GetLastError()); + CloseHandle(XskMap); + return 1; + } + + if (!DuplicateHandle( + GetCurrentProcess(), + XskMap, + TargetProcess, + &RemoteHandle, + 0, + FALSE, + DUPLICATE_SAME_ACCESS)) { + LOGERR("DuplicateHandle failed: %u", GetLastError()); + CloseHandle(TargetProcess); + CloseHandle(XskMap); + return 1; + } + + CloseHandle(TargetProcess); + + printf("[Stage 2] Duplicated XSKMAP into PID %u. Remote handle: 0x%IX\n", + TargetPid, (UINT_PTR)RemoteHandle); + printf("\n"); + printf(" Paste 0x%IX into the consumer, let it insert its XSKs.\n", + (UINT_PTR)RemoteHandle); + printf("\n"); + + // + // Stage 3: Wait for user signal that the consumer is ready. + // + printf("[Stage 3] Press ENTER when the consumer has inserted its XSKs...\n"); + fflush(stdout); + (void)getchar(); + + // + // Stage 4: Attach the XDP program with QUIC-aware match rules. + // + { + const XDP_HOOK_ID XdpInspectRxL2 = { + XDP_HOOK_L2, + XDP_HOOK_RX, + XDP_HOOK_INSPECT, + }; + + XDP_RULE Rules[2]; + UINT32 RuleCount = 0; + ZeroMemory(Rules, sizeof(Rules)); + + if (HasCibirId) { + // + // CIBIR mode: match on QUIC connection IDs. + // + Rules[RuleCount].Match = XDP_MATCH_QUIC_FLOW_SRC_CID; + Rules[RuleCount].Pattern.QuicFlow.UdpPort = htons(UdpPort); + Rules[RuleCount].Pattern.QuicFlow.CidLength = CibirIdLength; + Rules[RuleCount].Pattern.QuicFlow.CidOffset = CibirIdOffset; + memcpy(Rules[RuleCount].Pattern.QuicFlow.CidData, CibirIdData, CibirIdLength); + Rules[RuleCount].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RuleCount].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RuleCount].Redirect.Target = XskMap; + RuleCount++; + + Rules[RuleCount].Match = XDP_MATCH_QUIC_FLOW_DST_CID; + Rules[RuleCount].Pattern.QuicFlow.UdpPort = htons(UdpPort); + Rules[RuleCount].Pattern.QuicFlow.CidLength = CibirIdLength; + Rules[RuleCount].Pattern.QuicFlow.CidOffset = CibirIdOffset; + memcpy(Rules[RuleCount].Pattern.QuicFlow.CidData, CibirIdData, CibirIdLength); + Rules[RuleCount].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RuleCount].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RuleCount].Redirect.Target = XskMap; + RuleCount++; + } else { + // + // Simple mode: match on UDP destination port. + // + Rules[RuleCount].Match = XDP_MATCH_UDP_DST; + Rules[RuleCount].Pattern.Port = htons(UdpPort); + Rules[RuleCount].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RuleCount].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RuleCount].Redirect.Target = XskMap; + RuleCount++; + } + + XdpStatus = + XdpCreateProgram( + IfIndex, + &XdpInspectRxL2, + 0, + XDP_CREATE_PROGRAM_FLAG_ALL_QUEUES, + Rules, + RuleCount, + &Program); + if (FAILED(XdpStatus)) { + LOGERR("XdpCreateProgram failed: 0x%x", XdpStatus); + CloseHandle(XskMap); + return 1; + } + } + + printf("[Stage 4] XDP program attached on IfIndex %u\n", IfIndex); + if (HasCibirId) { + printf(" Rules: XDP_MATCH_QUIC_FLOW_SRC_CID + XDP_MATCH_QUIC_FLOW_DST_CID\n"); + printf(" UdpPort: %u, CidOffset: %u, CidLength: %u\n", + UdpPort, CibirIdOffset, CibirIdLength); + } else { + printf(" Rule: XDP_MATCH_UDP_DST, Port: %u\n", UdpPort); + } + printf(" Target: XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID\n"); + printf("\n"); + printf("Press ENTER to detach the program and exit.\n"); + fflush(stdout); + (void)getchar(); + + // + // Cleanup: closing the program handle detaches XDP. Closing the map + // handle releases our reference (consumer may still hold one). + // + printf("Detaching XDP program and cleaning up.\n"); + CloseHandle(Program); + CloseHandle(XskMap); + + return 0; +} diff --git a/src/tools/sample/sample.c b/src/tools/sample/sample.c index 1f1f519d61..fda35a2225 100644 --- a/src/tools/sample/sample.c +++ b/src/tools/sample/sample.c @@ -129,10 +129,14 @@ void PrintUsage() #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES " quicsample.exe -multiclient -count: -unsecure -target:{IPAddress|Hostname}\n" #endif - " quicsample.exe -server -cert_hash:<...> [-xdp] [-cibir_id:]\n" - " quicsample.exe -server -cert_file:<...> -key_file:<...> [-password:<...>] [-xdp] [-cibir_id:]\n" + " quicsample.exe -server -cert_hash:<...> [-xdp] [-xdp_map_ifindex:] [-cibir_id:]\n" + " quicsample.exe -server -cert_file:<...> -key_file:<...> [-password:<...>] [-xdp] [-xdp_map_ifindex:] [-cibir_id:]\n" "\n" - " -xdp Enable XDP datapath (server only, requires XDP driver)\n" + " -xdp Enable XDP datapath (server only, requires XDP driver).\n" + " When enabled, prints the exact XDP rules being plumbed.\n" + " -xdp_map_ifindex:\n" + " Enable XDP map mode for the given interface index. The server\n" + " waits for an XSKMAP handle from quicxskmapcreator. Requires -xdp.\n" " -cibir_id: Set CIBIR ID (hex: first byte is offset, rest is CID prefix)\n" " Example: -cibir_id:00AABBCCDD (offset=0, CID=AABBCCDD)\n" ); @@ -706,6 +710,74 @@ RunServer( goto Error; } + // + // Print the XDP rules that were plumbed (if XDP is enabled). + // This mirrors the rule construction in CxPlatDpRawPlumbRulesOnSocket + // so the user knows exactly what rules to replicate in an external + // map creator (quicxskmapcreator). + // +#ifdef _WIN32 + if (GetFlag(argc, argv, "xdp")) { + const char* CibirIdHex = GetValue(argc, argv, "cibir_id"); + + printf("\n"); + printf("=== XDP Rules Plumbed ===\n"); + printf(" PID: %u\n", (unsigned)GetCurrentProcessId()); + printf(" Port: %u\n", UdpPort); + printf("\n"); + + if (CibirIdHex != NULL) { + uint8_t CibirId[7]; + uint32_t CibirIdLen = DecodeHexBuffer(CibirIdHex, sizeof(CibirId), CibirId); + uint8_t CidOffset = CibirId[0]; + uint8_t CidLength = (uint8_t)(CibirIdLen - 1); + + printf(" Rule 1: XDP_MATCH_QUIC_FLOW_SRC_CID\n"); + printf(" UdpPort = %u\n", UdpPort); + printf(" CidOffset = %u\n", CidOffset); + printf(" CidLength = %u\n", CidLength); + printf(" CidData = "); + for (uint32_t k = 1; k < CibirIdLen; k++) { + printf("%02X", CibirId[k]); + } + printf("\n"); + printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); + printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); + printf("\n"); + + printf(" Rule 2: XDP_MATCH_QUIC_FLOW_DST_CID\n"); + printf(" UdpPort = %u\n", UdpPort); + printf(" CidOffset = %u\n", CidOffset); + printf(" CidLength = %u\n", CidLength); + printf(" CidData = "); + for (uint32_t k = 1; k < CibirIdLen; k++) { + printf("%02X", CibirId[k]); + } + printf("\n"); + printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); + printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); + } else { + printf(" Rule 1: XDP_MATCH_UDP_DST\n"); + printf(" Port = %u\n", UdpPort); + printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); + printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); + } + + printf("\n"); + printf(" Hook: XDP_HOOK_L2 / XDP_HOOK_RX / XDP_HOOK_INSPECT\n"); + printf(" Flags: per-queue (one XdpCreateProgram per RSS queue)\n"); + printf("=========================\n"); + printf("\n"); + printf("To use with quicxskmapcreator, pass:\n"); + printf(" quicxskmapcreator -TargetPid %u -IfIndex -UdpPort %u", + (unsigned)GetCurrentProcessId(), UdpPort); + if (CibirIdHex != NULL) { + printf(" -CibirId %s", CibirIdHex); + } + printf("\n\n"); + } +#endif + // // Continue listening for connections until the Enter key is pressed. // @@ -1181,6 +1253,62 @@ main( goto Error; } + // + // If the server will use XDP map mode, configure the map before + // RegistrationOpen (which triggers lazy init and creates the datapath). + // QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG must be set before LazyInitComplete. + // +#ifdef _WIN32 + if (GetFlag(argc, argv, "server") && GetFlag(argc, argv, "xdp")) { + const char* MapIfIndexStr = GetValue(argc, argv, "xdp_map_ifindex"); + if (MapIfIndexStr != NULL) { + UINT32 MapIfIndex = (UINT32)atoi(MapIfIndexStr); + char InputBuf[64]; + UINT_PTR HandleValue; + + printf("=== XDP Map Mode ===\n"); + printf(" PID: %u\n", (unsigned)GetCurrentProcessId()); + printf(" IfIndex: %u\n", MapIfIndex); + printf("\n"); + printf("Start quicxskmapcreator in another terminal:\n"); + printf(" quicxskmapcreator -TargetPid %u -IfIndex %u -UdpPort %u\n", + (unsigned)GetCurrentProcessId(), MapIfIndex, UdpPort); + printf("\n"); + printf("Paste the XSKMAP handle value here (hex): "); + fflush(stdout); + + if (fgets(InputBuf, sizeof(InputBuf), stdin) == NULL) { + printf("Failed to read input!\n"); + goto Error; + } + + HandleValue = (UINT_PTR)_strtoui64(InputBuf, NULL, 16); + if (HandleValue == 0 || HandleValue == (UINT_PTR)INVALID_HANDLE_VALUE) { + printf("Invalid handle value: %s\n", InputBuf); + goto Error; + } + + QUIC_XDP_MAP_CONFIG MapConfig; + MapConfig.InterfaceIndex = MapIfIndex; + MapConfig.MapHandle = (QUIC_XDP_MAP_HANDLE)HandleValue; + + Status = MsQuic->SetParam( + NULL, + QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG, + sizeof(MapConfig), + &MapConfig); + if (QUIC_FAILED(Status)) { + printf("SetParam(QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG) failed, 0x%x!\n", Status); + goto Error; + } + + printf("XDP map config set (IfIndex=%u, MapHandle=0x%IX).\n", + MapIfIndex, HandleValue); + printf("====================\n\n"); + } + } +#endif + // // Create a registration for the app's connections. // From 7ba0bf1716e464cb4e581be6c231a349da3dd223 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 15 May 2026 16:28:17 -0700 Subject: [PATCH 03/41] phase 2 improvements --- src/core/library.c | 2 + src/inc/quic_datapath.h | 8 +++ src/platform/datapath_xplat.c | 92 ++++++++++++++++++++++++-------- src/platform/platform_internal.h | 6 +++ 4 files changed, 87 insertions(+), 21 deletions(-) diff --git a/src/core/library.c b/src/core/library.c index a96f3cf37d..16906fcf94 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -915,6 +915,8 @@ QuicLibraryLazyInitialize( CXPLAT_DATAPATH_INIT_CONFIG InitConfig = {0}; InitConfig.EnableDscpOnRecv = MsQuicLib.EnableDscpOnRecv; + InitConfig.XdpMapMode = + MsQuicLib.XdpMapConfigCount > 0 && MsQuicLib.XdpMapConfigs != NULL; Status = CxPlatDataPathInitialize( diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 25ed3af898..1729fde6f8 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -476,6 +476,14 @@ typedef struct CXPLAT_DATAPATH_INIT_CONFIG { // the Windows fast path causing a large performance regression. // BOOLEAN EnableDscpOnRecv; + + // + // When TRUE, the datapath operates in XDP map mode: the WinSock (normal) + // datapath is skipped and the raw (XDP) datapath is required to succeed. + // An external process owns the XDP program; this process only creates XSK + // sockets and inserts them into a shared XSKMAP. + // + BOOLEAN XdpMapMode; } CXPLAT_DATAPATH_INIT_CONFIG; // diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index 9bf5b519d4..0ec4ae64a1 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -32,29 +32,71 @@ CxPlatDataPathInitialize( goto Error; } - Status = - DataPathInitialize( + if (InitConfig->XdpMapMode) { + // + // XDP map mode: bypass the platform datapath (WinSock/epoll) entirely. + // Allocate only the common base struct for callbacks and the raw + // datapath pointer. The raw (XDP) datapath is required to succeed. + // + CXPLAT_DATAPATH_COMMON* Common = + CXPLAT_ALLOC_PAGED(sizeof(CXPLAT_DATAPATH_COMMON), QUIC_POOL_DATAPATH); + if (Common == NULL) { + QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_DATAPATH_COMMON (map mode)", + sizeof(CXPLAT_DATAPATH_COMMON)); + Status = QUIC_STATUS_OUT_OF_MEMORY; + goto Error; + } + + CxPlatZeroMemory(Common, sizeof(*Common)); + if (UdpCallbacks) { + Common->UdpHandlers = *UdpCallbacks; + } + Common->WorkerPool = WorkerPool; + + *NewDataPath = (CXPLAT_DATAPATH*)Common; + + RawDataPathInitialize( ClientRecvContextLength, - UdpCallbacks, - TcpCallbacks, + *NewDataPath, WorkerPool, - InitConfig, - NewDataPath); - if (QUIC_FAILED(Status)) { - QuicTraceLogVerbose( - DatapathInitFail, - "[ dp] Failed to initialize datapath, status:%d", Status); - goto Error; - } + &Common->RawDataPath); + if (Common->RawDataPath == NULL) { + QuicTraceLogVerbose( + DatapathRawInitFailMapMode, + "[ dp] XDP map mode: raw datapath required but failed to initialize"); + CXPLAT_FREE(Common, QUIC_POOL_DATAPATH); + *NewDataPath = NULL; + Status = QUIC_STATUS_NOT_SUPPORTED; + goto Error; + } + } else { + Status = + DataPathInitialize( + ClientRecvContextLength, + UdpCallbacks, + TcpCallbacks, + WorkerPool, + InitConfig, + NewDataPath); + if (QUIC_FAILED(Status)) { + QuicTraceLogVerbose( + DatapathInitFail, + "[ dp] Failed to initialize datapath, status:%d", Status); + goto Error; + } - // - // Best effort try to initialize the raw datapath. - // - RawDataPathInitialize( - ClientRecvContextLength, - *NewDataPath, - WorkerPool, - &((*NewDataPath)->RawDataPath)); + // + // Best effort try to initialize the raw datapath. + // + RawDataPathInitialize( + ClientRecvContextLength, + *NewDataPath, + WorkerPool, + &((*NewDataPath)->RawDataPath)); + } Error: @@ -70,7 +112,15 @@ CxPlatDataPathUninitialize( if (Datapath->RawDataPath) { RawDataPathUninitialize(Datapath->RawDataPath); } - DataPathUninitialize(Datapath); + if (Datapath->XdpMapMode) { + // + // Map mode: this is just a CXPLAT_DATAPATH_COMMON, not a full + // platform datapath. Free directly without platform uninit. + // + CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); + } else { + DataPathUninitialize(Datapath); + } } _IRQL_requires_max_(PASSIVE_LEVEL) diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index f0c4979328..5a725602f7 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -61,6 +61,12 @@ typedef struct CXPLAT_DATAPATH_COMMON { CXPLAT_DATAPATH_FEATURES Features; CXPLAT_DATAPATH_RAW* RawDataPath; + + // + // When TRUE, this struct is the entire datapath (no platform-specific + // fields). The raw (XDP) datapath owns all partition/IO state. + // + BOOLEAN XdpMapMode; } CXPLAT_DATAPATH_COMMON; typedef struct CXPLAT_SOCKET_COMMON { From b364f47feb5ac6cb91b1eb85c45764833c578dd5 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 15 May 2026 16:35:31 -0700 Subject: [PATCH 04/41] better diagnostics --- src/tools/sample/sample.c | 106 ++++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/src/tools/sample/sample.c b/src/tools/sample/sample.c index fda35a2225..f20ecfb87e 100644 --- a/src/tools/sample/sample.c +++ b/src/tools/sample/sample.c @@ -711,70 +711,76 @@ RunServer( } // - // Print the XDP rules that were plumbed (if XDP is enabled). - // This mirrors the rule construction in CxPlatDpRawPlumbRulesOnSocket - // so the user knows exactly what rules to replicate in an external - // map creator (quicxskmapcreator). + // Print XDP status info after the listener starts. // #ifdef _WIN32 if (GetFlag(argc, argv, "xdp")) { const char* CibirIdHex = GetValue(argc, argv, "cibir_id"); + BOOLEAN MapMode = (GetValue(argc, argv, "xdp_map_ifindex") != NULL); - printf("\n"); - printf("=== XDP Rules Plumbed ===\n"); - printf(" PID: %u\n", (unsigned)GetCurrentProcessId()); - printf(" Port: %u\n", UdpPort); printf("\n"); - if (CibirIdHex != NULL) { - uint8_t CibirId[7]; - uint32_t CibirIdLen = DecodeHexBuffer(CibirIdHex, sizeof(CibirId), CibirId); - uint8_t CidOffset = CibirId[0]; - uint8_t CidLength = (uint8_t)(CibirIdLen - 1); - - printf(" Rule 1: XDP_MATCH_QUIC_FLOW_SRC_CID\n"); - printf(" UdpPort = %u\n", UdpPort); - printf(" CidOffset = %u\n", CidOffset); - printf(" CidLength = %u\n", CidLength); - printf(" CidData = "); - for (uint32_t k = 1; k < CibirIdLen; k++) { - printf("%02X", CibirId[k]); - } - printf("\n"); - printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); - printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); + if (MapMode) { + // + // Map mode: rules are NOT plumbed by us. The external + // quicxskmapcreator process owns the XDP program. + // + printf("=== XDP Map Mode (consumer) ===\n"); + printf(" XDP rules were NOT plumbed by this process.\n"); + printf(" Waiting for quicxskmapcreator to attach the XDP program.\n"); + printf("===============================\n"); + } else { + // + // Self-managed mode: print the rules that were plumbed. + // + printf("=== XDP Rules Plumbed ===\n"); + printf(" Port: %u\n", UdpPort); printf("\n"); - printf(" Rule 2: XDP_MATCH_QUIC_FLOW_DST_CID\n"); - printf(" UdpPort = %u\n", UdpPort); - printf(" CidOffset = %u\n", CidOffset); - printf(" CidLength = %u\n", CidLength); - printf(" CidData = "); - for (uint32_t k = 1; k < CibirIdLen; k++) { - printf("%02X", CibirId[k]); + if (CibirIdHex != NULL) { + uint8_t CibirId[7]; + uint32_t CibirIdLen = DecodeHexBuffer(CibirIdHex, sizeof(CibirId), CibirId); + uint8_t CidOffset = CibirId[0]; + uint8_t CidLength = (uint8_t)(CibirIdLen - 1); + + printf(" Rule 1: XDP_MATCH_QUIC_FLOW_SRC_CID\n"); + printf(" UdpPort = %u\n", UdpPort); + printf(" CidOffset = %u\n", CidOffset); + printf(" CidLength = %u\n", CidLength); + printf(" CidData = "); + for (uint32_t k = 1; k < CibirIdLen; k++) { + printf("%02X", CibirId[k]); + } + printf("\n"); + printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); + printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); + printf("\n"); + + printf(" Rule 2: XDP_MATCH_QUIC_FLOW_DST_CID\n"); + printf(" UdpPort = %u\n", UdpPort); + printf(" CidOffset = %u\n", CidOffset); + printf(" CidLength = %u\n", CidLength); + printf(" CidData = "); + for (uint32_t k = 1; k < CibirIdLen; k++) { + printf("%02X", CibirId[k]); + } + printf("\n"); + printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); + printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); + } else { + printf(" Rule 1: XDP_MATCH_UDP_DST\n"); + printf(" Port = %u\n", UdpPort); + printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); + printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); } + printf("\n"); - printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); - printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); - } else { - printf(" Rule 1: XDP_MATCH_UDP_DST\n"); - printf(" Port = %u\n", UdpPort); - printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); - printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); + printf(" Hook: XDP_HOOK_L2 / XDP_HOOK_RX / XDP_HOOK_INSPECT\n"); + printf(" Flags: per-queue (one XdpCreateProgram per RSS queue)\n"); + printf("=========================\n"); } printf("\n"); - printf(" Hook: XDP_HOOK_L2 / XDP_HOOK_RX / XDP_HOOK_INSPECT\n"); - printf(" Flags: per-queue (one XdpCreateProgram per RSS queue)\n"); - printf("=========================\n"); - printf("\n"); - printf("To use with quicxskmapcreator, pass:\n"); - printf(" quicxskmapcreator -TargetPid %u -IfIndex -UdpPort %u", - (unsigned)GetCurrentProcessId(), UdpPort); - if (CibirIdHex != NULL) { - printf(" -CibirId %s", CibirIdHex); - } - printf("\n\n"); } #endif From 01df0620bbd00297eded6d22ef919a7b67205b1e Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 15 May 2026 17:18:24 -0700 Subject: [PATCH 05/41] phase 3 plumbing... --- src/platform/datapath_raw.c | 16 +++++---- src/platform/datapath_raw_win.c | 42 ++++++++++++----------- src/platform/datapath_raw_xdp_win.c | 52 +++++++++++++++++++++-------- 3 files changed, 69 insertions(+), 41 deletions(-) diff --git a/src/platform/datapath_raw.c b/src/platform/datapath_raw.c index 99d0ec67e1..f67c21d755 100644 --- a/src/platform/datapath_raw.c +++ b/src/platform/datapath_raw.c @@ -179,13 +179,15 @@ RawSocketDelete( // some XDP rules may linger until the interface is reinitialized, but // there is nothing useful we can do at this point. // - QUIC_STATUS PlumbStatus = CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); - if (QUIC_FAILED(PlumbStatus)) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - PlumbStatus, - "CxPlatDpRawPlumbRulesOnSocket (delete)"); + if (!Socket->RawDatapath->ParentDataPath->XdpMapMode) { + QUIC_STATUS PlumbStatus = CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); + if (QUIC_FAILED(PlumbStatus)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + PlumbStatus, + "CxPlatDpRawPlumbRulesOnSocket (delete)"); + } } CxPlatRemoveSocket(&Socket->RawDatapath->SocketPool, Socket); CxPlatRundownReleaseAndWait(&Socket->RawRundown); diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index d409a43c7a..c3dcfb3405 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -170,27 +170,29 @@ RawSocketCreateUdp( goto Error; } - Status = CxPlatDpRawPlumbRulesOnSocket(Socket, TRUE); - if (QUIC_FAILED(Status)) { - // - // CxPlatDpRawPlumbRulesOnSocket(TRUE) stops at the first interface - // where rule installation fails, so some interfaces may already have - // partial state (rules installed or port bits set) that must be rolled - // back. Reuse the IsCreated=FALSE path for best-effort cleanup; it is - // safe to call against interfaces where nothing was installed (no-op - // for those). Cleanup failures are logged but do not change the - // returned error. - // - QUIC_STATUS CleanupStatus = CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); - if (QUIC_FAILED(CleanupStatus)) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - CleanupStatus, - "CxPlatDpRawPlumbRulesOnSocket cleanup"); + if (!Raw->ParentDataPath->XdpMapMode) { + Status = CxPlatDpRawPlumbRulesOnSocket(Socket, TRUE); + if (QUIC_FAILED(Status)) { + // + // CxPlatDpRawPlumbRulesOnSocket(TRUE) stops at the first interface + // where rule installation fails, so some interfaces may already have + // partial state (rules installed or port bits set) that must be rolled + // back. Reuse the IsCreated=FALSE path for best-effort cleanup; it is + // safe to call against interfaces where nothing was installed (no-op + // for those). Cleanup failures are logged but do not change the + // returned error. + // + QUIC_STATUS CleanupStatus = CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); + if (QUIC_FAILED(CleanupStatus)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + CleanupStatus, + "CxPlatDpRawPlumbRulesOnSocket cleanup"); + } + CxPlatRemoveSocket(&Raw->SocketPool, Socket); + goto Error; } - CxPlatRemoveSocket(&Raw->SocketPool, Socket); - goto Error; } Error: diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index c392b13fa2..114e13317c 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -338,6 +338,14 @@ CxPlatDpRawInterfaceUninitialize( for (uint32_t i = 0; Interface->Queues != NULL && i < Interface->QueueCount; i++) { CXPLAT_QUEUE *Queue = &Interface->Queues[i]; + // + // In map mode, remove this queue's XSK from the shared map before + // closing the socket handle. + // + if (Interface->XskMap != NULL && Queue->RxXsk != NULL) { + (void)XdpMapDelete(Interface->XskMap, &i); + } + if (Queue->TxXsk != NULL) { CloseHandle(Queue->TxXsk); } @@ -470,6 +478,8 @@ CxPlatDpRawInterfaceInitialize( goto Error; } + CXPLAT_DBG_ASSERT(Interface->QueueCount <= 128); // XSKMAP_MAX_SIZE + if (Interface->QueueCount == 0) { Status = QUIC_STATUS_INVALID_STATE; QuicTraceEvent( @@ -847,6 +857,34 @@ CxPlatDpRawInterfaceInitialize( } } + // + // Map mode: insert each queue's RX XSK into the shared XSKMAP. + // Done once here at init time; PlumbRules is not called in map mode. + // + if (Interface->XskMap != NULL) { + for (uint32_t i = 0; i < Interface->QueueCount; i++) { + CXPLAT_QUEUE* Queue = &Interface->Queues[i]; + if (Queue->RxXsk == NULL) { + continue; + } + Status = (QUIC_STATUS)XdpMapInsert(Interface->XskMap, &i, &Queue->RxXsk); + if (QUIC_FAILED(Status)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Status, + "XdpMapInsert"); + goto Error; + } + QuicTraceLogVerbose( + XdpMapModeInserted, + "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", + Interface, + i, + Interface->ActualIfIndex); + } + } + Error: if (QUIC_FAILED(Status)) { CxPlatDpRawInterfaceUninitialize(Interface); @@ -865,20 +903,6 @@ CxPlatDpRawInterfaceUpdateRules( _In_ XDP_INTERFACE* Interface ) { - // - // Map mode: an external process owns the XDP program and XSKMAP. - // Skip self-managed rule plumbing. - // TODO Phase 3: Insert per-queue XSK sockets into the map via XdpMapInsert. - // - if (Interface->XskMap != NULL) { - QuicTraceLogVerbose( - XdpMapModeSkipRules, - "[ixdp][%p] Map mode: skipping self-managed XDP rules (IfIndex=%u)", - Interface, - Interface->ActualIfIndex); - return QUIC_STATUS_SUCCESS; - } - static const XDP_HOOK_ID RxHook = { .Layer = XDP_HOOK_L2, .Direction = XDP_HOOK_RX, From 3097b18fad6562f1f8b209c6eb627d539b3529e1 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 15 May 2026 17:46:38 -0700 Subject: [PATCH 06/41] phase 3: iter 2 --- src/platform/datapath_xplat.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index 0ec4ae64a1..d68f6db5fc 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -35,39 +35,41 @@ CxPlatDataPathInitialize( if (InitConfig->XdpMapMode) { // // XDP map mode: bypass the platform datapath (WinSock/epoll) entirely. - // Allocate only the common base struct for callbacks and the raw - // datapath pointer. The raw (XDP) datapath is required to succeed. + // Allocate the full CXPLAT_DATAPATH struct (zero-initialized) so that + // any code traversing platform-specific fields sees safe defaults. + // Only the common base fields and the raw datapath are used. // - CXPLAT_DATAPATH_COMMON* Common = - CXPLAT_ALLOC_PAGED(sizeof(CXPLAT_DATAPATH_COMMON), QUIC_POOL_DATAPATH); - if (Common == NULL) { + CXPLAT_DATAPATH* Datapath = + CXPLAT_ALLOC_PAGED(sizeof(CXPLAT_DATAPATH), QUIC_POOL_DATAPATH); + if (Datapath == NULL) { QuicTraceEvent( AllocFailure, "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH_COMMON (map mode)", - sizeof(CXPLAT_DATAPATH_COMMON)); + "CXPLAT_DATAPATH (map mode)", + sizeof(CXPLAT_DATAPATH)); Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } - CxPlatZeroMemory(Common, sizeof(*Common)); + CxPlatZeroMemory(Datapath, sizeof(CXPLAT_DATAPATH)); if (UdpCallbacks) { - Common->UdpHandlers = *UdpCallbacks; + Datapath->UdpHandlers = *UdpCallbacks; } - Common->WorkerPool = WorkerPool; + Datapath->WorkerPool = WorkerPool; + Datapath->XdpMapMode = TRUE; - *NewDataPath = (CXPLAT_DATAPATH*)Common; + *NewDataPath = Datapath; RawDataPathInitialize( ClientRecvContextLength, *NewDataPath, WorkerPool, - &Common->RawDataPath); - if (Common->RawDataPath == NULL) { + &Datapath->RawDataPath); + if (Datapath->RawDataPath == NULL) { QuicTraceLogVerbose( DatapathRawInitFailMapMode, "[ dp] XDP map mode: raw datapath required but failed to initialize"); - CXPLAT_FREE(Common, QUIC_POOL_DATAPATH); + CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); *NewDataPath = NULL; Status = QUIC_STATUS_NOT_SUPPORTED; goto Error; From 5750131ff32e4966083d8c33b515883b86f977f6 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 15 May 2026 18:21:37 -0700 Subject: [PATCH 07/41] bug fixes man --- src/platform/datapath_raw_xdp_win.c | 90 +++++++++++------------------ 1 file changed, 34 insertions(+), 56 deletions(-) diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 114e13317c..3059d8c583 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -46,19 +46,12 @@ typedef struct XDP_DATAPATH { BOOLEAN TxAlwaysPoke; BOOLEAN Running; // Signal to stop partitions. - // - // External XDP map configurations (set via QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG). - // - const QUIC_XDP_MAP_CONFIG* XdpMapConfigs; - uint32_t XdpMapConfigCount; - XDP_PARTITION Partitions[0]; } XDP_DATAPATH; typedef struct XDP_INTERFACE { XDP_INTERFACE_COMMON; HANDLE XdpHandle; - HANDLE XskMap; // External XSKMAP handle (map mode). NULL = self-managed. uint8_t RuleCount; CXPLAT_LOCK RuleLock; XDP_RULE* Rules; @@ -338,14 +331,6 @@ CxPlatDpRawInterfaceUninitialize( for (uint32_t i = 0; Interface->Queues != NULL && i < Interface->QueueCount; i++) { CXPLAT_QUEUE *Queue = &Interface->Queues[i]; - // - // In map mode, remove this queue's XSK from the shared map before - // closing the socket handle. - // - if (Interface->XskMap != NULL && Queue->RxXsk != NULL) { - (void)XdpMapDelete(Interface->XskMap, &i); - } - if (Queue->TxXsk != NULL) { CloseHandle(Queue->TxXsk); } @@ -857,34 +842,6 @@ CxPlatDpRawInterfaceInitialize( } } - // - // Map mode: insert each queue's RX XSK into the shared XSKMAP. - // Done once here at init time; PlumbRules is not called in map mode. - // - if (Interface->XskMap != NULL) { - for (uint32_t i = 0; i < Interface->QueueCount; i++) { - CXPLAT_QUEUE* Queue = &Interface->Queues[i]; - if (Queue->RxXsk == NULL) { - continue; - } - Status = (QUIC_STATUS)XdpMapInsert(Interface->XskMap, &i, &Queue->RxXsk); - if (QUIC_FAILED(Status)) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Status, - "XdpMapInsert"); - goto Error; - } - QuicTraceLogVerbose( - XdpMapModeInserted, - "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", - Interface, - i, - Interface->ActualIfIndex); - } - } - Error: if (QUIC_FAILED(Status)) { CxPlatDpRawInterfaceUninitialize(Interface); @@ -2318,27 +2275,48 @@ CxPlatDpRawSetXdpMapConfigs( ) { XDP_DATAPATH* Xdp = (XDP_DATAPATH*)RawDataPath; - Xdp->XdpMapConfigs = Configs; - Xdp->XdpMapConfigCount = Count; // - // Walk all interfaces and set the XskMap handle for matching ones. + // Walk all interfaces. For each matching map config, insert every + // queue's RX XSK into the shared XSKMAP immediately. // CXPLAT_LIST_ENTRY* Entry; for (Entry = Xdp->Interfaces.Flink; Entry != &Xdp->Interfaces; Entry = Entry->Flink) { XDP_INTERFACE* Interface = CONTAINING_RECORD(Entry, XDP_INTERFACE, Link); for (uint32_t i = 0; i < Count; i++) { - if (Configs[i].InterfaceIndex == Interface->ActualIfIndex || - Configs[i].InterfaceIndex == Interface->IfIndex) { - Interface->XskMap = (HANDLE)Configs[i].MapHandle; - QuicTraceLogVerbose( - XdpMapModeConfigured, - "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", - Interface, - Interface->ActualIfIndex, - Interface->XskMap); - break; + if (Configs[i].InterfaceIndex != Interface->ActualIfIndex && + Configs[i].InterfaceIndex != Interface->IfIndex) { + continue; + } + HANDLE XskMap = (HANDLE)Configs[i].MapHandle; + QuicTraceLogVerbose( + XdpMapModeConfigured, + "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", + Interface, + Interface->ActualIfIndex, + XskMap); + for (uint32_t j = 0; j < Interface->QueueCount; j++) { + CXPLAT_QUEUE* Queue = &Interface->Queues[j]; + if (Queue->RxXsk == NULL) { + continue; + } + HRESULT Hr = XdpMapInsert(XskMap, &j, &Queue->RxXsk); + if (FAILED(Hr)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + Hr, + "XdpMapInsert"); + } else { + QuicTraceLogVerbose( + XdpMapModeInserted, + "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", + Interface, + j, + Interface->ActualIfIndex); + } } + break; } } } From bbf64e036c3b5e225feb07659a3ef02baa5853af Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 15 May 2026 18:35:25 -0700 Subject: [PATCH 08/41] E2E --- src/core/library.c | 13 ++------ src/inc/quic_datapath.h | 31 ++++++------------- src/platform/datapath_raw_dummy.c | 13 ++++---- src/platform/datapath_raw_xdp_win.c | 46 ++++++++++++++++------------- src/platform/datapath_xplat.c | 31 ++++++++++--------- src/platform/platform_internal.h | 13 +++++--- 6 files changed, 70 insertions(+), 77 deletions(-) diff --git a/src/core/library.c b/src/core/library.c index 16906fcf94..b7e37ab24c 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -915,8 +915,8 @@ QuicLibraryLazyInitialize( CXPLAT_DATAPATH_INIT_CONFIG InitConfig = {0}; InitConfig.EnableDscpOnRecv = MsQuicLib.EnableDscpOnRecv; - InitConfig.XdpMapMode = - MsQuicLib.XdpMapConfigCount > 0 && MsQuicLib.XdpMapConfigs != NULL; + InitConfig.XdpMapConfigs = MsQuicLib.XdpMapConfigs; + InitConfig.XdpMapConfigCount = MsQuicLib.XdpMapConfigCount; Status = CxPlatDataPathInitialize( @@ -937,15 +937,6 @@ QuicLibraryLazyInitialize( MsQuicLib.Datapath, MsQuicLib.ExecutionConfig->PollingIdleTimeoutUs); } - // - // Apply any pre-configured XDP map configs to the raw datapath. - // - if (MsQuicLib.XdpMapConfigCount > 0 && MsQuicLib.XdpMapConfigs != NULL) { - CxPlatDataPathSetXdpMapConfigs( - MsQuicLib.Datapath, - MsQuicLib.XdpMapConfigs, - MsQuicLib.XdpMapConfigCount); - } } else { MsQuicLibraryFreePartitions(); #ifndef _KERNEL_MODE diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 1729fde6f8..12cc273bf7 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -477,13 +477,17 @@ typedef struct CXPLAT_DATAPATH_INIT_CONFIG { // BOOLEAN EnableDscpOnRecv; +#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES // - // When TRUE, the datapath operates in XDP map mode: the WinSock (normal) - // datapath is skipped and the raw (XDP) datapath is required to succeed. - // An external process owns the XDP program; this process only creates XSK - // sockets and inserts them into a shared XSKMAP. + // External XDP map configurations. When non-NULL (and Count > 0), the + // datapath operates in XDP map mode: the WinSock (normal) datapath is + // skipped, the raw (XDP) datapath is required to succeed, and XSK sockets + // are inserted into the provided XSKMAPs at init time. + // The buffer must remain valid for the lifetime of the datapath. // - BOOLEAN XdpMapMode; + const struct QUIC_XDP_MAP_CONFIG* XdpMapConfigs; + uint32_t XdpMapConfigCount; +#endif } CXPLAT_DATAPATH_INIT_CONFIG; // @@ -519,23 +523,6 @@ CxPlatDataPathUpdatePollingIdleTimeout( _In_ uint32_t PollingIdleTimeoutUs ); -// -// Sets external XDP map configurations on the datapath. Must be called after -// the datapath is initialized but before sockets start plumbing rules. -// The Configs buffer must remain valid (i.e., point to QUIC_XDP_MAP_CONFIG[]) -// for the lifetime of the datapath. -// -#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES -struct QUIC_XDP_MAP_CONFIG; // Forward declaration -_IRQL_requires_max_(PASSIVE_LEVEL) -void -CxPlatDataPathSetXdpMapConfigs( - _In_ CXPLAT_DATAPATH* Datapath, - _In_reads_(Count) const struct QUIC_XDP_MAP_CONFIG* Configs, - _In_ uint32_t Count - ); -#endif - // // Queries the currently supported features of the datapath for the given type // of socket. diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index 33d6d8db0d..e7b67c1eb2 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -86,16 +86,17 @@ RawDataPathUpdatePollingIdleTimeout( } _IRQL_requires_max_(PASSIVE_LEVEL) -void -CxPlatDpRawSetXdpMapConfigs( +QUIC_STATUS +CxPlatDpRawInsertXskByMapConfigs( _In_ CXPLAT_DATAPATH_RAW* RawDataPath, - _In_reads_(Count) const QUIC_XDP_MAP_CONFIG* Configs, - _In_ uint32_t Count + _In_reads_(MapConfigCount) const QUIC_XDP_MAP_CONFIG* MapConfigs, + _In_ uint32_t MapConfigCount ) { UNREFERENCED_PARAMETER(RawDataPath); - UNREFERENCED_PARAMETER(Configs); - UNREFERENCED_PARAMETER(Count); + UNREFERENCED_PARAMETER(MapConfigs); + UNREFERENCED_PARAMETER(MapConfigCount); + return QUIC_STATUS_NOT_SUPPORTED; } _IRQL_requires_max_(DISPATCH_LEVEL) diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 3059d8c583..c537c42b28 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -2266,34 +2266,35 @@ CxPlatDataPathRssConfigFree( CXPLAT_FREE(RssConfig, QUIC_POOL_DATAPATH_RSS_CONFIG); } +// +// Inserts each interface's RX XSK sockets into the matching XSKMAP from the +// provided map configs. Uses an O(Interfaces * MapConfigCount) search to match +// each config to its interface by IfIndex. +// _IRQL_requires_max_(PASSIVE_LEVEL) -void -CxPlatDpRawSetXdpMapConfigs( +QUIC_STATUS +CxPlatDpRawInsertXskByMapConfigs( _In_ CXPLAT_DATAPATH_RAW* RawDataPath, - _In_reads_(Count) const QUIC_XDP_MAP_CONFIG* Configs, - _In_ uint32_t Count + _In_reads_(MapConfigCount) const QUIC_XDP_MAP_CONFIG* MapConfigs, + _In_ uint32_t MapConfigCount ) { XDP_DATAPATH* Xdp = (XDP_DATAPATH*)RawDataPath; + QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - // - // Walk all interfaces. For each matching map config, insert every - // queue's RX XSK into the shared XSKMAP immediately. - // CXPLAT_LIST_ENTRY* Entry; for (Entry = Xdp->Interfaces.Flink; Entry != &Xdp->Interfaces; Entry = Entry->Flink) { XDP_INTERFACE* Interface = CONTAINING_RECORD(Entry, XDP_INTERFACE, Link); - for (uint32_t i = 0; i < Count; i++) { - if (Configs[i].InterfaceIndex != Interface->ActualIfIndex && - Configs[i].InterfaceIndex != Interface->IfIndex) { + for (uint32_t i = 0; i < MapConfigCount; i++) { + if (MapConfigs[i].InterfaceIndex != Interface->IfIndex) { continue; } - HANDLE XskMap = (HANDLE)Configs[i].MapHandle; + HANDLE XskMap = (HANDLE)MapConfigs[i].MapHandle; QuicTraceLogVerbose( XdpMapModeConfigured, "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", Interface, - Interface->ActualIfIndex, + Interface->IfIndex, XskMap); for (uint32_t j = 0; j < Interface->QueueCount; j++) { CXPLAT_QUEUE* Queue = &Interface->Queues[j]; @@ -2307,16 +2308,21 @@ CxPlatDpRawSetXdpMapConfigs( "[ lib] ERROR, %u, %s.", Hr, "XdpMapInsert"); - } else { - QuicTraceLogVerbose( - XdpMapModeInserted, - "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", - Interface, - j, - Interface->ActualIfIndex); + Status = (QUIC_STATUS)Hr; + goto Exit; } + QuicTraceLogVerbose( + XdpMapModeInserted, + "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", + Interface, + j, + Interface->IfIndex); } break; } } + +Exit: + + return Status; } diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index d68f6db5fc..5e1f61fa73 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -32,7 +32,7 @@ CxPlatDataPathInitialize( goto Error; } - if (InitConfig->XdpMapMode) { + if (InitConfig->XdpMapConfigCount > 0 && InitConfig->XdpMapConfigs != NULL) { // // XDP map mode: bypass the platform datapath (WinSock/epoll) entirely. // Allocate the full CXPLAT_DATAPATH struct (zero-initialized) so that @@ -74,6 +74,22 @@ CxPlatDataPathInitialize( Status = QUIC_STATUS_NOT_SUPPORTED; goto Error; } + + Status = + CxPlatDpRawInsertXskByMapConfigs( + Datapath->RawDataPath, + InitConfig->XdpMapConfigs, + InitConfig->XdpMapConfigCount); + if (QUIC_FAILED(Status)) { + QuicTraceLogVerbose( + DatapathRawMapInsertFail, + "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d", + Status); + RawDataPathUninitialize(Datapath->RawDataPath); + CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); + *NewDataPath = NULL; + goto Error; + } } else { Status = DataPathInitialize( @@ -139,19 +155,6 @@ CxPlatDataPathUpdatePollingIdleTimeout( } } -_IRQL_requires_max_(PASSIVE_LEVEL) -void -CxPlatDataPathSetXdpMapConfigs( - _In_ CXPLAT_DATAPATH* Datapath, - _In_reads_(Count) const QUIC_XDP_MAP_CONFIG* Configs, - _In_ uint32_t Count - ) -{ - if (Datapath->RawDataPath) { - CxPlatDpRawSetXdpMapConfigs(Datapath->RawDataPath, Configs, Count); - } -} - _IRQL_requires_max_(DISPATCH_LEVEL) CXPLAT_DATAPATH_FEATURES CxPlatDataPathGetSupportedFeatures( diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 5a725602f7..62ac7f6b8a 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -1226,12 +1226,17 @@ RawDataPathUpdatePollingIdleTimeout( ); #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES +// +// Inserts each interface's RX XSK sockets into the matching XSKMAP from the +// provided map configs. Uses an O(Interfaces * MapConfigCount) search to match +// each config to its interface by IfIndex. +// _IRQL_requires_max_(PASSIVE_LEVEL) -void -CxPlatDpRawSetXdpMapConfigs( +QUIC_STATUS +CxPlatDpRawInsertXskByMapConfigs( _In_ CXPLAT_DATAPATH_RAW* RawDataPath, - _In_reads_(Count) const QUIC_XDP_MAP_CONFIG* Configs, - _In_ uint32_t Count + _In_reads_(MapConfigCount) const QUIC_XDP_MAP_CONFIG* MapConfigs, + _In_ uint32_t MapConfigCount ); #endif From c62d7b17d910ca9472be5d93da036972972a2c18 Mon Sep 17 00:00:00 2001 From: Jack He Date: Sun, 17 May 2026 17:53:15 -0700 Subject: [PATCH 09/41] fix comment --- src/inc/quic_datapath.h | 2 -- src/platform/datapath_xplat.c | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 12cc273bf7..c184b6a062 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -477,7 +477,6 @@ typedef struct CXPLAT_DATAPATH_INIT_CONFIG { // BOOLEAN EnableDscpOnRecv; -#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES // // External XDP map configurations. When non-NULL (and Count > 0), the // datapath operates in XDP map mode: the WinSock (normal) datapath is @@ -487,7 +486,6 @@ typedef struct CXPLAT_DATAPATH_INIT_CONFIG { // const struct QUIC_XDP_MAP_CONFIG* XdpMapConfigs; uint32_t XdpMapConfigCount; -#endif } CXPLAT_DATAPATH_INIT_CONFIG; // diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index 5e1f61fa73..3d3f0f7167 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -132,8 +132,8 @@ CxPlatDataPathUninitialize( } if (Datapath->XdpMapMode) { // - // Map mode: this is just a CXPLAT_DATAPATH_COMMON, not a full - // platform datapath. Free directly without platform uninit. + // Map mode: no platform (WinSock) datapath was initialized, + // so free directly without platform uninit. // CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); } else { From 08ff04e5cbc497e6f1734e940e0257964534885c Mon Sep 17 00:00:00 2001 From: Jack He Date: Sun, 17 May 2026 17:54:46 -0700 Subject: [PATCH 10/41] update xdp commit --- submodules/xdp-for-windows | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/xdp-for-windows b/submodules/xdp-for-windows index 793dc2a0d7..ccd59c2e6e 160000 --- a/submodules/xdp-for-windows +++ b/submodules/xdp-for-windows @@ -1 +1 @@ -Subproject commit 793dc2a0d7e6f8a86dae06c84de7d8fd6eacd205 +Subproject commit ccd59c2e6ed98e54108b8e3140d17ab3656036ce From b8285dccc0470db13e0f62fe95a4f55082e5f10f Mon Sep 17 00:00:00 2001 From: Jack He Date: Sun, 17 May 2026 18:04:19 -0700 Subject: [PATCH 11/41] get rid of tools --- src/tools/CMakeLists.txt | 3 - src/tools/quicxskmapcreator/CMakeLists.txt | 9 - .../quicxskmapcreator/quicxskmapcreator.c | 325 ------------------ src/tools/sample/sample.c | 210 +---------- src/tools/xdpmapconsumer/CMakeLists.txt | 9 - src/tools/xdpmapconsumer/xdpmapconsumer.c | 323 ----------------- src/tools/xdpmapcreator/CMakeLists.txt | 9 - src/tools/xdpmapcreator/xdpmapcreator.c | 227 ------------ 8 files changed, 3 insertions(+), 1112 deletions(-) delete mode 100644 src/tools/quicxskmapcreator/CMakeLists.txt delete mode 100644 src/tools/quicxskmapcreator/quicxskmapcreator.c delete mode 100644 src/tools/xdpmapconsumer/CMakeLists.txt delete mode 100644 src/tools/xdpmapconsumer/xdpmapconsumer.c delete mode 100644 src/tools/xdpmapcreator/CMakeLists.txt delete mode 100644 src/tools/xdpmapcreator/xdpmapcreator.c diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 314c3eda45..8535e99ff6 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -38,7 +38,4 @@ if(WIN32 AND (NOT QUIC_UWP_BUILD AND NOT QUIC_GAMECORE_BUILD)) add_subdirectory(execution) add_subdirectory(etw) add_subdirectory(recvfuzz) - add_subdirectory(xdpmapcreator) - add_subdirectory(xdpmapconsumer) - add_subdirectory(quicxskmapcreator) endif() diff --git a/src/tools/quicxskmapcreator/CMakeLists.txt b/src/tools/quicxskmapcreator/CMakeLists.txt deleted file mode 100644 index 5e6836e39a..0000000000 --- a/src/tools/quicxskmapcreator/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -add_quic_tool(quicxskmapcreator quicxskmapcreator.c) -quic_tool_warnings(quicxskmapcreator) - -target_compile_definitions(quicxskmapcreator PRIVATE XDP_API_VERSION=3) -target_include_directories(quicxskmapcreator SYSTEM PRIVATE - ${PROJECT_SOURCE_DIR}/submodules/xdp-for-windows/published/external) diff --git a/src/tools/quicxskmapcreator/quicxskmapcreator.c b/src/tools/quicxskmapcreator/quicxskmapcreator.c deleted file mode 100644 index c89857bca6..0000000000 --- a/src/tools/quicxskmapcreator/quicxskmapcreator.c +++ /dev/null @@ -1,325 +0,0 @@ -/*++ - - Copyright (c) Microsoft Corporation. - Licensed under the MIT License. - -Abstract: - - Creates an XSKMAP, duplicates the handle into a QUIC server process, and - attaches an XDP program with QUIC-aware match rules once the consumer - signals it has inserted its XSKs. - - This tool is the "creator" half of the XSKMAP creator/consumer pattern - for MsQuic. The consumer is typically quicsample running with - -xdp -xdp_map_ifindex (Phase 2) or the MsQuic datapath itself (Phase 3). - - Usage: - 1. Start the QUIC server: - quicsample -server -cert_hash: -xdp -xdp_map_ifindex: - 2. Run this tool: - quicxskmapcreator -TargetPid -IfIndex -UdpPort - 3. Paste the printed handle value into the consumer's stdin - 4. Press Enter here to attach the XDP program - - The creator owns the map and the XDP program lifetime. - ---*/ - -#define _CRT_SECURE_NO_WARNINGS 1 -#define XDP_API_VERSION 3 -#define XDP_INCLUDE_WINCOMMON - -#pragma warning(disable:5105) -#include -#include -#include -#include -#include - -#include - -#define LOGERR(...) \ - fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") - -static UINT32 TargetPid; -static UINT32 IfIndex; -static UINT16 UdpPort; -static BOOLEAN HasCibirId; -static UINT8 CibirIdOffset; -static UINT8 CibirIdData[6]; -static UINT8 CibirIdLength; - -static UINT8 -DecodeHexChar( - char c - ) -{ - if (c >= '0' && c <= '9') return (UINT8)(c - '0'); - if (c >= 'A' && c <= 'F') return (UINT8)(10 + c - 'A'); - if (c >= 'a' && c <= 'f') return (UINT8)(10 + c - 'a'); - return 0; -} - -static UINT32 -DecodeHexBuffer( - const char* Hex, - UINT32 OutLen, - UINT8* Out - ) -{ - UINT32 Len = (UINT32)strlen(Hex) / 2; - if (Len > OutLen) { - Len = OutLen; - } - for (UINT32 i = 0; i < Len; i++) { - Out[i] = (DecodeHexChar(Hex[i * 2]) << 4) | DecodeHexChar(Hex[i * 2 + 1]); - } - return Len; -} - -static void -PrintUsage(void) -{ - printf( - "quicxskmapcreator.exe -TargetPid -IfIndex -UdpPort [OPTIONS]\n" - "\n" - "Creates an XSKMAP, duplicates it into the target QUIC server process, and\n" - "attaches an XDP program with QUIC-aware match rules.\n" - "\n" - "OPTIONS:\n" - " -TargetPid PID of the consumer process (required)\n" - " -IfIndex Network interface index (required)\n" - " -UdpPort QUIC server UDP port (required)\n" - " -CibirId CIBIR ID (hex: first byte is offset, rest is CID prefix)\n" - " Example: -CibirId 00AABBCCDD\n" - ); -} - -static BOOLEAN -ParseArgs( - int ArgC, - char** ArgV - ) -{ - int i = 1; - TargetPid = 0; - IfIndex = MAXUINT32; - UdpPort = 0; - HasCibirId = FALSE; - - while (i < ArgC) { - if (!_stricmp(ArgV[i], "-TargetPid") || !_stricmp(ArgV[i], "-pid")) { - if (++i >= ArgC) { - LOGERR("Missing TargetPid value"); - return FALSE; - } - TargetPid = (UINT32)atoi(ArgV[i]); - } else if (!_stricmp(ArgV[i], "-IfIndex")) { - if (++i >= ArgC) { - LOGERR("Missing IfIndex value"); - return FALSE; - } - IfIndex = (UINT32)atoi(ArgV[i]); - } else if (!_stricmp(ArgV[i], "-UdpPort")) { - if (++i >= ArgC) { - LOGERR("Missing UdpPort value"); - return FALSE; - } - UdpPort = (UINT16)atoi(ArgV[i]); - } else if (!_stricmp(ArgV[i], "-CibirId")) { - if (++i >= ArgC) { - LOGERR("Missing CibirId value"); - return FALSE; - } - UINT8 CibirRaw[7]; // offset (1 byte) + max 6 bytes CID - UINT32 CibirRawLen = DecodeHexBuffer(ArgV[i], sizeof(CibirRaw), CibirRaw); - if (CibirRawLen < 2) { - LOGERR("CIBIR ID too short (need at least offset + 1 byte CID)"); - return FALSE; - } - CibirIdOffset = CibirRaw[0]; - CibirIdLength = (UINT8)(CibirRawLen - 1); - memcpy(CibirIdData, &CibirRaw[1], CibirIdLength); - HasCibirId = TRUE; - } else { - LOGERR("Unexpected parameter \"%s\"", ArgV[i]); - return FALSE; - } - ++i; - } - - if (TargetPid == 0) { - LOGERR("-TargetPid is required"); - return FALSE; - } - if (IfIndex == MAXUINT32) { - LOGERR("-IfIndex is required"); - return FALSE; - } - if (UdpPort == 0) { - LOGERR("-UdpPort is required"); - return FALSE; - } - - return TRUE; -} - -int -__cdecl -main( - int argc, - char** argv - ) -{ - XDP_STATUS XdpStatus; - HANDLE XskMap = NULL; - HANDLE TargetProcess = NULL; - HANDLE RemoteHandle = NULL; - HANDLE Program = NULL; - - if (!ParseArgs(argc, argv)) { - PrintUsage(); - return 1; - } - - // - // Stage 1: Create the XSKMAP. - // - XdpStatus = XdpMapCreate(&XskMap, XDP_MAP_TYPE_XSKMAP); - if (FAILED(XdpStatus)) { - LOGERR("XdpMapCreate failed: 0x%x", XdpStatus); - return 1; - } - - printf("[Stage 1] Created XSKMAP (local handle: 0x%IX)\n", (UINT_PTR)XskMap); - - // - // Stage 2: Duplicate the map handle into the consumer process. - // - TargetProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, TargetPid); - if (TargetProcess == NULL) { - LOGERR("OpenProcess(%u) failed: %u. Ensure the consumer is running.", - TargetPid, GetLastError()); - CloseHandle(XskMap); - return 1; - } - - if (!DuplicateHandle( - GetCurrentProcess(), - XskMap, - TargetProcess, - &RemoteHandle, - 0, - FALSE, - DUPLICATE_SAME_ACCESS)) { - LOGERR("DuplicateHandle failed: %u", GetLastError()); - CloseHandle(TargetProcess); - CloseHandle(XskMap); - return 1; - } - - CloseHandle(TargetProcess); - - printf("[Stage 2] Duplicated XSKMAP into PID %u. Remote handle: 0x%IX\n", - TargetPid, (UINT_PTR)RemoteHandle); - printf("\n"); - printf(" Paste 0x%IX into the consumer, let it insert its XSKs.\n", - (UINT_PTR)RemoteHandle); - printf("\n"); - - // - // Stage 3: Wait for user signal that the consumer is ready. - // - printf("[Stage 3] Press ENTER when the consumer has inserted its XSKs...\n"); - fflush(stdout); - (void)getchar(); - - // - // Stage 4: Attach the XDP program with QUIC-aware match rules. - // - { - const XDP_HOOK_ID XdpInspectRxL2 = { - XDP_HOOK_L2, - XDP_HOOK_RX, - XDP_HOOK_INSPECT, - }; - - XDP_RULE Rules[2]; - UINT32 RuleCount = 0; - ZeroMemory(Rules, sizeof(Rules)); - - if (HasCibirId) { - // - // CIBIR mode: match on QUIC connection IDs. - // - Rules[RuleCount].Match = XDP_MATCH_QUIC_FLOW_SRC_CID; - Rules[RuleCount].Pattern.QuicFlow.UdpPort = htons(UdpPort); - Rules[RuleCount].Pattern.QuicFlow.CidLength = CibirIdLength; - Rules[RuleCount].Pattern.QuicFlow.CidOffset = CibirIdOffset; - memcpy(Rules[RuleCount].Pattern.QuicFlow.CidData, CibirIdData, CibirIdLength); - Rules[RuleCount].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RuleCount].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RuleCount].Redirect.Target = XskMap; - RuleCount++; - - Rules[RuleCount].Match = XDP_MATCH_QUIC_FLOW_DST_CID; - Rules[RuleCount].Pattern.QuicFlow.UdpPort = htons(UdpPort); - Rules[RuleCount].Pattern.QuicFlow.CidLength = CibirIdLength; - Rules[RuleCount].Pattern.QuicFlow.CidOffset = CibirIdOffset; - memcpy(Rules[RuleCount].Pattern.QuicFlow.CidData, CibirIdData, CibirIdLength); - Rules[RuleCount].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RuleCount].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RuleCount].Redirect.Target = XskMap; - RuleCount++; - } else { - // - // Simple mode: match on UDP destination port. - // - Rules[RuleCount].Match = XDP_MATCH_UDP_DST; - Rules[RuleCount].Pattern.Port = htons(UdpPort); - Rules[RuleCount].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RuleCount].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RuleCount].Redirect.Target = XskMap; - RuleCount++; - } - - XdpStatus = - XdpCreateProgram( - IfIndex, - &XdpInspectRxL2, - 0, - XDP_CREATE_PROGRAM_FLAG_ALL_QUEUES, - Rules, - RuleCount, - &Program); - if (FAILED(XdpStatus)) { - LOGERR("XdpCreateProgram failed: 0x%x", XdpStatus); - CloseHandle(XskMap); - return 1; - } - } - - printf("[Stage 4] XDP program attached on IfIndex %u\n", IfIndex); - if (HasCibirId) { - printf(" Rules: XDP_MATCH_QUIC_FLOW_SRC_CID + XDP_MATCH_QUIC_FLOW_DST_CID\n"); - printf(" UdpPort: %u, CidOffset: %u, CidLength: %u\n", - UdpPort, CibirIdOffset, CibirIdLength); - } else { - printf(" Rule: XDP_MATCH_UDP_DST, Port: %u\n", UdpPort); - } - printf(" Target: XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID\n"); - printf("\n"); - printf("Press ENTER to detach the program and exit.\n"); - fflush(stdout); - (void)getchar(); - - // - // Cleanup: closing the program handle detaches XDP. Closing the map - // handle releases our reference (consumer may still hold one). - // - printf("Detaching XDP program and cleaning up.\n"); - CloseHandle(Program); - CloseHandle(XskMap); - - return 0; -} diff --git a/src/tools/sample/sample.c b/src/tools/sample/sample.c index f20ecfb87e..d13993fe7e 100644 --- a/src/tools/sample/sample.c +++ b/src/tools/sample/sample.c @@ -125,20 +125,12 @@ void PrintUsage() "\n" "Usage:\n" "\n" - " quicsample.exe -client -unsecure -target:{IPAddress|Hostname} [-ticket:] [-cibir_id:]\n" + " quicsample.exe -client -unsecure -target:{IPAddress|Hostname} [-ticket:]\n" #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES " quicsample.exe -multiclient -count: -unsecure -target:{IPAddress|Hostname}\n" #endif - " quicsample.exe -server -cert_hash:<...> [-xdp] [-xdp_map_ifindex:] [-cibir_id:]\n" - " quicsample.exe -server -cert_file:<...> -key_file:<...> [-password:<...>] [-xdp] [-xdp_map_ifindex:] [-cibir_id:]\n" - "\n" - " -xdp Enable XDP datapath (server only, requires XDP driver).\n" - " When enabled, prints the exact XDP rules being plumbed.\n" - " -xdp_map_ifindex:\n" - " Enable XDP map mode for the given interface index. The server\n" - " waits for an XSKMAP handle from quicxskmapcreator. Requires -xdp.\n" - " -cibir_id: Set CIBIR ID (hex: first byte is offset, rest is CID prefix)\n" - " Example: -cibir_id:00AABBCCDD (offset=0, CID=AABBCCDD)\n" + " quicsample.exe -server -cert_hash:<...>\n" + " quicsample.exe -server -cert_file:<...> -key_file:<...> [-password:<...>]\n" ); } @@ -563,23 +555,6 @@ ServerLoadConfiguration( Settings.PeerBidiStreamCount = 1; Settings.IsSet.PeerBidiStreamCount = TRUE; - // - // Optionally enable XDP datapath for high-performance packet processing. - // XDP must be set at the global library level (MsQuicLib.Settings) because - // the listener socket creation checks the global setting, not per-config. - // - if (GetFlag(argc, argv, "xdp")) { - QUIC_SETTINGS XdpSettings = {0}; - XdpSettings.XdpEnabled = TRUE; - XdpSettings.IsSet.XdpEnabled = TRUE; - QUIC_STATUS XdpStatus = MsQuic->SetParam(NULL, QUIC_PARAM_GLOBAL_SETTINGS, sizeof(XdpSettings), &XdpSettings); - if (QUIC_FAILED(XdpStatus)) { - printf("Failed to enable XDP globally, 0x%x!\n", XdpStatus); - return FALSE; - } - printf("XDP datapath enabled (global).\n"); - } - QUIC_CREDENTIAL_CONFIG_HELPER Config; memset(&Config, 0, sizeof(Config)); Config.CredConfig.Flags = QUIC_CREDENTIAL_FLAG_NONE; @@ -682,26 +657,6 @@ RunServer( goto Error; } - // - // Optionally set the CIBIR ID on the listener for CID-based XDP steering. - // - { - const char* CibirIdHex = GetValue(argc, argv, "cibir_id"); - if (CibirIdHex != NULL) { - uint8_t CibirId[7]; // offset (1 byte) + max 6 bytes CID - uint32_t CibirIdLen = DecodeHexBuffer(CibirIdHex, sizeof(CibirId), CibirId); - if (CibirIdLen < 2) { - printf("CIBIR ID too short (need at least offset + 1 byte CID)!\n"); - goto Error; - } - if (QUIC_FAILED(Status = MsQuic->SetParam(Listener, QUIC_PARAM_LISTENER_CIBIR_ID, CibirIdLen, CibirId))) { - printf("SetParam(QUIC_PARAM_LISTENER_CIBIR_ID) failed, 0x%x!\n", Status); - goto Error; - } - printf("CIBIR ID set on listener (offset=%u, %u bytes CID).\n", CibirId[0], CibirIdLen - 1); - } - } - // // Starts listening for incoming connections. // @@ -710,80 +665,6 @@ RunServer( goto Error; } - // - // Print XDP status info after the listener starts. - // -#ifdef _WIN32 - if (GetFlag(argc, argv, "xdp")) { - const char* CibirIdHex = GetValue(argc, argv, "cibir_id"); - BOOLEAN MapMode = (GetValue(argc, argv, "xdp_map_ifindex") != NULL); - - printf("\n"); - - if (MapMode) { - // - // Map mode: rules are NOT plumbed by us. The external - // quicxskmapcreator process owns the XDP program. - // - printf("=== XDP Map Mode (consumer) ===\n"); - printf(" XDP rules were NOT plumbed by this process.\n"); - printf(" Waiting for quicxskmapcreator to attach the XDP program.\n"); - printf("===============================\n"); - } else { - // - // Self-managed mode: print the rules that were plumbed. - // - printf("=== XDP Rules Plumbed ===\n"); - printf(" Port: %u\n", UdpPort); - printf("\n"); - - if (CibirIdHex != NULL) { - uint8_t CibirId[7]; - uint32_t CibirIdLen = DecodeHexBuffer(CibirIdHex, sizeof(CibirId), CibirId); - uint8_t CidOffset = CibirId[0]; - uint8_t CidLength = (uint8_t)(CibirIdLen - 1); - - printf(" Rule 1: XDP_MATCH_QUIC_FLOW_SRC_CID\n"); - printf(" UdpPort = %u\n", UdpPort); - printf(" CidOffset = %u\n", CidOffset); - printf(" CidLength = %u\n", CidLength); - printf(" CidData = "); - for (uint32_t k = 1; k < CibirIdLen; k++) { - printf("%02X", CibirId[k]); - } - printf("\n"); - printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); - printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); - printf("\n"); - - printf(" Rule 2: XDP_MATCH_QUIC_FLOW_DST_CID\n"); - printf(" UdpPort = %u\n", UdpPort); - printf(" CidOffset = %u\n", CidOffset); - printf(" CidLength = %u\n", CidLength); - printf(" CidData = "); - for (uint32_t k = 1; k < CibirIdLen; k++) { - printf("%02X", CibirId[k]); - } - printf("\n"); - printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); - printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); - } else { - printf(" Rule 1: XDP_MATCH_UDP_DST\n"); - printf(" Port = %u\n", UdpPort); - printf(" Action = XDP_PROGRAM_ACTION_REDIRECT\n"); - printf(" Target = XDP_REDIRECT_TARGET_TYPE_XSK\n"); - } - - printf("\n"); - printf(" Hook: XDP_HOOK_L2 / XDP_HOOK_RX / XDP_HOOK_INSPECT\n"); - printf(" Flags: per-queue (one XdpCreateProgram per RSS queue)\n"); - printf("=========================\n"); - } - - printf("\n"); - } -#endif - // // Continue listening for connections until the Enter key is pressed. // @@ -1078,35 +959,6 @@ RunClient( goto Error; } - // - // Optionally set the CIBIR ID on the connection for CID-based steering. - // - { - const char* CibirIdHex = GetValue(argc, argv, "cibir_id"); - if (CibirIdHex != NULL) { - // - // CIBIR requires shared binding (so the connection uses source CIDs). - // - BOOLEAN ShareBinding = TRUE; - if (QUIC_FAILED(Status = MsQuic->SetParam(Connection, QUIC_PARAM_CONN_SHARE_UDP_BINDING, sizeof(ShareBinding), &ShareBinding))) { - printf("SetParam(QUIC_PARAM_CONN_SHARE_UDP_BINDING) failed, 0x%x!\n", Status); - goto Error; - } - - uint8_t CibirId[7]; // offset (1 byte) + max 6 bytes CID - uint32_t CibirIdLen = DecodeHexBuffer(CibirIdHex, sizeof(CibirId), CibirId); - if (CibirIdLen < 2) { - printf("CIBIR ID too short (need at least offset + 1 byte CID)!\n"); - goto Error; - } - if (QUIC_FAILED(Status = MsQuic->SetParam(Connection, QUIC_PARAM_CONN_CIBIR_ID, CibirIdLen, CibirId))) { - printf("SetParam(QUIC_PARAM_CONN_CIBIR_ID) failed, 0x%x!\n", Status); - goto Error; - } - printf("CIBIR ID set on connection (offset=%u, %u bytes CID).\n", CibirId[0], CibirIdLen - 1); - } - } - if ((ResumptionTicketString = GetValue(argc, argv, "ticket")) != NULL) { // // If provided at the command line, set the resumption ticket that can @@ -1259,62 +1111,6 @@ main( goto Error; } - // - // If the server will use XDP map mode, configure the map before - // RegistrationOpen (which triggers lazy init and creates the datapath). - // QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG must be set before LazyInitComplete. - // -#ifdef _WIN32 - if (GetFlag(argc, argv, "server") && GetFlag(argc, argv, "xdp")) { - const char* MapIfIndexStr = GetValue(argc, argv, "xdp_map_ifindex"); - if (MapIfIndexStr != NULL) { - UINT32 MapIfIndex = (UINT32)atoi(MapIfIndexStr); - char InputBuf[64]; - UINT_PTR HandleValue; - - printf("=== XDP Map Mode ===\n"); - printf(" PID: %u\n", (unsigned)GetCurrentProcessId()); - printf(" IfIndex: %u\n", MapIfIndex); - printf("\n"); - printf("Start quicxskmapcreator in another terminal:\n"); - printf(" quicxskmapcreator -TargetPid %u -IfIndex %u -UdpPort %u\n", - (unsigned)GetCurrentProcessId(), MapIfIndex, UdpPort); - printf("\n"); - printf("Paste the XSKMAP handle value here (hex): "); - fflush(stdout); - - if (fgets(InputBuf, sizeof(InputBuf), stdin) == NULL) { - printf("Failed to read input!\n"); - goto Error; - } - - HandleValue = (UINT_PTR)_strtoui64(InputBuf, NULL, 16); - if (HandleValue == 0 || HandleValue == (UINT_PTR)INVALID_HANDLE_VALUE) { - printf("Invalid handle value: %s\n", InputBuf); - goto Error; - } - - QUIC_XDP_MAP_CONFIG MapConfig; - MapConfig.InterfaceIndex = MapIfIndex; - MapConfig.MapHandle = (QUIC_XDP_MAP_HANDLE)HandleValue; - - Status = MsQuic->SetParam( - NULL, - QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG, - sizeof(MapConfig), - &MapConfig); - if (QUIC_FAILED(Status)) { - printf("SetParam(QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG) failed, 0x%x!\n", Status); - goto Error; - } - - printf("XDP map config set (IfIndex=%u, MapHandle=0x%IX).\n", - MapIfIndex, HandleValue); - printf("====================\n\n"); - } - } -#endif - // // Create a registration for the app's connections. // diff --git a/src/tools/xdpmapconsumer/CMakeLists.txt b/src/tools/xdpmapconsumer/CMakeLists.txt deleted file mode 100644 index a99aaa569a..0000000000 --- a/src/tools/xdpmapconsumer/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -add_quic_tool(xdpmapconsumer xdpmapconsumer.c) -quic_tool_warnings(xdpmapconsumer) - -target_compile_definitions(xdpmapconsumer PRIVATE XDP_API_VERSION=3) -target_include_directories(xdpmapconsumer SYSTEM PRIVATE - ${PROJECT_SOURCE_DIR}/submodules/xdp-for-windows/published/external) diff --git a/src/tools/xdpmapconsumer/xdpmapconsumer.c b/src/tools/xdpmapconsumer/xdpmapconsumer.c deleted file mode 100644 index 37ba474b65..0000000000 --- a/src/tools/xdpmapconsumer/xdpmapconsumer.c +++ /dev/null @@ -1,323 +0,0 @@ -/*++ - - Copyright (c) Microsoft Corporation. - Licensed under the MIT License. - -Abstract: - - Consumes an XSKMAP handle duplicated from another process. Creates XSK - sockets, inserts them into the map, and receives packets. - - Usage: - 1. Run: xdpmapconsumer.exe -IfIndex -QueueCount - 2. Note the printed PID - 3. In another terminal, run: xdpmapcreator.exe -TargetPid - 4. Paste the remote handle value printed by the creator into this console - 5. The consumer creates per-queue XSKs, inserts them into the map, - and starts receiving packets. - - The creator owns the map and the XDP program; the consumer only inserts - its XSK sockets and drains received frames. - ---*/ - -#define _CRT_SECURE_NO_WARNINGS 1 -#define XDP_API_VERSION 3 -#define XDP_INCLUDE_WINCOMMON - -#pragma warning(disable:5105) -#include -#include -#include - -#include -#include -#include - -#define LOGERR(...) \ - fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") - -#define RX_RING_SIZE 64 -#define FRAME_SIZE 2048 - -typedef struct _QUEUE_CONTEXT { - HANDLE Socket; - XSK_RING RxRing; - XSK_RING RxFillRing; - UCHAR *Umem; - UINT64 PacketsReceived; - UINT64 BytesReceived; -} QUEUE_CONTEXT; - -static UINT32 IfIndex; -static UINT32 QueueCount; - -static void -PrintUsage(void) -{ - printf( - "xdpmapconsumer.exe -IfIndex -QueueCount \n" - "\n" - "Receives an XSKMAP handle from xdpmapcreator, creates per-queue\n" - "XSK sockets, inserts them into the map, and receives packets.\n" - "\n" - "OPTIONS:\n" - " -IfIndex Network interface index to bind XSKs to (required)\n" - " -QueueCount Number of RSS queues to cover (required, 1-128)\n" - ); -} - -static BOOLEAN -ParseArgs( - int ArgC, - char** ArgV - ) -{ - int i = 1; - IfIndex = MAXUINT32; - QueueCount = 0; - - while (i < ArgC) { - if (!_stricmp(ArgV[i], "-IfIndex")) { - if (++i >= ArgC) { - LOGERR("Missing IfIndex value"); - return FALSE; - } - IfIndex = (UINT32)atoi(ArgV[i]); - } else if (!_stricmp(ArgV[i], "-QueueCount")) { - if (++i >= ArgC) { - LOGERR("Missing QueueCount value"); - return FALSE; - } - QueueCount = (UINT32)atoi(ArgV[i]); - if (QueueCount == 0 || QueueCount > 128) { - LOGERR("QueueCount must be between 1 and 128"); - return FALSE; - } - } else { - LOGERR("Unexpected parameter \"%s\"", ArgV[i]); - return FALSE; - } - ++i; - } - - if (IfIndex == MAXUINT32) { - LOGERR("-IfIndex is required"); - return FALSE; - } - if (QueueCount == 0) { - LOGERR("-QueueCount is required"); - return FALSE; - } - return TRUE; -} - -static XDP_STATUS -SetupQueueSocket( - _In_ UINT32 QueueId, - _Inout_ QUEUE_CONTEXT *Ctx - ) -{ - XDP_STATUS XdpStatus; - XSK_UMEM_REG UmemReg = {0}; - UINT32 RingSize = RX_RING_SIZE; - XSK_RING_INFO_SET RingInfo; - UINT32 OptionLength; - UINT32 FillIndex; - - Ctx->PacketsReceived = 0; - Ctx->BytesReceived = 0; - - Ctx->Umem = (UCHAR *)calloc(RX_RING_SIZE, FRAME_SIZE); - if (Ctx->Umem == NULL) { - LOGERR("Failed to allocate UMEM for queue %u", QueueId); - return E_OUTOFMEMORY; - } - - XdpStatus = XskCreate(&Ctx->Socket); - if (FAILED(XdpStatus)) { - LOGERR("XskCreate failed for queue %u: %x", QueueId, XdpStatus); - return XdpStatus; - } - - UmemReg.TotalSize = (UINT32)RX_RING_SIZE * FRAME_SIZE; - UmemReg.ChunkSize = FRAME_SIZE; - UmemReg.Address = Ctx->Umem; - - XdpStatus = XskSetSockopt(Ctx->Socket, XSK_SOCKOPT_UMEM_REG, &UmemReg, sizeof(UmemReg)); - if (FAILED(XdpStatus)) { - LOGERR("XSK_SOCKOPT_UMEM_REG failed for queue %u: %x", QueueId, XdpStatus); - return XdpStatus; - } - - XdpStatus = XskBind(Ctx->Socket, IfIndex, QueueId, XSK_BIND_FLAG_RX); - if (FAILED(XdpStatus)) { - LOGERR("XskBind failed for queue %u: %x", QueueId, XdpStatus); - return XdpStatus; - } - - XdpStatus = XskSetSockopt(Ctx->Socket, XSK_SOCKOPT_RX_RING_SIZE, &RingSize, sizeof(RingSize)); - if (FAILED(XdpStatus)) { - LOGERR("XSK_SOCKOPT_RX_RING_SIZE failed for queue %u: %x", QueueId, XdpStatus); - return XdpStatus; - } - - XdpStatus = XskSetSockopt(Ctx->Socket, XSK_SOCKOPT_RX_FILL_RING_SIZE, &RingSize, sizeof(RingSize)); - if (FAILED(XdpStatus)) { - LOGERR("XSK_SOCKOPT_RX_FILL_RING_SIZE failed for queue %u: %x", QueueId, XdpStatus); - return XdpStatus; - } - - XdpStatus = XskActivate(Ctx->Socket, XSK_ACTIVATE_FLAG_NONE); - if (FAILED(XdpStatus)) { - LOGERR("XskActivate failed for queue %u: %x", QueueId, XdpStatus); - return XdpStatus; - } - - OptionLength = sizeof(RingInfo); - XdpStatus = XskGetSockopt(Ctx->Socket, XSK_SOCKOPT_RING_INFO, &RingInfo, &OptionLength); - if (FAILED(XdpStatus)) { - LOGERR("XSK_SOCKOPT_RING_INFO failed for queue %u: %x", QueueId, XdpStatus); - return XdpStatus; - } - - XskRingInitialize(&Ctx->RxRing, &RingInfo.Rx); - XskRingInitialize(&Ctx->RxFillRing, &RingInfo.Fill); - - XskRingProducerReserve(&Ctx->RxFillRing, RX_RING_SIZE, &FillIndex); - for (UINT32 j = 0; j < RX_RING_SIZE; j++) { - *(UINT64 *)XskRingGetElement(&Ctx->RxFillRing, FillIndex + j) = - (UINT64)j * FRAME_SIZE; - } - XskRingProducerSubmit(&Ctx->RxFillRing, RX_RING_SIZE); - - return S_OK; -} - -static void -PrintStats( - _In_ QUEUE_CONTEXT *Queues, - _In_ UINT32 Count - ) -{ - UINT64 TotalPackets = 0; - UINT64 TotalBytes = 0; - - printf("\n--- RX Stats ---\n"); - for (UINT32 i = 0; i < Count; i++) { - printf(" Queue %2u: %8llu pkts %12llu bytes\n", - i, Queues[i].PacketsReceived, Queues[i].BytesReceived); - TotalPackets += Queues[i].PacketsReceived; - TotalBytes += Queues[i].BytesReceived; - } - printf(" Total: %8llu pkts %12llu bytes\n", TotalPackets, TotalBytes); - printf("----------------\n"); -} - -int -__cdecl -main( - int argc, - char** argv - ) -{ - UINT_PTR HandleValue; - HANDLE XskMap; - QUEUE_CONTEXT *Queues = NULL; - XDP_STATUS XdpStatus; - char InputBuf[64]; - - if (!ParseArgs(argc, argv)) { - PrintUsage(); - return 1; - } - - printf("=== XSKMAP Consumer ===\n"); - printf("My PID: %u\n", GetCurrentProcessId()); - printf("IfIndex: %u, QueueCount: %u\n", IfIndex, QueueCount); - printf("\n"); - printf("Start xdpmapcreator.exe -TargetPid %u in another terminal,\n", - GetCurrentProcessId()); - printf("then paste the remote handle value here.\n"); - printf("\n"); - printf("Handle value (hex): "); - fflush(stdout); - - if (fgets(InputBuf, sizeof(InputBuf), stdin) == NULL) { - LOGERR("Failed to read input"); - return 1; - } - - HandleValue = (UINT_PTR)_strtoui64(InputBuf, NULL, 16); - if (HandleValue == 0 || HandleValue == (UINT_PTR)INVALID_HANDLE_VALUE) { - LOGERR("Invalid handle value: %s", InputBuf); - return 1; - } - - XskMap = (HANDLE)HandleValue; - printf("Received XSKMAP handle: 0x%IX\n", (UINT_PTR)XskMap); - - // - // Create per-queue XSK sockets and insert them into the map. - // - Queues = (QUEUE_CONTEXT *)calloc(QueueCount, sizeof(QUEUE_CONTEXT)); - if (Queues == NULL) { - LOGERR("Failed to allocate queue context array"); - return 1; - } - - for (UINT32 i = 0; i < QueueCount; i++) { - XdpStatus = SetupQueueSocket(i, &Queues[i]); - if (FAILED(XdpStatus)) { - return 1; - } - - XdpStatus = XdpMapInsert(XskMap, &i, &Queues[i].Socket); - if (FAILED(XdpStatus)) { - LOGERR("XdpMapInsert failed for queue %u: %x", i, XdpStatus); - return 1; - } - - printf("Queue %u: XSK created, bound, activated, inserted into map\n", i); - } - - printf("\nReceiving packets... (Ctrl+C to stop)\n"); - - // - // Poll all queues for received packets, print stats every second. - // - ULONGLONG LastPrintTick = GetTickCount64(); - - for (;;) { - ULONGLONG Now = GetTickCount64(); - if (Now - LastPrintTick >= 1000) { - PrintStats(Queues, QueueCount); - LastPrintTick = Now; - } - - for (UINT32 q = 0; q < QueueCount; q++) { - QUEUE_CONTEXT *Ctx = &Queues[q]; - UINT32 RxIndex; - UINT32 Available = XskRingConsumerReserve(&Ctx->RxRing, RX_RING_SIZE, &RxIndex); - - for (UINT32 j = 0; j < Available; j++) { - XSK_BUFFER_DESCRIPTOR *Desc = - (XSK_BUFFER_DESCRIPTOR *)XskRingGetElement(&Ctx->RxRing, RxIndex + j); - Ctx->PacketsReceived++; - Ctx->BytesReceived += Desc->Length; - } - - if (Available > 0) { - XskRingConsumerRelease(&Ctx->RxRing, Available); - - UINT32 FillIndex; - UINT32 Filled = XskRingProducerReserve(&Ctx->RxFillRing, Available, &FillIndex); - for (UINT32 j = 0; j < Filled; j++) { - *(UINT64 *)XskRingGetElement(&Ctx->RxFillRing, FillIndex + j) = - (UINT64)((RxIndex + j) % RX_RING_SIZE) * FRAME_SIZE; - } - XskRingProducerSubmit(&Ctx->RxFillRing, Filled); - } - } - } -} diff --git a/src/tools/xdpmapcreator/CMakeLists.txt b/src/tools/xdpmapcreator/CMakeLists.txt deleted file mode 100644 index 1b30054265..0000000000 --- a/src/tools/xdpmapcreator/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -add_quic_tool(xdpmapcreator xdpmapcreator.c) -quic_tool_warnings(xdpmapcreator) - -target_compile_definitions(xdpmapcreator PRIVATE XDP_API_VERSION=3) -target_include_directories(xdpmapcreator SYSTEM PRIVATE - ${PROJECT_SOURCE_DIR}/submodules/xdp-for-windows/published/external) diff --git a/src/tools/xdpmapcreator/xdpmapcreator.c b/src/tools/xdpmapcreator/xdpmapcreator.c deleted file mode 100644 index 405cb0668d..0000000000 --- a/src/tools/xdpmapcreator/xdpmapcreator.c +++ /dev/null @@ -1,227 +0,0 @@ -/*++ - - Copyright (c) Microsoft Corporation. - Licensed under the MIT License. - -Abstract: - - Creates an XSKMAP, duplicates the handle into a target process, and - attaches an XDP program once the consumer signals it has inserted its XSKs. - - Usage: - 1. Start xdpmapconsumer.exe -IfIndex -QueueCount (prints PID) - 2. Run: xdpmapcreator.exe -TargetPid -IfIndex [-IcmpOnly] - 3. Paste the printed handle value into the consumer's stdin - 4. Once the consumer has inserted its XSKs, press Enter here to - attach the XDP program and start steering traffic. - - The creator owns the map and the XDP program lifetime. - ---*/ - -#define _CRT_SECURE_NO_WARNINGS 1 -#define XDP_API_VERSION 3 -#define XDP_INCLUDE_WINCOMMON - -#pragma warning(disable:5105) -#include -#include -#include - -#include - -#define LOGERR(...) \ - fprintf(stderr, "ERR: "); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n") - -static UINT32 TargetPid; -static UINT32 IfIndex; -static BOOLEAN UseIcmpMatch; - -static void -PrintUsage(void) -{ - printf( - "xdpmapcreator.exe -TargetPid -IfIndex [OPTIONS]\n" - "\n" - "Creates an XSKMAP, duplicates it into the target process, and attaches\n" - "an XDP program once you signal readiness.\n" - "\n" - "OPTIONS:\n" - " -TargetPid PID of the consumer process (required)\n" - " -IfIndex Network interface index (required)\n" - " -IcmpOnly Match only ICMPv4 traffic (default: match all)\n" - ); -} - -static BOOLEAN -ParseArgs( - int ArgC, - char** ArgV - ) -{ - int i = 1; - TargetPid = 0; - IfIndex = MAXUINT32; - UseIcmpMatch = FALSE; - - while (i < ArgC) { - if (!_stricmp(ArgV[i], "-TargetPid") || !_stricmp(ArgV[i], "-pid")) { - if (++i >= ArgC) { - LOGERR("Missing TargetPid value"); - return FALSE; - } - TargetPid = (UINT32)atoi(ArgV[i]); - } else if (!_stricmp(ArgV[i], "-IfIndex")) { - if (++i >= ArgC) { - LOGERR("Missing IfIndex value"); - return FALSE; - } - IfIndex = (UINT32)atoi(ArgV[i]); - } else if (!_stricmp(ArgV[i], "-IcmpOnly")) { - UseIcmpMatch = TRUE; - } else { - LOGERR("Unexpected parameter \"%s\"", ArgV[i]); - return FALSE; - } - ++i; - } - - if (TargetPid == 0) { - LOGERR("-TargetPid is required"); - return FALSE; - } - if (IfIndex == MAXUINT32) { - LOGERR("-IfIndex is required"); - return FALSE; - } - - return TRUE; -} - -int -__cdecl -main( - int argc, - char** argv - ) -{ - XDP_STATUS XdpStatus; - HANDLE XskMap = NULL; - HANDLE TargetProcess = NULL; - HANDLE RemoteHandle = NULL; - HANDLE Program = NULL; - - if (!ParseArgs(argc, argv)) { - PrintUsage(); - return 1; - } - - // - // Stage 1: Create the XSKMAP. - // - XdpStatus = XdpMapCreate(&XskMap, XDP_MAP_TYPE_XSKMAP); - if (FAILED(XdpStatus)) { - LOGERR("XdpMapCreate failed: 0x%x", XdpStatus); - return 1; - } - - printf("[Stage 1] Created XSKMAP (local handle: 0x%IX)\n", (UINT_PTR)XskMap); - - // - // Stage 2: Duplicate the map handle into the consumer process. - // - TargetProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, TargetPid); - if (TargetProcess == NULL) { - LOGERR("OpenProcess(%u) failed: %u. Ensure the consumer is running.", - TargetPid, GetLastError()); - CloseHandle(XskMap); - return 1; - } - - if (!DuplicateHandle( - GetCurrentProcess(), - XskMap, - TargetProcess, - &RemoteHandle, - 0, - FALSE, - DUPLICATE_SAME_ACCESS)) { - LOGERR("DuplicateHandle failed: %u", GetLastError()); - CloseHandle(TargetProcess); - CloseHandle(XskMap); - return 1; - } - - CloseHandle(TargetProcess); - - printf("[Stage 2] Duplicated XSKMAP into PID %u. Remote handle: 0x%IX\n", - TargetPid, (UINT_PTR)RemoteHandle); - printf("\n"); - printf(" Paste 0x%IX into the consumer, let it create and insert its XSKs.\n", - (UINT_PTR)RemoteHandle); - printf("\n"); - - // - // Stage 3: Wait for user signal that the consumer is ready. - // - printf("[Stage 3] Press ENTER when the consumer has inserted its XSKs...\n"); - fflush(stdout); - (void)getchar(); - - // - // Stage 4: Attach the XDP program with the now-populated map. - // - { - const XDP_HOOK_ID XdpInspectRxL2 = { - XDP_HOOK_L2, - XDP_HOOK_RX, - XDP_HOOK_INSPECT, - }; - - XDP_RULE Rule; - ZeroMemory(&Rule, sizeof(Rule)); - - if (UseIcmpMatch) { - Rule.Match = XDP_MATCH_IP_NEXT_HEADER; - Rule.Pattern.NextHeader = 1; // IPPROTO_ICMP - } else { - Rule.Match = XDP_MATCH_ALL; - } - - Rule.Action = XDP_PROGRAM_ACTION_REDIRECT; - Rule.Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rule.Redirect.Target = XskMap; - - XdpStatus = - XdpCreateProgram( - IfIndex, - &XdpInspectRxL2, - 0, - XDP_CREATE_PROGRAM_FLAG_ALL_QUEUES, - &Rule, - 1, - &Program); - if (FAILED(XdpStatus)) { - LOGERR("XdpCreateProgram failed: 0x%x", XdpStatus); - CloseHandle(XskMap); - return 1; - } - } - - printf("[Stage 4] XDP program attached on IfIndex %u (%s). Traffic is flowing!\n", - IfIndex, UseIcmpMatch ? "ICMPv4 only" : "all traffic"); - printf("\n"); - printf("Press ENTER to detach the program and exit.\n"); - fflush(stdout); - (void)getchar(); - - // - // Cleanup: closing the program handle detaches XDP. Closing the map - // handle releases our reference (consumer may still hold one). - // - printf("Detaching XDP program and cleaning up.\n"); - CloseHandle(Program); - CloseHandle(XskMap); - - return 0; -} From 4546dfe64664e04451d7428904ca6ee42264d102 Mon Sep 17 00:00:00 2001 From: Jack He Date: Sun, 17 May 2026 19:19:45 -0700 Subject: [PATCH 12/41] get rid of preview features --- src/platform/platform_internal.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 62ac7f6b8a..a21fac60c1 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -1225,7 +1225,6 @@ RawDataPathUpdatePollingIdleTimeout( _In_ uint32_t PollingIdleTimeoutUs ); -#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES // // Inserts each interface's RX XSK sockets into the matching XSKMAP from the // provided map configs. Uses an O(Interfaces * MapConfigCount) search to match @@ -1238,7 +1237,6 @@ CxPlatDpRawInsertXskByMapConfigs( _In_reads_(MapConfigCount) const QUIC_XDP_MAP_CONFIG* MapConfigs, _In_ uint32_t MapConfigCount ); -#endif _IRQL_requires_max_(DISPATCH_LEVEL) CXPLAT_DATAPATH_FEATURES From ba539a7349cd217d224ccfa97359c8271bb51ffc Mon Sep 17 00:00:00 2001 From: Jack He Date: Sun, 17 May 2026 19:25:36 -0700 Subject: [PATCH 13/41] add unit tests --- src/platform/unittest/DataPathTest.cpp | 270 +++++++++++++++++++++++++ 1 file changed, 270 insertions(+) diff --git a/src/platform/unittest/DataPathTest.cpp b/src/platform/unittest/DataPathTest.cpp index 134f8db040..936d55834e 100644 --- a/src/platform/unittest/DataPathTest.cpp +++ b/src/platform/unittest/DataPathTest.cpp @@ -1408,4 +1408,274 @@ TEST_F(DataPathTest, XdpRuleAddOomCleanup) } #endif // DEBUG +// +// XDP Map Mode Tests +// +// These tests exercise the XDP map mode initialization path in +// CxPlatDataPathInitialize (datapath_xplat.c). In map mode the platform +// (WinSock/epoll) datapath is bypassed and only the raw (XDP) datapath is +// used. Map mode is triggered when XdpMapConfigCount > 0 and +// XdpMapConfigs != NULL in the init config. +// + +TEST_F(DataPathTest, XdpMapMode_ZeroConfigUsesNormalPath) +{ + // + // XdpMapConfigCount == 0 should fall through to the normal (non-map) + // initialization path and succeed. + // + QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 0, {0} }; + CXPLAT_WORKER_POOL* WorkerPool = + CxPlatWorkerPoolCreate(&ExecConfig, CXPLAT_WORKER_POOL_REF_TOOL); + ASSERT_NE(nullptr, WorkerPool); + + CXPLAT_DATAPATH_INIT_CONFIG InitConfig = {0}; + InitConfig.EnableDscpOnRecv = TRUE; + InitConfig.XdpMapConfigs = nullptr; + InitConfig.XdpMapConfigCount = 0; + + CXPLAT_DATAPATH* Datapath = nullptr; + QUIC_STATUS Status = + CxPlatDataPathInitialize( + 0, + &EmptyUdpCallbacks, + nullptr, + WorkerPool, + &InitConfig, + &Datapath); + VERIFY_QUIC_SUCCESS(Status); + ASSERT_NE(nullptr, Datapath); + + CxPlatDataPathUninitialize(Datapath); + CxPlatWorkerPoolDelete(WorkerPool, CXPLAT_WORKER_POOL_REF_TOOL); +} + +TEST_F(DataPathTest, XdpMapMode_NullConfigsUsesNormalPath) +{ + // + // XdpMapConfigCount > 0 but XdpMapConfigs == NULL should fall through + // to the normal path because the condition requires both to be set. + // + QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 0, {0} }; + CXPLAT_WORKER_POOL* WorkerPool = + CxPlatWorkerPoolCreate(&ExecConfig, CXPLAT_WORKER_POOL_REF_TOOL); + ASSERT_NE(nullptr, WorkerPool); + + CXPLAT_DATAPATH_INIT_CONFIG InitConfig = {0}; + InitConfig.EnableDscpOnRecv = TRUE; + InitConfig.XdpMapConfigs = nullptr; + InitConfig.XdpMapConfigCount = 1; + + CXPLAT_DATAPATH* Datapath = nullptr; + QUIC_STATUS Status = + CxPlatDataPathInitialize( + 0, + &EmptyUdpCallbacks, + nullptr, + WorkerPool, + &InitConfig, + &Datapath); + VERIFY_QUIC_SUCCESS(Status); + ASSERT_NE(nullptr, Datapath); + + CxPlatDataPathUninitialize(Datapath); + CxPlatWorkerPoolDelete(WorkerPool, CXPLAT_WORKER_POOL_REF_TOOL); +} + +TEST_F(DataPathTest, XdpMapMode_InitFailsWithoutRawDatapath) +{ + // + // On machines without XDP/DuoNic, raw datapath initialization fails. + // In map mode this is a hard failure (QUIC_STATUS_NOT_SUPPORTED) unlike + // the normal path where raw datapath failure is tolerated. Verify that + // the error is propagated cleanly and the output pointer is set to NULL. + // + if (UseDuoNic) { + GTEST_SKIP_NO_RETURN_("DuoNic is available; raw datapath will succeed"); + return; + } + + const uint32_t FakeIfIndex = 0xDEAD; + const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)(uintptr_t)0x1234; + QUIC_XDP_MAP_CONFIG MapConfig = { FakeIfIndex, FakeHandle }; + + QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 0, {0} }; + CXPLAT_WORKER_POOL* WorkerPool = + CxPlatWorkerPoolCreate(&ExecConfig, CXPLAT_WORKER_POOL_REF_TOOL); + ASSERT_NE(nullptr, WorkerPool); + + CXPLAT_DATAPATH_INIT_CONFIG InitConfig = {0}; + InitConfig.EnableDscpOnRecv = TRUE; + InitConfig.XdpMapConfigs = &MapConfig; + InitConfig.XdpMapConfigCount = 1; + + CXPLAT_DATAPATH* Datapath = nullptr; + QUIC_STATUS Status = + CxPlatDataPathInitialize( + 0, + &EmptyUdpCallbacks, + nullptr, + WorkerPool, + &InitConfig, + &Datapath); + ASSERT_TRUE(QUIC_FAILED(Status)); + ASSERT_EQ(nullptr, Datapath); + + CxPlatWorkerPoolDelete(WorkerPool, CXPLAT_WORKER_POOL_REF_TOOL); +} + +TEST_F(DataPathTest, XdpMapMode_InitSucceedsWithNonMatchingIfIndex) +{ + // + // On DuoNic machines, raw datapath initialization succeeds. + // When the provided IfIndex doesn't match any real interface, + // CxPlatDpRawInsertXskByMapConfigs is a no-op and returns SUCCESS. + // The datapath should be created in XDP map mode. + // + if (!UseDuoNic) { + GTEST_SKIP_NO_RETURN_("Requires DuoNic/XDP for raw datapath init"); + return; + } + + const uint32_t FakeIfIndex = 0xDEAD; + const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)(uintptr_t)0x1234; + QUIC_XDP_MAP_CONFIG MapConfig = { FakeIfIndex, FakeHandle }; + + QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 1, {0} }; + CXPLAT_WORKER_POOL* WorkerPool = + CxPlatWorkerPoolCreate(&ExecConfig, CXPLAT_WORKER_POOL_REF_TOOL); + ASSERT_NE(nullptr, WorkerPool); + + CXPLAT_DATAPATH_INIT_CONFIG InitConfig = {0}; + InitConfig.EnableDscpOnRecv = TRUE; + InitConfig.XdpMapConfigs = &MapConfig; + InitConfig.XdpMapConfigCount = 1; + + CXPLAT_DATAPATH* Datapath = nullptr; + QUIC_STATUS Status = + CxPlatDataPathInitialize( + 0, + &EmptyUdpCallbacks, + nullptr, + WorkerPool, + &InitConfig, + &Datapath); + VERIFY_QUIC_SUCCESS(Status); + ASSERT_NE(nullptr, Datapath); + + // + // Uninitialize should take the map-mode cleanup path (direct free, + // no platform uninit) without crashing. + // + CxPlatDataPathUninitialize(Datapath); + CxPlatWorkerPoolDelete(WorkerPool, CXPLAT_WORKER_POOL_REF_TOOL); +} + +TEST_F(DataPathTest, XdpMapMode_MultipleNonMatchingConfigs) +{ + // + // Multiple map configs with non-matching IfIndex values should all + // be silently skipped and initialization should succeed. + // + if (!UseDuoNic) { + GTEST_SKIP_NO_RETURN_("Requires DuoNic/XDP for raw datapath init"); + return; + } + + QUIC_XDP_MAP_CONFIG MapConfigs[3] = { + { 0xDEAD, (QUIC_XDP_MAP_HANDLE)(uintptr_t)0x1111 }, + { 0xBEEF, (QUIC_XDP_MAP_HANDLE)(uintptr_t)0x2222 }, + { 0xCAFE, (QUIC_XDP_MAP_HANDLE)(uintptr_t)0x3333 }, + }; + + QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 1, {0} }; + CXPLAT_WORKER_POOL* WorkerPool = + CxPlatWorkerPoolCreate(&ExecConfig, CXPLAT_WORKER_POOL_REF_TOOL); + ASSERT_NE(nullptr, WorkerPool); + + CXPLAT_DATAPATH_INIT_CONFIG InitConfig = {0}; + InitConfig.EnableDscpOnRecv = TRUE; + InitConfig.XdpMapConfigs = MapConfigs; + InitConfig.XdpMapConfigCount = 3; + + CXPLAT_DATAPATH* Datapath = nullptr; + QUIC_STATUS Status = + CxPlatDataPathInitialize( + 0, + &EmptyUdpCallbacks, + nullptr, + WorkerPool, + &InitConfig, + &Datapath); + VERIFY_QUIC_SUCCESS(Status); + ASSERT_NE(nullptr, Datapath); + + CxPlatDataPathUninitialize(Datapath); + CxPlatWorkerPoolDelete(WorkerPool, CXPLAT_WORKER_POOL_REF_TOOL); +} + +TEST_F(DataPathTest, XdpMapMode_SocketSkipsRulePlumbing) +{ + // + // In XDP map mode, socket creation and deletion should skip XDP rule + // plumbing (CxPlatDpRawPlumbRulesOnSocket). Create a connected QTIP+XDP + // socket in map mode and verify that create and delete succeed without + // the rule plumbing step. + // + if (!UseDuoNic) { + GTEST_SKIP_NO_RETURN_("Requires DuoNic/XDP for raw datapath init"); + return; + } + + const uint32_t FakeIfIndex = 0xDEAD; + const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)(uintptr_t)0x1234; + QUIC_XDP_MAP_CONFIG MapConfig = { FakeIfIndex, FakeHandle }; + + QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 1, {0} }; + CXPLAT_WORKER_POOL* WorkerPool = + CxPlatWorkerPoolCreate(&ExecConfig, CXPLAT_WORKER_POOL_REF_TOOL); + ASSERT_NE(nullptr, WorkerPool); + + CXPLAT_DATAPATH_INIT_CONFIG InitConfig = {0}; + InitConfig.EnableDscpOnRecv = TRUE; + InitConfig.XdpMapConfigs = &MapConfig; + InitConfig.XdpMapConfigCount = 1; + + CXPLAT_DATAPATH* Datapath = nullptr; + QUIC_STATUS Status = + CxPlatDataPathInitialize( + 0, + &EmptyUdpCallbacks, + nullptr, + WorkerPool, + &InitConfig, + &Datapath); + VERIFY_QUIC_SUCCESS(Status); + ASSERT_NE(nullptr, Datapath); + + // + // Create a connected QTIP+XDP socket. QTIP skips the normal OS UDP + // socket creation and goes directly to RawSocketCreateUdp, which + // should skip rule plumbing in map mode. + // + QuicAddr RemoteAddr = GetNewLocalIPv4(); + + CXPLAT_UDP_CONFIG UdpConfig = {0}; + UdpConfig.RemoteAddress = &RemoteAddr.SockAddr; + UdpConfig.Flags = CXPLAT_SOCKET_FLAG_XDP | CXPLAT_SOCKET_FLAG_QTIP; + + CXPLAT_SOCKET* Socket = nullptr; + Status = CxPlatSocketCreateUdp(Datapath, &UdpConfig, &Socket); + VERIFY_QUIC_SUCCESS(Status); + ASSERT_NE(nullptr, Socket); + + // + // Delete should also skip rule unplumbing in map mode. + // + CxPlatSocketDelete(Socket); + + CxPlatDataPathUninitialize(Datapath); + CxPlatWorkerPoolDelete(WorkerPool, CXPLAT_WORKER_POOL_REF_TOOL); +} + INSTANTIATE_TEST_SUITE_P(DataPathTest, DataPathTest, ::testing::Values(4, 6), testing::PrintToStringParamName()); From d15f3a73b8132d54ec301700354653bf001649c3 Mon Sep 17 00:00:00 2001 From: Jack He Date: Sun, 17 May 2026 19:56:04 -0700 Subject: [PATCH 14/41] CLOG --- src/generated/linux/datapath_raw.c.clog.h | 8 +- .../linux/datapath_raw.c.clog.h.lttng.h | 8 +- .../linux/datapath_raw_xdp_win.c.clog.h | 44 +++++++++++ .../datapath_raw_xdp_win.c.clog.h.lttng.h | 54 +++++++++++++ src/generated/linux/datapath_xplat.c.clog.h | 62 ++++++++++++++- .../linux/datapath_xplat.c.clog.h.lttng.h | 62 ++++++++++++++- src/manifest/clog.sidecar | 79 +++++++++++++++++++ 7 files changed, 305 insertions(+), 12 deletions(-) diff --git a/src/generated/linux/datapath_raw.c.clog.h b/src/generated/linux/datapath_raw.c.clog.h index 35e4abef69..cdea908e42 100644 --- a/src/generated/linux/datapath_raw.c.clog.h +++ b/src/generated/linux/datapath_raw.c.clog.h @@ -45,10 +45,10 @@ tracepoint(CLOG_DATAPATH_RAW_C, AllocFailure , arg2, arg3);\ // Decoder Ring for LibraryErrorStatus // [ lib] ERROR, %u, %s. // QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - PlumbStatus, - "CxPlatDpRawPlumbRulesOnSocket (delete)"); + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + PlumbStatus, + "CxPlatDpRawPlumbRulesOnSocket (delete)"); // arg2 = arg2 = PlumbStatus = arg2 // arg3 = arg3 = "CxPlatDpRawPlumbRulesOnSocket (delete)" = arg3 ----------------------------------------------------------*/ diff --git a/src/generated/linux/datapath_raw.c.clog.h.lttng.h b/src/generated/linux/datapath_raw.c.clog.h.lttng.h index 4edb7a0fb6..c268da76b4 100644 --- a/src/generated/linux/datapath_raw.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw.c.clog.h.lttng.h @@ -28,10 +28,10 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_C, AllocFailure, // Decoder Ring for LibraryErrorStatus // [ lib] ERROR, %u, %s. // QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - PlumbStatus, - "CxPlatDpRawPlumbRulesOnSocket (delete)"); + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + PlumbStatus, + "CxPlatDpRawPlumbRulesOnSocket (delete)"); // arg2 = arg2 = PlumbStatus = arg2 // arg3 = arg3 = "CxPlatDpRawPlumbRulesOnSocket (delete)" = arg3 ----------------------------------------------------------*/ diff --git a/src/generated/linux/datapath_raw_xdp_win.c.clog.h b/src/generated/linux/datapath_raw_xdp_win.c.clog.h index 3b4f03bf3b..3e966180f9 100644 --- a/src/generated/linux/datapath_raw_xdp_win.c.clog.h +++ b/src/generated/linux/datapath_raw_xdp_win.c.clog.h @@ -332,6 +332,50 @@ tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpPartitionShutdownComplete , arg2);\ +/*---------------------------------------------------------- +// Decoder Ring for XdpMapModeConfigured +// [ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p) +// QuicTraceLogVerbose( + XdpMapModeConfigured, + "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", + Interface, + Interface->IfIndex, + XskMap); +// arg2 = arg2 = Interface = arg2 +// arg3 = arg3 = Interface->IfIndex = arg3 +// arg4 = arg4 = XskMap = arg4 +----------------------------------------------------------*/ +#ifndef _clog_5_ARGS_TRACE_XdpMapModeConfigured +#define _clog_5_ARGS_TRACE_XdpMapModeConfigured(uniqueId, encoded_arg_string, arg2, arg3, arg4)\ +tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeConfigured , arg2, arg3, arg4);\ + +#endif + + + + +/*---------------------------------------------------------- +// Decoder Ring for XdpMapModeInserted +// [ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u) +// QuicTraceLogVerbose( + XdpMapModeInserted, + "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", + Interface, + j, + Interface->IfIndex); +// arg2 = arg2 = Interface = arg2 +// arg3 = arg3 = j = arg3 +// arg4 = arg4 = Interface->IfIndex = arg4 +----------------------------------------------------------*/ +#ifndef _clog_5_ARGS_TRACE_XdpMapModeInserted +#define _clog_5_ARGS_TRACE_XdpMapModeInserted(uniqueId, encoded_arg_string, arg2, arg3, arg4)\ +tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeInserted , arg2, arg3, arg4);\ + +#endif + + + + /*---------------------------------------------------------- // Decoder Ring for LibraryErrorStatus // [ lib] ERROR, %u, %s. diff --git a/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h b/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h index a1850a527c..b0ad3c80d3 100644 --- a/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h @@ -336,6 +336,60 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpPartitionShutdownComplete, +/*---------------------------------------------------------- +// Decoder Ring for XdpMapModeConfigured +// [ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p) +// QuicTraceLogVerbose( + XdpMapModeConfigured, + "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", + Interface, + Interface->IfIndex, + XskMap); +// arg2 = arg2 = Interface = arg2 +// arg3 = arg3 = Interface->IfIndex = arg3 +// arg4 = arg4 = XskMap = arg4 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeConfigured, + TP_ARGS( + const void *, arg2, + unsigned int, arg3, + const void *, arg4), + TP_FIELDS( + ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2) + ctf_integer(unsigned int, arg3, arg3) + ctf_integer_hex(uint64_t, arg4, (uint64_t)arg4) + ) +) + + + +/*---------------------------------------------------------- +// Decoder Ring for XdpMapModeInserted +// [ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u) +// QuicTraceLogVerbose( + XdpMapModeInserted, + "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", + Interface, + j, + Interface->IfIndex); +// arg2 = arg2 = Interface = arg2 +// arg3 = arg3 = j = arg3 +// arg4 = arg4 = Interface->IfIndex = arg4 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeInserted, + TP_ARGS( + const void *, arg2, + unsigned int, arg3, + unsigned int, arg4), + TP_FIELDS( + ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2) + ctf_integer(unsigned int, arg3, arg3) + ctf_integer(unsigned int, arg4, arg4) + ) +) + + + /*---------------------------------------------------------- // Decoder Ring for LibraryErrorStatus // [ lib] ERROR, %u, %s. diff --git a/src/generated/linux/datapath_xplat.c.clog.h b/src/generated/linux/datapath_xplat.c.clog.h index da7666512f..87a08f6aee 100644 --- a/src/generated/linux/datapath_xplat.c.clog.h +++ b/src/generated/linux/datapath_xplat.c.clog.h @@ -26,6 +26,10 @@ #define _clog_MACRO_QuicTraceLogError 1 #define QuicTraceLogError(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) #endif +#ifndef _clog_MACRO_QuicTraceEvent +#define _clog_MACRO_QuicTraceEvent 1 +#define QuicTraceEvent(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) +#endif #ifdef __cplusplus extern "C" { #endif @@ -63,12 +67,46 @@ tracepoint(CLOG_DATAPATH_XPLAT_C, WarnNoXdpForCibirSockets );\ +/*---------------------------------------------------------- +// Decoder Ring for DatapathRawInitFailMapMode +// [ dp] XDP map mode: raw datapath required but failed to initialize +// QuicTraceLogVerbose( + DatapathRawInitFailMapMode, + "[ dp] XDP map mode: raw datapath required but failed to initialize"); +----------------------------------------------------------*/ +#ifndef _clog_2_ARGS_TRACE_DatapathRawInitFailMapMode +#define _clog_2_ARGS_TRACE_DatapathRawInitFailMapMode(uniqueId, encoded_arg_string)\ +tracepoint(CLOG_DATAPATH_XPLAT_C, DatapathRawInitFailMapMode );\ + +#endif + + + + +/*---------------------------------------------------------- +// Decoder Ring for DatapathRawMapInsertFail +// [ dp] XDP map mode: failed to insert XSK sockets into map, status:%d +// QuicTraceLogVerbose( + DatapathRawMapInsertFail, + "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d", + Status); +// arg2 = arg2 = Status = arg2 +----------------------------------------------------------*/ +#ifndef _clog_3_ARGS_TRACE_DatapathRawMapInsertFail +#define _clog_3_ARGS_TRACE_DatapathRawMapInsertFail(uniqueId, encoded_arg_string, arg2)\ +tracepoint(CLOG_DATAPATH_XPLAT_C, DatapathRawMapInsertFail , arg2);\ + +#endif + + + + /*---------------------------------------------------------- // Decoder Ring for DatapathInitFail // [ dp] Failed to initialize datapath, status:%d // QuicTraceLogVerbose( - DatapathInitFail, - "[ dp] Failed to initialize datapath, status:%d", Status); + DatapathInitFail, + "[ dp] Failed to initialize datapath, status:%d", Status); // arg2 = arg2 = Status = arg2 ----------------------------------------------------------*/ #ifndef _clog_3_ARGS_TRACE_DatapathInitFail @@ -130,6 +168,26 @@ tracepoint(CLOG_DATAPATH_XPLAT_C, ErrNoXdpForQtip );\ +/*---------------------------------------------------------- +// Decoder Ring for AllocFailure +// Allocation of '%s' failed. (%llu bytes) +// QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_DATAPATH (map mode)", + sizeof(CXPLAT_DATAPATH)); +// arg2 = arg2 = "CXPLAT_DATAPATH (map mode)" = arg2 +// arg3 = arg3 = sizeof(CXPLAT_DATAPATH) = arg3 +----------------------------------------------------------*/ +#ifndef _clog_4_ARGS_TRACE_AllocFailure +#define _clog_4_ARGS_TRACE_AllocFailure(uniqueId, encoded_arg_string, arg2, arg3)\ +tracepoint(CLOG_DATAPATH_XPLAT_C, AllocFailure , arg2, arg3);\ + +#endif + + + + #ifdef __cplusplus } #endif diff --git a/src/generated/linux/datapath_xplat.c.clog.h.lttng.h b/src/generated/linux/datapath_xplat.c.clog.h.lttng.h index 45f0bcbaae..5cfa611716 100644 --- a/src/generated/linux/datapath_xplat.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_xplat.c.clog.h.lttng.h @@ -35,12 +35,47 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, WarnNoXdpForCibirSockets, +/*---------------------------------------------------------- +// Decoder Ring for DatapathRawInitFailMapMode +// [ dp] XDP map mode: raw datapath required but failed to initialize +// QuicTraceLogVerbose( + DatapathRawInitFailMapMode, + "[ dp] XDP map mode: raw datapath required but failed to initialize"); +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, DatapathRawInitFailMapMode, + TP_ARGS( +), + TP_FIELDS( + ) +) + + + +/*---------------------------------------------------------- +// Decoder Ring for DatapathRawMapInsertFail +// [ dp] XDP map mode: failed to insert XSK sockets into map, status:%d +// QuicTraceLogVerbose( + DatapathRawMapInsertFail, + "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d", + Status); +// arg2 = arg2 = Status = arg2 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, DatapathRawMapInsertFail, + TP_ARGS( + int, arg2), + TP_FIELDS( + ctf_integer(int, arg2, arg2) + ) +) + + + /*---------------------------------------------------------- // Decoder Ring for DatapathInitFail // [ dp] Failed to initialize datapath, status:%d // QuicTraceLogVerbose( - DatapathInitFail, - "[ dp] Failed to initialize datapath, status:%d", Status); + DatapathInitFail, + "[ dp] Failed to initialize datapath, status:%d", Status); // arg2 = arg2 = Status = arg2 ----------------------------------------------------------*/ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, DatapathInitFail, @@ -102,3 +137,26 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, ErrNoXdpForQtip, TP_FIELDS( ) ) + + + +/*---------------------------------------------------------- +// Decoder Ring for AllocFailure +// Allocation of '%s' failed. (%llu bytes) +// QuicTraceEvent( + AllocFailure, + "Allocation of '%s' failed. (%llu bytes)", + "CXPLAT_DATAPATH (map mode)", + sizeof(CXPLAT_DATAPATH)); +// arg2 = arg2 = "CXPLAT_DATAPATH (map mode)" = arg2 +// arg3 = arg3 = sizeof(CXPLAT_DATAPATH) = arg3 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, AllocFailure, + TP_ARGS( + const char *, arg2, + unsigned long long, arg3), + TP_FIELDS( + ctf_string(arg2, arg2) + ctf_integer(uint64_t, arg3, arg3) + ) +) diff --git a/src/manifest/clog.sidecar b/src/manifest/clog.sidecar index 808f06c3fd..7de1d425c8 100644 --- a/src/manifest/clog.sidecar +++ b/src/manifest/clog.sidecar @@ -2807,6 +2807,25 @@ ], "macroName": "QuicTraceLogWarning" }, + "DatapathRawInitFailMapMode": { + "ModuleProperites": {}, + "TraceString": "[ dp] XDP map mode: raw datapath required but failed to initialize", + "UniqueId": "DatapathRawInitFailMapMode", + "splitArgs": [], + "macroName": "QuicTraceLogVerbose" + }, + "DatapathRawMapInsertFail": { + "ModuleProperites": {}, + "TraceString": "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d", + "UniqueId": "DatapathRawMapInsertFail", + "splitArgs": [ + { + "DefinationEncoding": "d", + "MacroVariableName": "arg2" + } + ], + "macroName": "QuicTraceLogVerbose" + }, "DatapathRecv": { "ModuleProperites": {}, "TraceString": "[data][%p] Recv %u bytes (segment=%hu) Src=%!ADDR! Dst=%!ADDR!", @@ -12331,6 +12350,46 @@ ], "macroName": "QuicTraceLogVerbose" }, + "XdpMapModeConfigured": { + "ModuleProperites": {}, + "TraceString": "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", + "UniqueId": "XdpMapModeConfigured", + "splitArgs": [ + { + "DefinationEncoding": "p", + "MacroVariableName": "arg2" + }, + { + "DefinationEncoding": "u", + "MacroVariableName": "arg3" + }, + { + "DefinationEncoding": "p", + "MacroVariableName": "arg4" + } + ], + "macroName": "QuicTraceLogVerbose" + }, + "XdpMapModeInserted": { + "ModuleProperites": {}, + "TraceString": "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", + "UniqueId": "XdpMapModeInserted", + "splitArgs": [ + { + "DefinationEncoding": "p", + "MacroVariableName": "arg2" + }, + { + "DefinationEncoding": "u", + "MacroVariableName": "arg3" + }, + { + "DefinationEncoding": "u", + "MacroVariableName": "arg4" + } + ], + "macroName": "QuicTraceLogVerbose" + }, "XdpPartitionShutdown": { "ModuleProperites": {}, "TraceString": "[ xdp][%p] XDP partition shutdown", @@ -13862,6 +13921,16 @@ "TraceID": "DatapathQueryUdpSendMsgFailedAsync", "EncodingString": "[data] Query for UDP_SEND_MSG_SIZE failed (async), 0x%x" }, + { + "UniquenessHash": "bc85b642-06e2-35a0-f754-716b4e5484cb", + "TraceID": "DatapathRawInitFailMapMode", + "EncodingString": "[ dp] XDP map mode: raw datapath required but failed to initialize" + }, + { + "UniquenessHash": "38cb5c9d-e612-01a0-ec5f-6af064bb0724", + "TraceID": "DatapathRawMapInsertFail", + "EncodingString": "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d" + }, { "UniquenessHash": "3b8a506d-ede0-3c3b-7c31-83127acebfc6", "TraceID": "DatapathRecv", @@ -16862,6 +16931,16 @@ "TraceID": "XdpInterfaceQueues", "EncodingString": "[ixdp][%p] Initializing %u queues on interface" }, + { + "UniquenessHash": "005c527e-db63-8dde-7829-b589b84080d9", + "TraceID": "XdpMapModeConfigured", + "EncodingString": "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)" + }, + { + "UniquenessHash": "ebd31a62-efa7-20b9-5d92-04e36ff534ba", + "TraceID": "XdpMapModeInserted", + "EncodingString": "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)" + }, { "UniquenessHash": "18009e0d-e738-467e-35c0-55ebabcbf31f", "TraceID": "XdpPartitionShutdown", From 4fe809462d89a18ffb2319b9df05e5157a6a58c6 Mon Sep 17 00:00:00 2001 From: Jack He Date: Mon, 18 May 2026 15:59:08 -0700 Subject: [PATCH 15/41] fix abi incompat --- src/platform/datapath_raw_xdp_win.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index c537c42b28..d2d73f7094 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -11,6 +11,8 @@ #define _CRT_SECURE_NO_WARNINGS 1 // TODO - Remove #define XDP_API_VERSION 3 +#define XDP_MINIMUM_MAJOR_VER 1 +#define XDP_MINIMUM_MINOR_VER 1 #define XDP_INCLUDE_WINCOMMON #include From 47f162d3db4c1ab09b11e391086b65220b3c166f Mon Sep 17 00:00:00 2001 From: Jack He Date: Tue, 19 May 2026 14:29:46 -0700 Subject: [PATCH 16/41] address code feedback --- src/inc/quic_datapath.h | 12 +++++---- src/platform/datapath_raw.c | 16 +++++------ src/platform/datapath_raw_win.c | 42 ++++++++++++++--------------- src/platform/datapath_raw_xdp_win.c | 20 +++++++++----- src/platform/datapath_xplat.c | 4 +-- src/platform/platform_internal.h | 12 ++++----- src/tools/recvfuzz/recvfuzz.cpp | 2 +- 7 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index c184b6a062..3b683d0090 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -478,11 +478,13 @@ typedef struct CXPLAT_DATAPATH_INIT_CONFIG { BOOLEAN EnableDscpOnRecv; // - // External XDP map configurations. When non-NULL (and Count > 0), the - // datapath operates in XDP map mode: the WinSock (normal) datapath is - // skipped, the raw (XDP) datapath is required to succeed, and XSK sockets - // are inserted into the provided XSKMAPs at init time. - // The buffer must remain valid for the lifetime of the datapath. + // External XDP map configurations. When XdpMapConfigCount > 0 and + // XdpMapConfigs is non-NULL, the datapath operates in XDP map mode: the + // WinSock (normal) datapath is skipped, the raw (XDP) datapath is required + // to succeed, and XSK sockets are inserted into the provided XSKMAPs at + // init time. + // XdpMapConfigs is NULL if and only if XdpMapConfigCount == 0. + // The map configs must remain valid for the lifetime of the datapath. // const struct QUIC_XDP_MAP_CONFIG* XdpMapConfigs; uint32_t XdpMapConfigCount; diff --git a/src/platform/datapath_raw.c b/src/platform/datapath_raw.c index f67c21d755..99d0ec67e1 100644 --- a/src/platform/datapath_raw.c +++ b/src/platform/datapath_raw.c @@ -179,15 +179,13 @@ RawSocketDelete( // some XDP rules may linger until the interface is reinitialized, but // there is nothing useful we can do at this point. // - if (!Socket->RawDatapath->ParentDataPath->XdpMapMode) { - QUIC_STATUS PlumbStatus = CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); - if (QUIC_FAILED(PlumbStatus)) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - PlumbStatus, - "CxPlatDpRawPlumbRulesOnSocket (delete)"); - } + QUIC_STATUS PlumbStatus = CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); + if (QUIC_FAILED(PlumbStatus)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + PlumbStatus, + "CxPlatDpRawPlumbRulesOnSocket (delete)"); } CxPlatRemoveSocket(&Socket->RawDatapath->SocketPool, Socket); CxPlatRundownReleaseAndWait(&Socket->RawRundown); diff --git a/src/platform/datapath_raw_win.c b/src/platform/datapath_raw_win.c index c3dcfb3405..d409a43c7a 100644 --- a/src/platform/datapath_raw_win.c +++ b/src/platform/datapath_raw_win.c @@ -170,29 +170,27 @@ RawSocketCreateUdp( goto Error; } - if (!Raw->ParentDataPath->XdpMapMode) { - Status = CxPlatDpRawPlumbRulesOnSocket(Socket, TRUE); - if (QUIC_FAILED(Status)) { - // - // CxPlatDpRawPlumbRulesOnSocket(TRUE) stops at the first interface - // where rule installation fails, so some interfaces may already have - // partial state (rules installed or port bits set) that must be rolled - // back. Reuse the IsCreated=FALSE path for best-effort cleanup; it is - // safe to call against interfaces where nothing was installed (no-op - // for those). Cleanup failures are logged but do not change the - // returned error. - // - QUIC_STATUS CleanupStatus = CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); - if (QUIC_FAILED(CleanupStatus)) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - CleanupStatus, - "CxPlatDpRawPlumbRulesOnSocket cleanup"); - } - CxPlatRemoveSocket(&Raw->SocketPool, Socket); - goto Error; + Status = CxPlatDpRawPlumbRulesOnSocket(Socket, TRUE); + if (QUIC_FAILED(Status)) { + // + // CxPlatDpRawPlumbRulesOnSocket(TRUE) stops at the first interface + // where rule installation fails, so some interfaces may already have + // partial state (rules installed or port bits set) that must be rolled + // back. Reuse the IsCreated=FALSE path for best-effort cleanup; it is + // safe to call against interfaces where nothing was installed (no-op + // for those). Cleanup failures are logged but do not change the + // returned error. + // + QUIC_STATUS CleanupStatus = CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE); + if (QUIC_FAILED(CleanupStatus)) { + QuicTraceEvent( + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + CleanupStatus, + "CxPlatDpRawPlumbRulesOnSocket cleanup"); } + CxPlatRemoveSocket(&Raw->SocketPool, Socket); + goto Error; } Error: diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index d2d73f7094..fff90c715c 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -31,6 +31,11 @@ #define XDP_MAX_SYNC_WAIT_TIMEOUT_MS 1000 // Used for querying XDP RSS capabilities. +// +// Mirrors XDP's private XSKMAP_MAX_SIZE; keep in sync with the XDP submodule. +// +#define CXPLAT_XSKMAP_MAX_SIZE 128 + typedef struct XDP_DATAPATH { CXPLAT_DATAPATH_RAW; DECLSPEC_CACHEALIGN @@ -465,7 +470,7 @@ CxPlatDpRawInterfaceInitialize( goto Error; } - CXPLAT_DBG_ASSERT(Interface->QueueCount <= 128); // XSKMAP_MAX_SIZE + CXPLAT_DBG_ASSERT(Interface->QueueCount <= CXPLAT_XSKMAP_MAX_SIZE); if (Interface->QueueCount == 0) { Status = QUIC_STATUS_INVALID_STATE; @@ -1469,6 +1474,14 @@ CxPlatDpRawPlumbRulesOnSocket( { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Socket->RawDatapath; + + // + // In external map mode, the caller manages XDP rules; nothing to do here. + // + if (Xdp->ParentDataPath->UseExternalXdpMaps) { + return QUIC_STATUS_SUCCESS; + } + if (Socket->Wildcard) { XDP_RULE Rules[5] = {0}; uint8_t RulesSize = 0; @@ -2268,11 +2281,6 @@ CxPlatDataPathRssConfigFree( CXPLAT_FREE(RssConfig, QUIC_POOL_DATAPATH_RSS_CONFIG); } -// -// Inserts each interface's RX XSK sockets into the matching XSKMAP from the -// provided map configs. Uses an O(Interfaces * MapConfigCount) search to match -// each config to its interface by IfIndex. -// _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDpRawInsertXskByMapConfigs( diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index 3d3f0f7167..a26ee576bd 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -56,7 +56,7 @@ CxPlatDataPathInitialize( Datapath->UdpHandlers = *UdpCallbacks; } Datapath->WorkerPool = WorkerPool; - Datapath->XdpMapMode = TRUE; + Datapath->UseExternalXdpMaps = TRUE; *NewDataPath = Datapath; @@ -130,7 +130,7 @@ CxPlatDataPathUninitialize( if (Datapath->RawDataPath) { RawDataPathUninitialize(Datapath->RawDataPath); } - if (Datapath->XdpMapMode) { + if (Datapath->UseExternalXdpMaps) { // // Map mode: no platform (WinSock) datapath was initialized, // so free directly without platform uninit. diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index a21fac60c1..f9b8b01299 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -61,12 +61,6 @@ typedef struct CXPLAT_DATAPATH_COMMON { CXPLAT_DATAPATH_FEATURES Features; CXPLAT_DATAPATH_RAW* RawDataPath; - - // - // When TRUE, this struct is the entire datapath (no platform-specific - // fields). The raw (XDP) datapath owns all partition/IO state. - // - BOOLEAN XdpMapMode; } CXPLAT_DATAPATH_COMMON; typedef struct CXPLAT_SOCKET_COMMON { @@ -269,6 +263,8 @@ typedef struct CXPLAT_DATAPATH_PROC_CONTEXT { typedef struct CXPLAT_DATAPATH { CXPLAT_DATAPATH_COMMON; + BOOLEAN UseExternalXdpMaps; + // // The registration with WinSock Kernel. // @@ -479,6 +475,8 @@ typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_PROC { typedef struct CXPLAT_DATAPATH { CXPLAT_DATAPATH_COMMON; + BOOLEAN UseExternalXdpMaps; + // // Function pointer to AcceptEx. // @@ -997,6 +995,8 @@ typedef struct QUIC_CACHEALIGN CXPLAT_DATAPATH_PARTITION { typedef struct CXPLAT_DATAPATH { CXPLAT_DATAPATH_COMMON; + BOOLEAN UseExternalXdpMaps; + // // Synchronization mechanism for cleanup. // diff --git a/src/tools/recvfuzz/recvfuzz.cpp b/src/tools/recvfuzz/recvfuzz.cpp index 03b1cf1577..f5acc57dc8 100644 --- a/src/tools/recvfuzz/recvfuzz.cpp +++ b/src/tools/recvfuzz/recvfuzz.cpp @@ -1660,7 +1660,7 @@ void SetupAndFuzz() { CxPlatSystemLoad(); CxPlatInitialize(); - CXPLAT_DATAPATH* Datapath; + CXPLAT_DATAPATH* Datapath = NULL; const CXPLAT_UDP_DATAPATH_CALLBACKS DatapathCallbacks = { UdpRecvCallback, UdpUnreachCallback, From f68a1d874a0b2bc6d9dcc6cdf3be1a03afb18f0b Mon Sep 17 00:00:00 2001 From: Jack He Date: Wed, 20 May 2026 14:36:21 -0700 Subject: [PATCH 17/41] fix dp tests + update logging --- scripts/update-sidecar.ps1 | 9 +++- src/generated/linux/datapath_raw.c.clog.h | 8 +-- .../linux/datapath_raw.c.clog.h.lttng.h | 8 +-- .../linux/datapath_raw_xdp_win.c.clog.h | 42 ++++++++++++--- .../datapath_raw_xdp_win.c.clog.h.lttng.h | 51 +++++++++++++++++-- src/manifest/clog.sidecar | 47 +++++++++++++++-- src/platform/datapath_raw_xdp_win.c | 20 +++++--- src/test/lib/ApiTest.cpp | 7 +++ 8 files changed, 161 insertions(+), 31 deletions(-) diff --git a/scripts/update-sidecar.ps1 b/scripts/update-sidecar.ps1 index ed8dbf8b1a..a600c90b34 100644 --- a/scripts/update-sidecar.ps1 +++ b/scripts/update-sidecar.ps1 @@ -33,10 +33,15 @@ if (Test-Path $OutputDir) { } } +$TmpOutputDir = Join-Path $RootDir "build" "tmp" + +# Clean stale generated files so removed/renamed sources don't leave orphans +if (Test-Path $TmpOutputDir) { + Remove-Item $TmpOutputDir -Recurse -Force +} + $Sidecar = Join-Path $SrcDir "manifest" "clog.sidecar" $ConfigFile = Join-Path $SrcDir "manifest" "msquic.clog_config" - -$TmpOutputDir = Join-Path $RootDir "build" "tmp" $ClogDir = Join-Path $RootDir "build" "clog" # Create directories diff --git a/src/generated/linux/datapath_raw.c.clog.h b/src/generated/linux/datapath_raw.c.clog.h index cdea908e42..35e4abef69 100644 --- a/src/generated/linux/datapath_raw.c.clog.h +++ b/src/generated/linux/datapath_raw.c.clog.h @@ -45,10 +45,10 @@ tracepoint(CLOG_DATAPATH_RAW_C, AllocFailure , arg2, arg3);\ // Decoder Ring for LibraryErrorStatus // [ lib] ERROR, %u, %s. // QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - PlumbStatus, - "CxPlatDpRawPlumbRulesOnSocket (delete)"); + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + PlumbStatus, + "CxPlatDpRawPlumbRulesOnSocket (delete)"); // arg2 = arg2 = PlumbStatus = arg2 // arg3 = arg3 = "CxPlatDpRawPlumbRulesOnSocket (delete)" = arg3 ----------------------------------------------------------*/ diff --git a/src/generated/linux/datapath_raw.c.clog.h.lttng.h b/src/generated/linux/datapath_raw.c.clog.h.lttng.h index c268da76b4..4edb7a0fb6 100644 --- a/src/generated/linux/datapath_raw.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw.c.clog.h.lttng.h @@ -28,10 +28,10 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_C, AllocFailure, // Decoder Ring for LibraryErrorStatus // [ lib] ERROR, %u, %s. // QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - PlumbStatus, - "CxPlatDpRawPlumbRulesOnSocket (delete)"); + LibraryErrorStatus, + "[ lib] ERROR, %u, %s.", + PlumbStatus, + "CxPlatDpRawPlumbRulesOnSocket (delete)"); // arg2 = arg2 = PlumbStatus = arg2 // arg3 = arg3 = "CxPlatDpRawPlumbRulesOnSocket (delete)" = arg3 ----------------------------------------------------------*/ diff --git a/src/generated/linux/datapath_raw_xdp_win.c.clog.h b/src/generated/linux/datapath_raw_xdp_win.c.clog.h index 3e966180f9..27f35690ca 100644 --- a/src/generated/linux/datapath_raw_xdp_win.c.clog.h +++ b/src/generated/linux/datapath_raw_xdp_win.c.clog.h @@ -354,22 +354,52 @@ tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeConfigured , arg2, arg3, arg4) +/*---------------------------------------------------------- +// Decoder Ring for XdpMapInsertFailed +// [ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p +// QuicTraceLogVerbose( + XdpMapInsertFailed, + "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p", + Interface, + Interface->IfIndex, + j, + XskMap, + Queue->RxXsk); +// arg2 = arg2 = Interface = arg2 +// arg3 = arg3 = Interface->IfIndex = arg3 +// arg4 = arg4 = j = arg4 +// arg5 = arg5 = XskMap = arg5 +// arg6 = arg6 = Queue->RxXsk = arg6 +----------------------------------------------------------*/ +#ifndef _clog_7_ARGS_TRACE_XdpMapInsertFailed +#define _clog_7_ARGS_TRACE_XdpMapInsertFailed(uniqueId, encoded_arg_string, arg2, arg3, arg4, arg5, arg6)\ +tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapInsertFailed , arg2, arg3, arg4, arg5, arg6);\ + +#endif + + + + /*---------------------------------------------------------- // Decoder Ring for XdpMapModeInserted -// [ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u) +// [ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p) // QuicTraceLogVerbose( XdpMapModeInserted, - "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", + "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p)", Interface, j, - Interface->IfIndex); + Interface->IfIndex, + XskMap, + Queue->RxXsk); // arg2 = arg2 = Interface = arg2 // arg3 = arg3 = j = arg3 // arg4 = arg4 = Interface->IfIndex = arg4 +// arg5 = arg5 = XskMap = arg5 +// arg6 = arg6 = Queue->RxXsk = arg6 ----------------------------------------------------------*/ -#ifndef _clog_5_ARGS_TRACE_XdpMapModeInserted -#define _clog_5_ARGS_TRACE_XdpMapModeInserted(uniqueId, encoded_arg_string, arg2, arg3, arg4)\ -tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeInserted , arg2, arg3, arg4);\ +#ifndef _clog_7_ARGS_TRACE_XdpMapModeInserted +#define _clog_7_ARGS_TRACE_XdpMapModeInserted(uniqueId, encoded_arg_string, arg2, arg3, arg4, arg5, arg6)\ +tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeInserted , arg2, arg3, arg4, arg5, arg6);\ #endif diff --git a/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h b/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h index b0ad3c80d3..feea20f04b 100644 --- a/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h @@ -363,28 +363,71 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeConfigured, +/*---------------------------------------------------------- +// Decoder Ring for XdpMapInsertFailed +// [ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p +// QuicTraceLogVerbose( + XdpMapInsertFailed, + "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p", + Interface, + Interface->IfIndex, + j, + XskMap, + Queue->RxXsk); +// arg2 = arg2 = Interface = arg2 +// arg3 = arg3 = Interface->IfIndex = arg3 +// arg4 = arg4 = j = arg4 +// arg5 = arg5 = XskMap = arg5 +// arg6 = arg6 = Queue->RxXsk = arg6 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapInsertFailed, + TP_ARGS( + const void *, arg2, + unsigned int, arg3, + unsigned int, arg4, + const void *, arg5, + const void *, arg6), + TP_FIELDS( + ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2) + ctf_integer(unsigned int, arg3, arg3) + ctf_integer(unsigned int, arg4, arg4) + ctf_integer_hex(uint64_t, arg5, (uint64_t)arg5) + ctf_integer_hex(uint64_t, arg6, (uint64_t)arg6) + ) +) + + + /*---------------------------------------------------------- // Decoder Ring for XdpMapModeInserted -// [ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u) +// [ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p) // QuicTraceLogVerbose( XdpMapModeInserted, - "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", + "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p)", Interface, j, - Interface->IfIndex); + Interface->IfIndex, + XskMap, + Queue->RxXsk); // arg2 = arg2 = Interface = arg2 // arg3 = arg3 = j = arg3 // arg4 = arg4 = Interface->IfIndex = arg4 +// arg5 = arg5 = XskMap = arg5 +// arg6 = arg6 = Queue->RxXsk = arg6 ----------------------------------------------------------*/ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeInserted, TP_ARGS( const void *, arg2, unsigned int, arg3, - unsigned int, arg4), + unsigned int, arg4, + const void *, arg5, + const void *, arg6), TP_FIELDS( ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2) ctf_integer(unsigned int, arg3, arg3) ctf_integer(unsigned int, arg4, arg4) + ctf_integer_hex(uint64_t, arg5, (uint64_t)arg5) + ctf_integer_hex(uint64_t, arg6, (uint64_t)arg6) ) ) diff --git a/src/manifest/clog.sidecar b/src/manifest/clog.sidecar index 7de1d425c8..915004935c 100644 --- a/src/manifest/clog.sidecar +++ b/src/manifest/clog.sidecar @@ -12350,6 +12350,34 @@ ], "macroName": "QuicTraceLogVerbose" }, + "XdpMapInsertFailed": { + "ModuleProperites": {}, + "TraceString": "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p", + "UniqueId": "XdpMapInsertFailed", + "splitArgs": [ + { + "DefinationEncoding": "p", + "MacroVariableName": "arg2" + }, + { + "DefinationEncoding": "u", + "MacroVariableName": "arg3" + }, + { + "DefinationEncoding": "u", + "MacroVariableName": "arg4" + }, + { + "DefinationEncoding": "p", + "MacroVariableName": "arg5" + }, + { + "DefinationEncoding": "p", + "MacroVariableName": "arg6" + } + ], + "macroName": "QuicTraceLogVerbose" + }, "XdpMapModeConfigured": { "ModuleProperites": {}, "TraceString": "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", @@ -12372,7 +12400,7 @@ }, "XdpMapModeInserted": { "ModuleProperites": {}, - "TraceString": "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", + "TraceString": "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p)", "UniqueId": "XdpMapModeInserted", "splitArgs": [ { @@ -12386,6 +12414,14 @@ { "DefinationEncoding": "u", "MacroVariableName": "arg4" + }, + { + "DefinationEncoding": "p", + "MacroVariableName": "arg5" + }, + { + "DefinationEncoding": "p", + "MacroVariableName": "arg6" } ], "macroName": "QuicTraceLogVerbose" @@ -16931,15 +16967,20 @@ "TraceID": "XdpInterfaceQueues", "EncodingString": "[ixdp][%p] Initializing %u queues on interface" }, + { + "UniquenessHash": "c69cd3d4-9f76-1bd6-efc3-ccc85d8b3f9d", + "TraceID": "XdpMapInsertFailed", + "EncodingString": "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p" + }, { "UniquenessHash": "005c527e-db63-8dde-7829-b589b84080d9", "TraceID": "XdpMapModeConfigured", "EncodingString": "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)" }, { - "UniquenessHash": "ebd31a62-efa7-20b9-5d92-04e36ff534ba", + "UniquenessHash": "ef9c8310-696c-a60e-7004-f26815bf4101", "TraceID": "XdpMapModeInserted", - "EncodingString": "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)" + "EncodingString": "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p)" }, { "UniquenessHash": "18009e0d-e738-467e-35c0-55ebabcbf31f", diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index fff90c715c..8dfde47982 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -31,10 +31,6 @@ #define XDP_MAX_SYNC_WAIT_TIMEOUT_MS 1000 // Used for querying XDP RSS capabilities. -// -// Mirrors XDP's private XSKMAP_MAX_SIZE; keep in sync with the XDP submodule. -// -#define CXPLAT_XSKMAP_MAX_SIZE 128 typedef struct XDP_DATAPATH { CXPLAT_DATAPATH_RAW; @@ -470,8 +466,6 @@ CxPlatDpRawInterfaceInitialize( goto Error; } - CXPLAT_DBG_ASSERT(Interface->QueueCount <= CXPLAT_XSKMAP_MAX_SIZE); - if (Interface->QueueCount == 0) { Status = QUIC_STATUS_INVALID_STATE; QuicTraceEvent( @@ -2318,15 +2312,25 @@ CxPlatDpRawInsertXskByMapConfigs( "[ lib] ERROR, %u, %s.", Hr, "XdpMapInsert"); + QuicTraceLogVerbose( + XdpMapInsertFailed, + "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p", + Interface, + Interface->IfIndex, + j, + XskMap, + Queue->RxXsk); Status = (QUIC_STATUS)Hr; goto Exit; } QuicTraceLogVerbose( XdpMapModeInserted, - "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u)", + "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p)", Interface, j, - Interface->IfIndex); + Interface->IfIndex, + XskMap, + Queue->RxXsk); } break; } diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index f34e8cc1a0..ca977ce8ee 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -3177,9 +3177,16 @@ void QuicTestXdpMapConfigParam() // // Setting after a registration is created should fail. + // Clear fake configs first so lazy init does not try to use them. // { TestScopeLogger LogScope1("SetParam after registration fails"); + TEST_QUIC_SUCCEEDED( + MsQuic->SetParam( + nullptr, + QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG, + 0, + nullptr)); MsQuicRegistration Registration(true); TEST_TRUE(Registration.IsValid()); QUIC_XDP_MAP_CONFIG Config = { FakeIfIndex1, FakeHandle1 }; From d8fe2d2115f5ac04fdffd795b63129c370df376d Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 21 May 2026 13:47:17 -0700 Subject: [PATCH 18/41] infer xdp enablement if map mode is set --- src/inc/quic_datapath.h | 6 ++++ src/platform/datapath_raw_dummy.c | 6 ++++ src/platform/datapath_winuser.c | 30 +++++++++++++++++- src/platform/datapath_xplat.c | 51 +++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 3b683d0090..372b7b15af 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -486,6 +486,12 @@ typedef struct CXPLAT_DATAPATH_INIT_CONFIG { // XdpMapConfigs is NULL if and only if XdpMapConfigCount == 0. // The map configs must remain valid for the lifetime of the datapath. // + // N.B. XDP with/without maps is currently only supported on the winuser + // datapath. These fields are not compile-guarded, so a Linux/macOS app + // could set them, but RawDataPathInitialize will return NULL on + // non-Windows platforms (see datapath_raw_dummy.c) causing + // CxPlatDataPathInitialize to fail with QUIC_STATUS_NOT_SUPPORTED. + // const struct QUIC_XDP_MAP_CONFIG* XdpMapConfigs; uint32_t XdpMapConfigCount; } CXPLAT_DATAPATH_INIT_CONFIG; diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index e7b67c1eb2..c35fdd115f 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -93,6 +93,12 @@ CxPlatDpRawInsertXskByMapConfigs( _In_ uint32_t MapConfigCount ) { + // + // Stub for platforms without XDP support. This is the safety net that + // prevents XDP map mode from succeeding on non-Windows datapaths — even + // if an app sets QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG, RawDataPathInitialize + // above already returns NULL which fails map mode init before we get here. + // UNREFERENCED_PARAMETER(RawDataPath); UNREFERENCED_PARAMETER(MapConfigs); UNREFERENCED_PARAMETER(MapConfigCount); diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 87ba64a8cc..b5133a07dc 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1227,7 +1227,7 @@ SocketCreateUdp( int Result, Option; CXPLAT_DBG_ASSERT(Datapath->UdpHandlers.Receive != NULL || Config->Flags & CXPLAT_SOCKET_FLAG_PCP); - CXPLAT_DBG_ASSERT(IsServerSocket || Config->PartitionIndex < Datapath->PartitionCount); + CXPLAT_DBG_ASSERT(Datapath->UseExternalXdpMaps || IsServerSocket || Config->PartitionIndex < Datapath->PartitionCount); CXPLAT_DBG_ASSERT(Config->CibirIdLength <= sizeof(Config->CibirId)); const uint32_t RawSocketLength = CxPlatGetRawSocketSize() + SocketCount * sizeof(CXPLAT_SOCKET_PROC); @@ -1277,6 +1277,34 @@ SocketCreateUdp( MAX_URO_PAYLOAD_LENGTH : Socket->Mtu - CXPLAT_MIN_IPV4_HEADER_SIZE - CXPLAT_UDP_HEADER_SIZE; + if (Datapath->UseExternalXdpMaps) { + // + // XDP map mode: the WinSock datapath was not initialized (no WSAStartup, + // no IOCP, PartitionCount == 0), so skip OS socket creation entirely. + // Like CIBIR, an explicit local port is required since there is no OS + // socket to assign an ephemeral port. This check must come before the + // QTIP and CIBIR checks because it applies to all socket types in map + // mode — there are no OS sockets to fall back to regardless of flags. + // + // N.B. CxPlatSocketCreateUdp implicitly enables CreateRaw in map mode, + // so Config->Flags may not have CXPLAT_SOCKET_FLAG_XDP set by the app. + // + CXPLAT_DBG_ASSERT(Datapath->RawDataPath != NULL); + Socket->SkipCreatingOsSockets = TRUE; + CxPlatRefInitializeEx(&Socket->RefCount, 1); + if (Config->LocalAddress == NULL || Config->LocalAddress->Ipv4.sin_port == 0) { + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + (uint32_t)QUIC_STATUS_INVALID_PARAMETER, + "XDP map mode requires an explicit local port"); + Status = QUIC_STATUS_INVALID_PARAMETER; + goto Error; + } + goto Skip; + } + if (Socket->ReserveAuxTcpSockForQtip && !IsServerSocket) { // // QTIP clients will skip normal UDP socket reservation to use AuxSocket (TCP socket reservation) in raw socket. diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index a26ee576bd..a29809bb54 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -39,6 +39,16 @@ CxPlatDataPathInitialize( // any code traversing platform-specific fields sees safe defaults. // Only the common base fields and the raw datapath are used. // + // Important: XDP map mode has a hard dependency on the Windows (winuser) + // datapath and the Windows XDP raw datapath implementation. The platform + // socket creation path (SocketCreateUdp in datapath_winuser.c) checks + // UseExternalXdpMaps and skips OS socket creation, reusing the + // SkipCreatingOsSockets mechanism from the CIBIR path. On non-Windows + // platforms, RawDataPathInitialize is a no-op stub that returns NULL + // (datapath_raw_dummy.c), which causes the initialization below to fail + // gracefully. If XDP support is ever added to other platforms, the + // corresponding SocketCreateUdp implementation must also handle map mode. + // CXPLAT_DATAPATH* Datapath = CXPLAT_ALLOC_PAGED(sizeof(CXPLAT_DATAPATH), QUIC_POOL_DATAPATH); if (Datapath == NULL) { @@ -195,6 +205,47 @@ CxPlatSocketCreateUdp( QUIC_STATUS Status = QUIC_STATUS_SUCCESS; BOOLEAN CreateRaw = Config->Flags & CXPLAT_SOCKET_FLAG_XDP; + if (Datapath->UseExternalXdpMaps) { + // + // XDP map mode: there are no OS sockets — the raw (XDP) datapath is + // the only data path. Implicitly enable XDP for all sockets since the + // app already opted into XDP-only operation by setting map configs. + // Treat any raw socket failure as fatal (no OS fallback, no QTIP TCP + // port retry). + // + CreateRaw = TRUE; + + Status = + SocketCreateUdp( + Datapath, + Config, + NewSocket); + if (QUIC_FAILED(Status)) { + QuicTraceLogVerbose( + SockCreateFail, + "[sock] Failed to create socket, status:%d", Status); + goto Error; + } + + (*NewSocket)->RawSocketAvailable = 0; + CXPLAT_DBG_ASSERT(Datapath->RawDataPath != NULL); + Status = + RawSocketCreateUdp( + Datapath->RawDataPath, + Config, + CxPlatSocketToRaw(*NewSocket)); + if (QUIC_FAILED(Status)) { + QuicTraceLogVerbose( + RawSockCreateFail, + "[sock] Failed to create raw socket, status:%d", Status); + CxPlatSocketDelete(*NewSocket); + *NewSocket = NULL; + goto Error; + } + (*NewSocket)->RawSocketAvailable = TRUE; + goto Error; // Success path; Status is QUIC_STATUS_SUCCESS. + } + // // In a real production (XDP/QTIP+XDP) scenario, we never have to loop more than once // because server admins will ensure whatever port they are binding to is available. From 96ded75a5fa9cd13aa547b73833375b5157797b7 Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 21 May 2026 14:35:10 -0700 Subject: [PATCH 19/41] set xdpenabled to false explicitly in map mode returns error --- src/core/settings.c | 14 ++++++++++++++ src/generated/linux/datapath_xplat.c.clog.h | 4 ++-- .../linux/datapath_xplat.c.clog.h.lttng.h | 4 ++-- src/generated/linux/settings.c.clog.h | 16 ++++++++++++++++ src/generated/linux/settings.c.clog.h.lttng.h | 16 ++++++++++++++++ src/manifest/clog.sidecar | 12 ++++++++++++ src/platform/datapath_xplat.c | 6 +++--- 7 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/core/settings.c b/src/core/settings.c index 814aed882c..0535c3e9f6 100644 --- a/src/core/settings.c +++ b/src/core/settings.c @@ -717,6 +717,20 @@ QuicSettingApply( Destination->IsSet.XdpEnabled = TRUE; } + // + // If XDP map mode is active (XdpMapConfigCount > 0), XDP is implicitly + // enabled for all sockets. Reject an explicit XdpEnabled = FALSE since + // it contradicts map mode — there is no OS datapath to fall back to. + // + if (Destination->IsSet.XdpEnabled && + !Destination->XdpEnabled && + MsQuicLib.XdpMapConfigCount > 0) { + QuicTraceLogError( + SettingXdpDisabledInMapMode, + "[ lib] Error: XdpEnabled cannot be set to FALSE when XDP map mode is active."); + return FALSE; + } + if (Source->IsSet.QTIPEnabled && (!Destination->IsSet.QTIPEnabled || OverWrite)) { Destination->QTIPEnabled = Source->QTIPEnabled; Destination->IsSet.QTIPEnabled = TRUE; diff --git a/src/generated/linux/datapath_xplat.c.clog.h b/src/generated/linux/datapath_xplat.c.clog.h index 87a08f6aee..c626438eea 100644 --- a/src/generated/linux/datapath_xplat.c.clog.h +++ b/src/generated/linux/datapath_xplat.c.clog.h @@ -139,8 +139,8 @@ tracepoint(CLOG_DATAPATH_XPLAT_C, SockCreateFail , arg2);\ // Decoder Ring for RawSockCreateFail // [sock] Failed to create raw socket, status:%d // QuicTraceLogVerbose( - RawSockCreateFail, - "[sock] Failed to create raw socket, status:%d", Status); + RawSockCreateFail, + "[sock] Failed to create raw socket, status:%d", Status); // arg2 = arg2 = Status = arg2 ----------------------------------------------------------*/ #ifndef _clog_3_ARGS_TRACE_RawSockCreateFail diff --git a/src/generated/linux/datapath_xplat.c.clog.h.lttng.h b/src/generated/linux/datapath_xplat.c.clog.h.lttng.h index 5cfa611716..873cbdf9ea 100644 --- a/src/generated/linux/datapath_xplat.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_xplat.c.clog.h.lttng.h @@ -110,8 +110,8 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, SockCreateFail, // Decoder Ring for RawSockCreateFail // [sock] Failed to create raw socket, status:%d // QuicTraceLogVerbose( - RawSockCreateFail, - "[sock] Failed to create raw socket, status:%d", Status); + RawSockCreateFail, + "[sock] Failed to create raw socket, status:%d", Status); // arg2 = arg2 = Status = arg2 ----------------------------------------------------------*/ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, RawSockCreateFail, diff --git a/src/generated/linux/settings.c.clog.h b/src/generated/linux/settings.c.clog.h index 6d721ac673..c39e2a3b8d 100644 --- a/src/generated/linux/settings.c.clog.h +++ b/src/generated/linux/settings.c.clog.h @@ -872,6 +872,22 @@ tracepoint(CLOG_SETTINGS_C, SettingStreamMultiReceiveEnabled , arg2);\ +/*---------------------------------------------------------- +// Decoder Ring for SettingXdpDisabledInMapMode +// [ lib] Error: XdpEnabled cannot be set to FALSE when XDP map mode is active. +// QuicTraceLogError( + SettingXdpDisabledInMapMode, + "[ lib] Error: XdpEnabled cannot be set to FALSE when XDP map mode is active."); +----------------------------------------------------------*/ +#ifndef _clog_2_ARGS_TRACE_SettingXdpDisabledInMapMode +#define _clog_2_ARGS_TRACE_SettingXdpDisabledInMapMode(uniqueId, encoded_arg_string)\ +tracepoint(CLOG_SETTINGS_C, SettingXdpDisabledInMapMode );\ + +#endif + + + + /*---------------------------------------------------------- // Decoder Ring for SettingsLoadInvalidAcceptableVersion // Invalid AcceptableVersion loaded from storage! 0x%x at position %d diff --git a/src/generated/linux/settings.c.clog.h.lttng.h b/src/generated/linux/settings.c.clog.h.lttng.h index f7e15549d7..9dbe097f25 100644 --- a/src/generated/linux/settings.c.clog.h.lttng.h +++ b/src/generated/linux/settings.c.clog.h.lttng.h @@ -906,6 +906,22 @@ TRACEPOINT_EVENT(CLOG_SETTINGS_C, SettingStreamMultiReceiveEnabled, +/*---------------------------------------------------------- +// Decoder Ring for SettingXdpDisabledInMapMode +// [ lib] Error: XdpEnabled cannot be set to FALSE when XDP map mode is active. +// QuicTraceLogError( + SettingXdpDisabledInMapMode, + "[ lib] Error: XdpEnabled cannot be set to FALSE when XDP map mode is active."); +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_SETTINGS_C, SettingXdpDisabledInMapMode, + TP_ARGS( +), + TP_FIELDS( + ) +) + + + /*---------------------------------------------------------- // Decoder Ring for SettingsLoadInvalidAcceptableVersion // Invalid AcceptableVersion loaded from storage! 0x%x at position %d diff --git a/src/manifest/clog.sidecar b/src/manifest/clog.sidecar index 915004935c..7b2c94ef69 100644 --- a/src/manifest/clog.sidecar +++ b/src/manifest/clog.sidecar @@ -11017,6 +11017,13 @@ ], "macroName": "QuicTraceLogVerbose" }, + "SettingXdpDisabledInMapMode": { + "ModuleProperites": {}, + "TraceString": "[ lib] Error: XdpEnabled cannot be set to FALSE when XDP map mode is active.", + "UniqueId": "SettingXdpDisabledInMapMode", + "splitArgs": [], + "macroName": "QuicTraceLogError" + }, "SettingXdpEnabled": { "ModuleProperites": {}, "TraceString": "[sett] XdpEnabled = %hhu", @@ -16487,6 +16494,11 @@ "TraceID": "SettingStreamMultiReceiveEnabled", "EncodingString": "[sett] StreamMultiReceiveEnabled = %hhu" }, + { + "UniquenessHash": "45180046-2197-4f5c-6e93-f5791314c245", + "TraceID": "SettingXdpDisabledInMapMode", + "EncodingString": "[ lib] Error: XdpEnabled cannot be set to FALSE when XDP map mode is active." + }, { "UniquenessHash": "3bdc4807-1d2f-01f2-3c8c-043542720899", "TraceID": "SettingXdpEnabled", diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index a29809bb54..8792ea75d4 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -203,17 +203,15 @@ CxPlatSocketCreateUdp( ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - BOOLEAN CreateRaw = Config->Flags & CXPLAT_SOCKET_FLAG_XDP; if (Datapath->UseExternalXdpMaps) { // - // XDP map mode: there are no OS sockets — the raw (XDP) datapath is + // XDP map mode: there are no OS sockets -- the raw (XDP) datapath is // the only data path. Implicitly enable XDP for all sockets since the // app already opted into XDP-only operation by setting map configs. // Treat any raw socket failure as fatal (no OS fallback, no QTIP TCP // port retry). // - CreateRaw = TRUE; Status = SocketCreateUdp( @@ -246,6 +244,8 @@ CxPlatSocketCreateUdp( goto Error; // Success path; Status is QUIC_STATUS_SUCCESS. } + BOOLEAN CreateRaw = Config->Flags & CXPLAT_SOCKET_FLAG_XDP; + // // In a real production (XDP/QTIP+XDP) scenario, we never have to loop more than once // because server admins will ensure whatever port they are binding to is available. From 240f0d2548149e04c5b0be76d5f5ed4b34dfd880 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 11:36:45 -0700 Subject: [PATCH 20/41] fix unit test, address comments --- src/core/settings.c | 14 +-- .../linux/datapath_raw_xdp_win.c.clog.h | 74 +++++--------- .../datapath_raw_xdp_win.c.clog.h.lttng.h | 97 ++++++------------- src/inc/quic_datapath.h | 6 +- src/platform/datapath_raw_dummy.c | 6 +- src/platform/datapath_raw_xdp_win.c | 73 +++++++------- src/platform/datapath_winuser.c | 12 +-- src/platform/unittest/DataPathTest.cpp | 8 +- 8 files changed, 103 insertions(+), 187 deletions(-) diff --git a/src/core/settings.c b/src/core/settings.c index 0535c3e9f6..3a18af8034 100644 --- a/src/core/settings.c +++ b/src/core/settings.c @@ -712,18 +712,13 @@ QuicSettingApply( Destination->IsSet.ReliableResetEnabled = TRUE; } - if (Source->IsSet.XdpEnabled && (!Destination->IsSet.XdpEnabled || OverWrite)) { - Destination->XdpEnabled = Source->XdpEnabled; - Destination->IsSet.XdpEnabled = TRUE; - } - // // If XDP map mode is active (XdpMapConfigCount > 0), XDP is implicitly // enabled for all sockets. Reject an explicit XdpEnabled = FALSE since // it contradicts map mode — there is no OS datapath to fall back to. // - if (Destination->IsSet.XdpEnabled && - !Destination->XdpEnabled && + if (Source->IsSet.XdpEnabled && + !Source->XdpEnabled && MsQuicLib.XdpMapConfigCount > 0) { QuicTraceLogError( SettingXdpDisabledInMapMode, @@ -731,6 +726,11 @@ QuicSettingApply( return FALSE; } + if (Source->IsSet.XdpEnabled && (!Destination->IsSet.XdpEnabled || OverWrite)) { + Destination->XdpEnabled = Source->XdpEnabled; + Destination->IsSet.XdpEnabled = TRUE; + } + if (Source->IsSet.QTIPEnabled && (!Destination->IsSet.QTIPEnabled || OverWrite)) { Destination->QTIPEnabled = Source->QTIPEnabled; Destination->IsSet.QTIPEnabled = TRUE; diff --git a/src/generated/linux/datapath_raw_xdp_win.c.clog.h b/src/generated/linux/datapath_raw_xdp_win.c.clog.h index 27f35690ca..78f88ad254 100644 --- a/src/generated/linux/datapath_raw_xdp_win.c.clog.h +++ b/src/generated/linux/datapath_raw_xdp_win.c.clog.h @@ -354,58 +354,6 @@ tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeConfigured , arg2, arg3, arg4) -/*---------------------------------------------------------- -// Decoder Ring for XdpMapInsertFailed -// [ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p -// QuicTraceLogVerbose( - XdpMapInsertFailed, - "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p", - Interface, - Interface->IfIndex, - j, - XskMap, - Queue->RxXsk); -// arg2 = arg2 = Interface = arg2 -// arg3 = arg3 = Interface->IfIndex = arg3 -// arg4 = arg4 = j = arg4 -// arg5 = arg5 = XskMap = arg5 -// arg6 = arg6 = Queue->RxXsk = arg6 -----------------------------------------------------------*/ -#ifndef _clog_7_ARGS_TRACE_XdpMapInsertFailed -#define _clog_7_ARGS_TRACE_XdpMapInsertFailed(uniqueId, encoded_arg_string, arg2, arg3, arg4, arg5, arg6)\ -tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapInsertFailed , arg2, arg3, arg4, arg5, arg6);\ - -#endif - - - - -/*---------------------------------------------------------- -// Decoder Ring for XdpMapModeInserted -// [ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p) -// QuicTraceLogVerbose( - XdpMapModeInserted, - "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p)", - Interface, - j, - Interface->IfIndex, - XskMap, - Queue->RxXsk); -// arg2 = arg2 = Interface = arg2 -// arg3 = arg3 = j = arg3 -// arg4 = arg4 = Interface->IfIndex = arg4 -// arg5 = arg5 = XskMap = arg5 -// arg6 = arg6 = Queue->RxXsk = arg6 -----------------------------------------------------------*/ -#ifndef _clog_7_ARGS_TRACE_XdpMapModeInserted -#define _clog_7_ARGS_TRACE_XdpMapModeInserted(uniqueId, encoded_arg_string, arg2, arg3, arg4, arg5, arg6)\ -tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeInserted , arg2, arg3, arg4, arg5, arg6);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for LibraryErrorStatus // [ lib] ERROR, %u, %s. @@ -464,6 +412,28 @@ tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, LibraryError , arg2);\ +/*---------------------------------------------------------- +// Decoder Ring for DatapathErrorStatus +// [data][%p] ERROR, %u, %s. +// QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Interface, + Hr, + "XdpMapInsert"); +// arg2 = arg2 = Interface = arg2 +// arg3 = arg3 = Hr = arg3 +// arg4 = arg4 = "XdpMapInsert" = arg4 +----------------------------------------------------------*/ +#ifndef _clog_5_ARGS_TRACE_DatapathErrorStatus +#define _clog_5_ARGS_TRACE_DatapathErrorStatus(uniqueId, encoded_arg_string, arg2, arg3, arg4)\ +tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, DatapathErrorStatus , arg2, arg3, arg4);\ + +#endif + + + + #ifdef __cplusplus } #endif diff --git a/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h b/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h index feea20f04b..461029dc9a 100644 --- a/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h @@ -363,76 +363,6 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeConfigured, -/*---------------------------------------------------------- -// Decoder Ring for XdpMapInsertFailed -// [ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p -// QuicTraceLogVerbose( - XdpMapInsertFailed, - "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p", - Interface, - Interface->IfIndex, - j, - XskMap, - Queue->RxXsk); -// arg2 = arg2 = Interface = arg2 -// arg3 = arg3 = Interface->IfIndex = arg3 -// arg4 = arg4 = j = arg4 -// arg5 = arg5 = XskMap = arg5 -// arg6 = arg6 = Queue->RxXsk = arg6 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapInsertFailed, - TP_ARGS( - const void *, arg2, - unsigned int, arg3, - unsigned int, arg4, - const void *, arg5, - const void *, arg6), - TP_FIELDS( - ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2) - ctf_integer(unsigned int, arg3, arg3) - ctf_integer(unsigned int, arg4, arg4) - ctf_integer_hex(uint64_t, arg5, (uint64_t)arg5) - ctf_integer_hex(uint64_t, arg6, (uint64_t)arg6) - ) -) - - - -/*---------------------------------------------------------- -// Decoder Ring for XdpMapModeInserted -// [ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p) -// QuicTraceLogVerbose( - XdpMapModeInserted, - "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p)", - Interface, - j, - Interface->IfIndex, - XskMap, - Queue->RxXsk); -// arg2 = arg2 = Interface = arg2 -// arg3 = arg3 = j = arg3 -// arg4 = arg4 = Interface->IfIndex = arg4 -// arg5 = arg5 = XskMap = arg5 -// arg6 = arg6 = Queue->RxXsk = arg6 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapModeInserted, - TP_ARGS( - const void *, arg2, - unsigned int, arg3, - unsigned int, arg4, - const void *, arg5, - const void *, arg6), - TP_FIELDS( - ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2) - ctf_integer(unsigned int, arg3, arg3) - ctf_integer(unsigned int, arg4, arg4) - ctf_integer_hex(uint64_t, arg5, (uint64_t)arg5) - ctf_integer_hex(uint64_t, arg6, (uint64_t)arg6) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for LibraryErrorStatus // [ lib] ERROR, %u, %s. @@ -495,3 +425,30 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, LibraryError, ctf_string(arg2, arg2) ) ) + + + +/*---------------------------------------------------------- +// Decoder Ring for DatapathErrorStatus +// [data][%p] ERROR, %u, %s. +// QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Interface, + Hr, + "XdpMapInsert"); +// arg2 = arg2 = Interface = arg2 +// arg3 = arg3 = Hr = arg3 +// arg4 = arg4 = "XdpMapInsert" = arg4 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, DatapathErrorStatus, + TP_ARGS( + const void *, arg2, + unsigned int, arg3, + const char *, arg4), + TP_FIELDS( + ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2) + ctf_integer(unsigned int, arg3, arg3) + ctf_string(arg4, arg4) + ) +) diff --git a/src/inc/quic_datapath.h b/src/inc/quic_datapath.h index 372b7b15af..0c4757da30 100644 --- a/src/inc/quic_datapath.h +++ b/src/inc/quic_datapath.h @@ -486,11 +486,7 @@ typedef struct CXPLAT_DATAPATH_INIT_CONFIG { // XdpMapConfigs is NULL if and only if XdpMapConfigCount == 0. // The map configs must remain valid for the lifetime of the datapath. // - // N.B. XDP with/without maps is currently only supported on the winuser - // datapath. These fields are not compile-guarded, so a Linux/macOS app - // could set them, but RawDataPathInitialize will return NULL on - // non-Windows platforms (see datapath_raw_dummy.c) causing - // CxPlatDataPathInitialize to fail with QUIC_STATUS_NOT_SUPPORTED. + // N.B. Currently only supported for Windows user-mode. // const struct QUIC_XDP_MAP_CONFIG* XdpMapConfigs; uint32_t XdpMapConfigCount; diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index c35fdd115f..26e6a8f07c 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -94,10 +94,8 @@ CxPlatDpRawInsertXskByMapConfigs( ) { // - // Stub for platforms without XDP support. This is the safety net that - // prevents XDP map mode from succeeding on non-Windows datapaths — even - // if an app sets QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG, RawDataPathInitialize - // above already returns NULL which fails map mode init before we get here. + // Stub for platforms without XDP support. Must return QUIC_STATUS_NOT_SUPPORTED + // so map mode init fails gracefully on non-Windows platforms. // UNREFERENCED_PARAMETER(RawDataPath); UNREFERENCED_PARAMETER(MapConfigs); diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 8dfde47982..a5b39b9e46 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -2275,6 +2275,33 @@ CxPlatDataPathRssConfigFree( CXPLAT_FREE(RssConfig, QUIC_POOL_DATAPATH_RSS_CONFIG); } +_IRQL_requires_max_(PASSIVE_LEVEL) +static +QUIC_STATUS +CxPlatDpRawInsertXskInMap( + _In_ XDP_INTERFACE* Interface, + _In_ HANDLE XskMap + ) +{ + for (uint32_t j = 0; j < Interface->QueueCount; j++) { + CXPLAT_QUEUE* Queue = &Interface->Queues[j]; + if (Queue->RxXsk == NULL) { + continue; + } + HRESULT Hr = XdpMapInsert(XskMap, &j, &Queue->RxXsk); + if (FAILED(Hr)) { + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Interface, + Hr, + "XdpMapInsert"); + return (QUIC_STATUS)Hr; + } + } + return QUIC_STATUS_SUCCESS; +} + _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS CxPlatDpRawInsertXskByMapConfigs( @@ -2289,50 +2316,26 @@ CxPlatDpRawInsertXskByMapConfigs( CXPLAT_LIST_ENTRY* Entry; for (Entry = Xdp->Interfaces.Flink; Entry != &Xdp->Interfaces; Entry = Entry->Flink) { XDP_INTERFACE* Interface = CONTAINING_RECORD(Entry, XDP_INTERFACE, Link); + + HANDLE XskMap = NULL; for (uint32_t i = 0; i < MapConfigCount; i++) { - if (MapConfigs[i].InterfaceIndex != Interface->IfIndex) { - continue; + if (MapConfigs[i].InterfaceIndex == Interface->IfIndex) { + XskMap = (HANDLE)MapConfigs[i].MapHandle; + break; } - HANDLE XskMap = (HANDLE)MapConfigs[i].MapHandle; + } + + if (XskMap != NULL) { QuicTraceLogVerbose( XdpMapModeConfigured, "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", Interface, Interface->IfIndex, XskMap); - for (uint32_t j = 0; j < Interface->QueueCount; j++) { - CXPLAT_QUEUE* Queue = &Interface->Queues[j]; - if (Queue->RxXsk == NULL) { - continue; - } - HRESULT Hr = XdpMapInsert(XskMap, &j, &Queue->RxXsk); - if (FAILED(Hr)) { - QuicTraceEvent( - LibraryErrorStatus, - "[ lib] ERROR, %u, %s.", - Hr, - "XdpMapInsert"); - QuicTraceLogVerbose( - XdpMapInsertFailed, - "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p", - Interface, - Interface->IfIndex, - j, - XskMap, - Queue->RxXsk); - Status = (QUIC_STATUS)Hr; - goto Exit; - } - QuicTraceLogVerbose( - XdpMapModeInserted, - "[ixdp][%p] Map mode: inserted XSK for queue %u (IfIndex=%u, XskMap=%p, RxXsk=%p)", - Interface, - j, - Interface->IfIndex, - XskMap, - Queue->RxXsk); + Status = CxPlatDpRawInsertXskInMap(Interface, XskMap); + if (QUIC_FAILED(Status)) { + goto Exit; } - break; } } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index b5133a07dc..1285f51b4a 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1279,15 +1279,9 @@ SocketCreateUdp( if (Datapath->UseExternalXdpMaps) { // - // XDP map mode: the WinSock datapath was not initialized (no WSAStartup, - // no IOCP, PartitionCount == 0), so skip OS socket creation entirely. - // Like CIBIR, an explicit local port is required since there is no OS - // socket to assign an ephemeral port. This check must come before the - // QTIP and CIBIR checks because it applies to all socket types in map - // mode — there are no OS sockets to fall back to regardless of flags. - // - // N.B. CxPlatSocketCreateUdp implicitly enables CreateRaw in map mode, - // so Config->Flags may not have CXPLAT_SOCKET_FLAG_XDP set by the app. + // There is no OS native datapath when XDP maps are used. The + // application must specify the local port. Skip OS socket creation + // entirely and defer to the raw (XDP) datapath. // CXPLAT_DBG_ASSERT(Datapath->RawDataPath != NULL); Socket->SkipCreatingOsSockets = TRUE; diff --git a/src/platform/unittest/DataPathTest.cpp b/src/platform/unittest/DataPathTest.cpp index 936d55834e..1cbc4c605c 100644 --- a/src/platform/unittest/DataPathTest.cpp +++ b/src/platform/unittest/DataPathTest.cpp @@ -1622,6 +1622,7 @@ TEST_F(DataPathTest, XdpMapMode_SocketSkipsRulePlumbing) // socket in map mode and verify that create and delete succeed without // the rule plumbing step. // + if (!UseDuoNic) { GTEST_SKIP_NO_RETURN_("Requires DuoNic/XDP for raw datapath init"); return; @@ -1653,15 +1654,12 @@ TEST_F(DataPathTest, XdpMapMode_SocketSkipsRulePlumbing) VERIFY_QUIC_SUCCESS(Status); ASSERT_NE(nullptr, Datapath); - // - // Create a connected QTIP+XDP socket. QTIP skips the normal OS UDP - // socket creation and goes directly to RawSocketCreateUdp, which - // should skip rule plumbing in map mode. - // QuicAddr RemoteAddr = GetNewLocalIPv4(); + QuicAddr LocalAddr = GetNewLocalIPv4(); CXPLAT_UDP_CONFIG UdpConfig = {0}; UdpConfig.RemoteAddress = &RemoteAddr.SockAddr; + UdpConfig.LocalAddress = &LocalAddr.SockAddr; UdpConfig.Flags = CXPLAT_SOCKET_FLAG_XDP | CXPLAT_SOCKET_FLAG_QTIP; CXPLAT_SOCKET* Socket = nullptr; From 1559660d1ce6ef9d58b76812ff264e2e3794fefe Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 11:53:51 -0700 Subject: [PATCH 21/41] generate clog, fix code pattern --- .../linux/datapath_raw_xdp_win.c.clog.h | 58 ++++++++------- .../datapath_raw_xdp_win.c.clog.h.lttng.h | 72 ++++++++++--------- src/platform/datapath_raw_xdp_win.c | 38 +++++----- 3 files changed, 89 insertions(+), 79 deletions(-) diff --git a/src/generated/linux/datapath_raw_xdp_win.c.clog.h b/src/generated/linux/datapath_raw_xdp_win.c.clog.h index 78f88ad254..1f0d10daac 100644 --- a/src/generated/linux/datapath_raw_xdp_win.c.clog.h +++ b/src/generated/linux/datapath_raw_xdp_win.c.clog.h @@ -333,14 +333,40 @@ tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpPartitionShutdownComplete , arg2);\ /*---------------------------------------------------------- -// Decoder Ring for XdpMapModeConfigured -// [ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p) +// Decoder Ring for XdpMapInsertFailed +// [ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p // QuicTraceLogVerbose( - XdpMapModeConfigured, - "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", + XdpMapInsertFailed, + "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p", Interface, Interface->IfIndex, - XskMap); + j, + XskMap, + Queue->RxXsk); +// arg2 = arg2 = Interface = arg2 +// arg3 = arg3 = Interface->IfIndex = arg3 +// arg4 = arg4 = j = arg4 +// arg5 = arg5 = XskMap = arg5 +// arg6 = arg6 = Queue->RxXsk = arg6 +----------------------------------------------------------*/ +#ifndef _clog_7_ARGS_TRACE_XdpMapInsertFailed +#define _clog_7_ARGS_TRACE_XdpMapInsertFailed(uniqueId, encoded_arg_string, arg2, arg3, arg4, arg5, arg6)\ +tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapInsertFailed , arg2, arg3, arg4, arg5, arg6);\ + +#endif + + + + +/*---------------------------------------------------------- +// Decoder Ring for XdpMapModeConfigured +// [ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p) +// QuicTraceLogVerbose( + XdpMapModeConfigured, + "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", + Interface, + Interface->IfIndex, + XskMap); // arg2 = arg2 = Interface = arg2 // arg3 = arg3 = Interface->IfIndex = arg3 // arg4 = arg4 = XskMap = arg4 @@ -412,28 +438,6 @@ tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, LibraryError , arg2);\ -/*---------------------------------------------------------- -// Decoder Ring for DatapathErrorStatus -// [data][%p] ERROR, %u, %s. -// QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Interface, - Hr, - "XdpMapInsert"); -// arg2 = arg2 = Interface = arg2 -// arg3 = arg3 = Hr = arg3 -// arg4 = arg4 = "XdpMapInsert" = arg4 -----------------------------------------------------------*/ -#ifndef _clog_5_ARGS_TRACE_DatapathErrorStatus -#define _clog_5_ARGS_TRACE_DatapathErrorStatus(uniqueId, encoded_arg_string, arg2, arg3, arg4)\ -tracepoint(CLOG_DATAPATH_RAW_XDP_WIN_C, DatapathErrorStatus , arg2, arg3, arg4);\ - -#endif - - - - #ifdef __cplusplus } #endif diff --git a/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h b/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h index 461029dc9a..c4bc81b8f3 100644 --- a/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw_xdp_win.c.clog.h.lttng.h @@ -337,14 +337,49 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpPartitionShutdownComplete, /*---------------------------------------------------------- -// Decoder Ring for XdpMapModeConfigured -// [ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p) +// Decoder Ring for XdpMapInsertFailed +// [ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p // QuicTraceLogVerbose( - XdpMapModeConfigured, - "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", + XdpMapInsertFailed, + "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p", Interface, Interface->IfIndex, - XskMap); + j, + XskMap, + Queue->RxXsk); +// arg2 = arg2 = Interface = arg2 +// arg3 = arg3 = Interface->IfIndex = arg3 +// arg4 = arg4 = j = arg4 +// arg5 = arg5 = XskMap = arg5 +// arg6 = arg6 = Queue->RxXsk = arg6 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, XdpMapInsertFailed, + TP_ARGS( + const void *, arg2, + unsigned int, arg3, + unsigned int, arg4, + const void *, arg5, + const void *, arg6), + TP_FIELDS( + ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2) + ctf_integer(unsigned int, arg3, arg3) + ctf_integer(unsigned int, arg4, arg4) + ctf_integer_hex(uint64_t, arg5, (uint64_t)arg5) + ctf_integer_hex(uint64_t, arg6, (uint64_t)arg6) + ) +) + + + +/*---------------------------------------------------------- +// Decoder Ring for XdpMapModeConfigured +// [ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p) +// QuicTraceLogVerbose( + XdpMapModeConfigured, + "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", + Interface, + Interface->IfIndex, + XskMap); // arg2 = arg2 = Interface = arg2 // arg3 = arg3 = Interface->IfIndex = arg3 // arg4 = arg4 = XskMap = arg4 @@ -425,30 +460,3 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, LibraryError, ctf_string(arg2, arg2) ) ) - - - -/*---------------------------------------------------------- -// Decoder Ring for DatapathErrorStatus -// [data][%p] ERROR, %u, %s. -// QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", - Interface, - Hr, - "XdpMapInsert"); -// arg2 = arg2 = Interface = arg2 -// arg3 = arg3 = Hr = arg3 -// arg4 = arg4 = "XdpMapInsert" = arg4 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_XDP_WIN_C, DatapathErrorStatus, - TP_ARGS( - const void *, arg2, - unsigned int, arg3, - const char *, arg4), - TP_FIELDS( - ctf_integer_hex(uint64_t, arg2, (uint64_t)arg2) - ctf_integer(unsigned int, arg3, arg3) - ctf_string(arg4, arg4) - ) -) diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index a5b39b9e46..75b40698fe 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -2290,12 +2290,14 @@ CxPlatDpRawInsertXskInMap( } HRESULT Hr = XdpMapInsert(XskMap, &j, &Queue->RxXsk); if (FAILED(Hr)) { - QuicTraceEvent( - DatapathErrorStatus, - "[data][%p] ERROR, %u, %s.", + QuicTraceLogVerbose( + XdpMapInsertFailed, + "[ixdp][%p] XdpMapInsert failed for IfIndex=%u, QueueId=%u, XskMap=%p, RxXsk=%p", Interface, - Hr, - "XdpMapInsert"); + Interface->IfIndex, + j, + XskMap, + Queue->RxXsk); return (QUIC_STATUS)Hr; } } @@ -2317,26 +2319,22 @@ CxPlatDpRawInsertXskByMapConfigs( for (Entry = Xdp->Interfaces.Flink; Entry != &Xdp->Interfaces; Entry = Entry->Flink) { XDP_INTERFACE* Interface = CONTAINING_RECORD(Entry, XDP_INTERFACE, Link); - HANDLE XskMap = NULL; for (uint32_t i = 0; i < MapConfigCount; i++) { if (MapConfigs[i].InterfaceIndex == Interface->IfIndex) { - XskMap = (HANDLE)MapConfigs[i].MapHandle; + HANDLE XskMap = (HANDLE)MapConfigs[i].MapHandle; + Status = CxPlatDpRawInsertXskInMap(Interface, XskMap); + if (QUIC_FAILED(Status)) { + goto Exit; + } + QuicTraceLogVerbose( + XdpMapModeConfigured, + "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", + Interface, + Interface->IfIndex, + XskMap); break; } } - - if (XskMap != NULL) { - QuicTraceLogVerbose( - XdpMapModeConfigured, - "[ixdp][%p] Map mode configured for IfIndex=%u (MapHandle=%p)", - Interface, - Interface->IfIndex, - XskMap); - Status = CxPlatDpRawInsertXskInMap(Interface, XskMap); - if (QUIC_FAILED(Status)) { - goto Exit; - } - } } Exit: From 892304cb8ad27e9145917e60843130e71a394217 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 12:44:34 -0700 Subject: [PATCH 22/41] update comment --- src/platform/datapath_xplat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index 8792ea75d4..b2dfbc730a 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -47,7 +47,7 @@ CxPlatDataPathInitialize( // platforms, RawDataPathInitialize is a no-op stub that returns NULL // (datapath_raw_dummy.c), which causes the initialization below to fail // gracefully. If XDP support is ever added to other platforms, the - // corresponding SocketCreateUdp implementation must also handle map mode. + // corresponding SocketCreateUdp implementation needs to be aware of map mode. // CXPLAT_DATAPATH* Datapath = CXPLAT_ALLOC_PAGED(sizeof(CXPLAT_DATAPATH), QUIC_POOL_DATAPATH); From 1388f07b8cca4dc2e7269ee808f2809b93229276 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 12:57:26 -0700 Subject: [PATCH 23/41] put map mode flag in rawdatapath --- src/platform/datapath_raw.c | 16 ++++++++++++++++ src/platform/datapath_raw.h | 1 + src/platform/datapath_raw_dummy.c | 9 +++++++++ src/platform/datapath_raw_xdp_win.c | 2 +- src/platform/datapath_winuser.c | 4 ++-- src/platform/datapath_xplat.c | 12 +++++++----- src/platform/platform_internal.h | 13 ++++++++++--- 7 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/platform/datapath_raw.c b/src/platform/datapath_raw.c index 99d0ec67e1..37cc05543b 100644 --- a/src/platform/datapath_raw.c +++ b/src/platform/datapath_raw.c @@ -86,6 +86,22 @@ RawDataPathInitialize( } } +BOOLEAN +CxPlatDpRawIsExternalXdpMapMode( + _In_opt_ const CXPLAT_DATAPATH_RAW* RawDataPath + ) +{ + return RawDataPath != NULL && RawDataPath->UseExternalXdpMaps; +} + +void +CxPlatDpRawSetExternalXdpMapMode( + _In_ CXPLAT_DATAPATH_RAW* RawDataPath + ) +{ + RawDataPath->UseExternalXdpMaps = TRUE; +} + _IRQL_requires_max_(PASSIVE_LEVEL) void RawDataPathUninitialize( diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 8999779f7e..fffca30273 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -63,6 +63,7 @@ typedef struct CXPLAT_DATAPATH_RAW { BOOLEAN Freed : 1; #endif BOOLEAN ReserveAuxTcpSockForQtip; // Whether or not we create an auxiliary TCP socket. + BOOLEAN UseExternalXdpMaps; // XDP map mode: app manages XDP rules externally. } CXPLAT_DATAPATH_RAW; diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index 26e6a8f07c..b25fad87f4 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -74,6 +74,15 @@ RawDataPathUninitialize( UNREFERENCED_PARAMETER(Datapath); } +BOOLEAN +CxPlatDpRawIsExternalXdpMapMode( + _In_opt_ const CXPLAT_DATAPATH_RAW* RawDataPath + ) +{ + UNREFERENCED_PARAMETER(RawDataPath); + return FALSE; // Dummy raw datapath never has XDP map mode. +} + _IRQL_requires_max_(PASSIVE_LEVEL) void RawDataPathUpdatePollingIdleTimeout( diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 75b40698fe..92cadf0c75 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -1472,7 +1472,7 @@ CxPlatDpRawPlumbRulesOnSocket( // // In external map mode, the caller manages XDP rules; nothing to do here. // - if (Xdp->ParentDataPath->UseExternalXdpMaps) { + if (Xdp->UseExternalXdpMaps) { return QUIC_STATUS_SUCCESS; } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 1285f51b4a..c6f4637fd6 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1227,7 +1227,7 @@ SocketCreateUdp( int Result, Option; CXPLAT_DBG_ASSERT(Datapath->UdpHandlers.Receive != NULL || Config->Flags & CXPLAT_SOCKET_FLAG_PCP); - CXPLAT_DBG_ASSERT(Datapath->UseExternalXdpMaps || IsServerSocket || Config->PartitionIndex < Datapath->PartitionCount); + CXPLAT_DBG_ASSERT(CxPlatDpRawIsExternalXdpMapMode(Datapath->RawDataPath) || IsServerSocket || Config->PartitionIndex < Datapath->PartitionCount); CXPLAT_DBG_ASSERT(Config->CibirIdLength <= sizeof(Config->CibirId)); const uint32_t RawSocketLength = CxPlatGetRawSocketSize() + SocketCount * sizeof(CXPLAT_SOCKET_PROC); @@ -1277,7 +1277,7 @@ SocketCreateUdp( MAX_URO_PAYLOAD_LENGTH : Socket->Mtu - CXPLAT_MIN_IPV4_HEADER_SIZE - CXPLAT_UDP_HEADER_SIZE; - if (Datapath->UseExternalXdpMaps) { + if (CxPlatDpRawIsExternalXdpMapMode(Datapath->RawDataPath)) { // // There is no OS native datapath when XDP maps are used. The // application must specify the local port. Skip OS socket creation diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index b2dfbc730a..cbc9e7f1c5 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -42,8 +42,8 @@ CxPlatDataPathInitialize( // Important: XDP map mode has a hard dependency on the Windows (winuser) // datapath and the Windows XDP raw datapath implementation. The platform // socket creation path (SocketCreateUdp in datapath_winuser.c) checks - // UseExternalXdpMaps and skips OS socket creation, reusing the - // SkipCreatingOsSockets mechanism from the CIBIR path. On non-Windows + // UseExternalXdpMaps (on the raw datapath) and skips OS socket creation, + // reusing the SkipCreatingOsSockets mechanism from the CIBIR path. On non-Windows // platforms, RawDataPathInitialize is a no-op stub that returns NULL // (datapath_raw_dummy.c), which causes the initialization below to fail // gracefully. If XDP support is ever added to other platforms, the @@ -66,7 +66,6 @@ CxPlatDataPathInitialize( Datapath->UdpHandlers = *UdpCallbacks; } Datapath->WorkerPool = WorkerPool; - Datapath->UseExternalXdpMaps = TRUE; *NewDataPath = Datapath; @@ -85,6 +84,8 @@ CxPlatDataPathInitialize( goto Error; } + CxPlatDpRawSetExternalXdpMapMode(Datapath->RawDataPath); + Status = CxPlatDpRawInsertXskByMapConfigs( Datapath->RawDataPath, @@ -137,10 +138,11 @@ CxPlatDataPathUninitialize( _In_ CXPLAT_DATAPATH* Datapath ) { + BOOLEAN IsMapMode = CxPlatDpRawIsExternalXdpMapMode(Datapath->RawDataPath); if (Datapath->RawDataPath) { RawDataPathUninitialize(Datapath->RawDataPath); } - if (Datapath->UseExternalXdpMaps) { + if (IsMapMode) { // // Map mode: no platform (WinSock) datapath was initialized, // so free directly without platform uninit. @@ -204,7 +206,7 @@ CxPlatSocketCreateUdp( { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - if (Datapath->UseExternalXdpMaps) { + if (CxPlatDpRawIsExternalXdpMapMode(Datapath->RawDataPath)) { // // XDP map mode: there are no OS sockets -- the raw (XDP) datapath is // the only data path. Implicitly enable XDP for all sockets since the diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index f9b8b01299..3da4a26d2c 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -263,7 +263,6 @@ typedef struct CXPLAT_DATAPATH_PROC_CONTEXT { typedef struct CXPLAT_DATAPATH { CXPLAT_DATAPATH_COMMON; - BOOLEAN UseExternalXdpMaps; // // The registration with WinSock Kernel. @@ -475,7 +474,6 @@ typedef struct QUIC_CACHEALIGN CXPLAT_SOCKET_PROC { typedef struct CXPLAT_DATAPATH { CXPLAT_DATAPATH_COMMON; - BOOLEAN UseExternalXdpMaps; // // Function pointer to AcceptEx. @@ -995,7 +993,6 @@ typedef struct QUIC_CACHEALIGN CXPLAT_DATAPATH_PARTITION { typedef struct CXPLAT_DATAPATH { CXPLAT_DATAPATH_COMMON; - BOOLEAN UseExternalXdpMaps; // // Synchronization mechanism for cleanup. @@ -1250,6 +1247,16 @@ RawDataPathIsPaddingPreferred( _In_ CXPLAT_DATAPATH* Datapath ); +BOOLEAN +CxPlatDpRawIsExternalXdpMapMode( + _In_opt_ const CXPLAT_DATAPATH_RAW* RawDataPath + ); + +void +CxPlatDpRawSetExternalXdpMapMode( + _In_ CXPLAT_DATAPATH_RAW* RawDataPath + ); + _IRQL_requires_max_(PASSIVE_LEVEL) QUIC_STATUS RawSocketUpdateQeo( From c472280ed5d5d8f89e2dcb3f4b0a9fee2521ef8f Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 13:01:30 -0700 Subject: [PATCH 24/41] rename --- src/platform/datapath_raw.c | 2 +- src/platform/datapath_xplat.c | 2 +- src/platform/platform_internal.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/datapath_raw.c b/src/platform/datapath_raw.c index 37cc05543b..fa7b9d94e7 100644 --- a/src/platform/datapath_raw.c +++ b/src/platform/datapath_raw.c @@ -95,7 +95,7 @@ CxPlatDpRawIsExternalXdpMapMode( } void -CxPlatDpRawSetExternalXdpMapMode( +CxPlatDpRawEnableExternalXdpMapMode( _In_ CXPLAT_DATAPATH_RAW* RawDataPath ) { diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index cbc9e7f1c5..ff1def2284 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -84,7 +84,7 @@ CxPlatDataPathInitialize( goto Error; } - CxPlatDpRawSetExternalXdpMapMode(Datapath->RawDataPath); + CxPlatDpRawEnableExternalXdpMapMode(Datapath->RawDataPath); Status = CxPlatDpRawInsertXskByMapConfigs( diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 3da4a26d2c..0ed63ff6d4 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -1253,7 +1253,7 @@ CxPlatDpRawIsExternalXdpMapMode( ); void -CxPlatDpRawSetExternalXdpMapMode( +CxPlatDpRawEnableExternalXdpMapMode( _In_ CXPLAT_DATAPATH_RAW* RawDataPath ); From acc113bb6244e2ed48a77b1977752b5c2416da86 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 13:25:17 -0700 Subject: [PATCH 25/41] simplify the code --- src/platform/datapath_xplat.c | 59 +++++++++++------------------------ 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index ff1def2284..09ef457e58 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -205,48 +205,14 @@ CxPlatSocketCreateUdp( ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; + BOOLEAN IsMapMode = CxPlatDpRawIsExternalXdpMapMode(Datapath->RawDataPath); - if (CxPlatDpRawIsExternalXdpMapMode(Datapath->RawDataPath)) { - // - // XDP map mode: there are no OS sockets -- the raw (XDP) datapath is - // the only data path. Implicitly enable XDP for all sockets since the - // app already opted into XDP-only operation by setting map configs. - // Treat any raw socket failure as fatal (no OS fallback, no QTIP TCP - // port retry). - // - - Status = - SocketCreateUdp( - Datapath, - Config, - NewSocket); - if (QUIC_FAILED(Status)) { - QuicTraceLogVerbose( - SockCreateFail, - "[sock] Failed to create socket, status:%d", Status); - goto Error; - } - - (*NewSocket)->RawSocketAvailable = 0; - CXPLAT_DBG_ASSERT(Datapath->RawDataPath != NULL); - Status = - RawSocketCreateUdp( - Datapath->RawDataPath, - Config, - CxPlatSocketToRaw(*NewSocket)); - if (QUIC_FAILED(Status)) { - QuicTraceLogVerbose( - RawSockCreateFail, - "[sock] Failed to create raw socket, status:%d", Status); - CxPlatSocketDelete(*NewSocket); - *NewSocket = NULL; - goto Error; - } - (*NewSocket)->RawSocketAvailable = TRUE; - goto Error; // Success path; Status is QUIC_STATUS_SUCCESS. - } - - BOOLEAN CreateRaw = Config->Flags & CXPLAT_SOCKET_FLAG_XDP; + // + // In map mode the raw (XDP) datapath is the only data path. Implicitly + // enable XDP for all sockets and treat any raw socket failure as fatal + // (no OS fallback, no QTIP TCP port retry). + // + BOOLEAN CreateRaw = IsMapMode || (Config->Flags & CXPLAT_SOCKET_FLAG_XDP); // // In a real production (XDP/QTIP+XDP) scenario, we never have to loop more than once @@ -272,6 +238,7 @@ CxPlatSocketCreateUdp( BOOLEAN CibirRequested = (Config->CibirIdLength > 0); (*NewSocket)->RawSocketAvailable = 0; + CXPLAT_DBG_ASSERT((IsMapMode && CreateRaw && Datapath->RawDataPath) || !IsMapMode); if (CreateRaw && Datapath->RawDataPath) { Status = RawSocketCreateUdp( @@ -283,6 +250,16 @@ CxPlatSocketCreateUdp( QuicTraceLogVerbose( RawSockCreateFail, "[sock] Failed to create raw socket, status:%d", Status); + + if (IsMapMode) { + // + // Map mode: no fallback allowed. + // + CxPlatSocketDelete(*NewSocket); + *NewSocket = NULL; + goto Error; + } + BOOLEAN IsServerSocket = !(*NewSocket)->HasFixedRemoteAddress; if (IsServerSocket && RequiresQtip) { // From 2ae68bafb7301fb31aa192372ea4f765f77b2c2e Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 14:13:09 -0700 Subject: [PATCH 26/41] fix build --- src/platform/datapath_raw_dummy.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index b25fad87f4..27cf688852 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -83,6 +83,14 @@ CxPlatDpRawIsExternalXdpMapMode( return FALSE; // Dummy raw datapath never has XDP map mode. } +void +CxPlatDpRawEnableExternalXdpMapMode( + _In_ CXPLAT_DATAPATH_RAW* RawDataPath + ) +{ + UNREFERENCED_PARAMETER(RawDataPath); +} + _IRQL_requires_max_(PASSIVE_LEVEL) void RawDataPathUpdatePollingIdleTimeout( From db73b3c7020c33112984cf4af095ce0f03df010c Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 15:36:20 -0700 Subject: [PATCH 27/41] refactor map into RAW dp --- src/platform/datapath_raw.c | 50 ++++++++++++++++++--------- src/platform/datapath_raw_dummy.c | 2 ++ src/platform/datapath_xplat.c | 56 +++++++++++-------------------- src/platform/platform_internal.h | 1 + 4 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/platform/datapath_raw.c b/src/platform/datapath_raw.c index fa7b9d94e7..602e56f709 100644 --- a/src/platform/datapath_raw.c +++ b/src/platform/datapath_raw.c @@ -24,6 +24,7 @@ RawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, _In_ CXPLAT_WORKER_POOL* WorkerPool, + _In_ CXPLAT_DATAPATH_INIT_CONFIG* InitConfig, _Outptr_result_maybenull_ CXPLAT_DATAPATH_RAW** NewDataPath ) { @@ -34,8 +35,8 @@ RawDataPathInitialize( *NewDataPath = NULL; - CXPLAT_DATAPATH_RAW* DataPath = CXPLAT_ALLOC_PAGED(DatapathSize, QUIC_POOL_DATAPATH); - if (DataPath == NULL) { + CXPLAT_DATAPATH_RAW* RawDataPath = CXPLAT_ALLOC_PAGED(DatapathSize, QUIC_POOL_DATAPATH); + if (RawDataPath == NULL) { QuicTraceEvent( AllocFailure, "Allocation of '%s' failed. (%llu bytes)", @@ -43,44 +44,63 @@ RawDataPathInitialize( DatapathSize); return; } - CxPlatZeroMemory(DataPath, DatapathSize); + CxPlatZeroMemory(RawDataPath, DatapathSize); CXPLAT_FRE_ASSERT(CxPlatWorkerPoolAddRef(WorkerPool, CXPLAT_WORKER_POOL_REF_RAW)); - DataPath->WorkerPool = WorkerPool; + RawDataPath->WorkerPool = WorkerPool; - if (!CxPlatSockPoolInitialize(&DataPath->SocketPool)) { + if (!CxPlatSockPoolInitialize(&RawDataPath->SocketPool)) { goto Error; } SockPoolInitialized = TRUE; - Status = CxPlatDpRawInitialize(DataPath, ClientRecvContextLength, WorkerPool); + Status = CxPlatDpRawInitialize(RawDataPath, ClientRecvContextLength, WorkerPool); if (QUIC_FAILED(Status)) { goto Error; } DpRawInitialized = TRUE; - Status = CxPlatDataPathRouteWorkerInitialize(DataPath); + Status = CxPlatDataPathRouteWorkerInitialize(RawDataPath); if (QUIC_FAILED(Status)) { goto Error; } - *NewDataPath = DataPath; - DataPath->ParentDataPath = ParentDataPath; - DataPath = NULL; + if (InitConfig->XdpMapConfigCount > 0) { + CXPLAT_DBG_ASSERT(InitConfig->XdpMapConfigs != NULL); + CxPlatDpRawEnableExternalXdpMapMode(RawDataPath); + Status = + CxPlatDpRawInsertXskByMapConfigs( + RawDataPath, + InitConfig->XdpMapConfigs, + InitConfig->XdpMapConfigCount); + if (QUIC_FAILED(Status)) { + QuicTraceLogVerbose( + DatapathRawMapInsertFail, + "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d", + Status); + RawDataPathUninitialize(RawDataPath); + RawDataPath = NULL; + goto Error; + } + } + + *NewDataPath = RawDataPath; + RawDataPath->ParentDataPath = ParentDataPath; + RawDataPath = NULL; Error: - if (DataPath != NULL) { + if (RawDataPath != NULL) { #if DEBUG - DataPath->Uninitialized = TRUE; + RawDataPath->Uninitialized = TRUE; #endif if (DpRawInitialized) { - CxPlatDpRawUninitialize(DataPath); + CxPlatDpRawUninitialize(RawDataPath); } else { if (SockPoolInitialized) { - CxPlatSockPoolUninitialize(&DataPath->SocketPool); + CxPlatSockPoolUninitialize(&RawDataPath->SocketPool); } - CXPLAT_FREE(DataPath, QUIC_POOL_DATAPATH); + CXPLAT_FREE(RawDataPath, QUIC_POOL_DATAPATH); CxPlatWorkerPoolRelease(WorkerPool, CXPLAT_WORKER_POOL_REF_RAW); } } diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index 27cf688852..20879f3c11 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -56,12 +56,14 @@ RawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, _In_ CXPLAT_WORKER_POOL* WorkerPool, + _In_ CXPLAT_DATAPATH_INIT_CONFIG* InitConfig, _Outptr_result_maybenull_ CXPLAT_DATAPATH_RAW** DataPath ) { UNREFERENCED_PARAMETER(ClientRecvContextLength); UNREFERENCED_PARAMETER(ParentDataPath); UNREFERENCED_PARAMETER(WorkerPool); + UNREFERENCED_PARAMETER(InitConfig); *DataPath = NULL; } diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index 09ef457e58..c3b50db345 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -32,23 +32,20 @@ CxPlatDataPathInitialize( goto Error; } - if (InitConfig->XdpMapConfigCount > 0 && InitConfig->XdpMapConfigs != NULL) { + if (InitConfig->XdpMapConfigCount > 0) { // - // XDP map mode: bypass the platform datapath (WinSock/epoll) entirely. - // Allocate the full CXPLAT_DATAPATH struct (zero-initialized) so that - // any code traversing platform-specific fields sees safe defaults. - // Only the common base fields and the raw datapath are used. - // - // Important: XDP map mode has a hard dependency on the Windows (winuser) - // datapath and the Windows XDP raw datapath implementation. The platform - // socket creation path (SocketCreateUdp in datapath_winuser.c) checks - // UseExternalXdpMaps (on the raw datapath) and skips OS socket creation, - // reusing the SkipCreatingOsSockets mechanism from the CIBIR path. On non-Windows - // platforms, RawDataPathInitialize is a no-op stub that returns NULL - // (datapath_raw_dummy.c), which causes the initialization below to fail - // gracefully. If XDP support is ever added to other platforms, the - // corresponding SocketCreateUdp implementation needs to be aware of map mode. + // XDP map mode: must require the raw datapath to be successfully initialized + // as we are skipping OS platform specific initializations. // + CXPLAT_DBG_ASSERT(InitConfig->XdpMapConfigs != NULL); + if (NewDataPath == NULL) { + return QUIC_STATUS_INVALID_PARAMETER; + } + if (UdpCallbacks != NULL) { + if (UdpCallbacks->Receive == NULL || UdpCallbacks->Unreachable == NULL) { + return QUIC_STATUS_INVALID_PARAMETER; + } + } CXPLAT_DATAPATH* Datapath = CXPLAT_ALLOC_PAGED(sizeof(CXPLAT_DATAPATH), QUIC_POOL_DATAPATH); if (Datapath == NULL) { @@ -60,19 +57,17 @@ CxPlatDataPathInitialize( Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; } - CxPlatZeroMemory(Datapath, sizeof(CXPLAT_DATAPATH)); if (UdpCallbacks) { Datapath->UdpHandlers = *UdpCallbacks; } Datapath->WorkerPool = WorkerPool; - *NewDataPath = Datapath; - RawDataPathInitialize( ClientRecvContextLength, - *NewDataPath, + Datapath, WorkerPool, + InitConfig, &Datapath->RawDataPath); if (Datapath->RawDataPath == NULL) { QuicTraceLogVerbose( @@ -83,25 +78,11 @@ CxPlatDataPathInitialize( Status = QUIC_STATUS_NOT_SUPPORTED; goto Error; } - - CxPlatDpRawEnableExternalXdpMapMode(Datapath->RawDataPath); - - Status = - CxPlatDpRawInsertXskByMapConfigs( - Datapath->RawDataPath, - InitConfig->XdpMapConfigs, - InitConfig->XdpMapConfigCount); - if (QUIC_FAILED(Status)) { - QuicTraceLogVerbose( - DatapathRawMapInsertFail, - "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d", - Status); - RawDataPathUninitialize(Datapath->RawDataPath); - CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); - *NewDataPath = NULL; - goto Error; - } + *NewDataPath = Datapath; } else { + // + // OS platform-specific initializations. + // Status = DataPathInitialize( ClientRecvContextLength, @@ -124,6 +105,7 @@ CxPlatDataPathInitialize( ClientRecvContextLength, *NewDataPath, WorkerPool, + InitConfig, &((*NewDataPath)->RawDataPath)); } diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 0ed63ff6d4..45998e0408 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -1206,6 +1206,7 @@ RawDataPathInitialize( _In_ uint32_t ClientRecvContextLength, _In_opt_ const CXPLAT_DATAPATH* ParentDataPath, _In_ CXPLAT_WORKER_POOL* WorkerPool, + _In_ CXPLAT_DATAPATH_INIT_CONFIG* InitConfig, _Outptr_result_maybenull_ CXPLAT_DATAPATH_RAW** DataPath ); From 4eb0d7039aedc0db8af98410c731119a32bcb2b6 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 15:39:51 -0700 Subject: [PATCH 28/41] CLOG --- src/generated/linux/datapath_raw.c.clog.h | 22 ++++++++++++++++++ .../linux/datapath_raw.c.clog.h.lttng.h | 19 +++++++++++++++ src/generated/linux/datapath_xplat.c.clog.h | 22 ++---------------- .../linux/datapath_xplat.c.clog.h.lttng.h | 23 ++----------------- 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/generated/linux/datapath_raw.c.clog.h b/src/generated/linux/datapath_raw.c.clog.h index 35e4abef69..3f47da86a4 100644 --- a/src/generated/linux/datapath_raw.c.clog.h +++ b/src/generated/linux/datapath_raw.c.clog.h @@ -14,6 +14,10 @@ #include "datapath_raw.c.clog.h.lttng.h" #endif #include +#ifndef _clog_MACRO_QuicTraceLogVerbose +#define _clog_MACRO_QuicTraceLogVerbose 1 +#define QuicTraceLogVerbose(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) +#endif #ifndef _clog_MACRO_QuicTraceEvent #define _clog_MACRO_QuicTraceEvent 1 #define QuicTraceEvent(a, ...) _clog_CAT(_clog_ARGN_SELECTOR(__VA_ARGS__), _clog_CAT(_,a(#a, __VA_ARGS__))) @@ -21,6 +25,24 @@ #ifdef __cplusplus extern "C" { #endif +/*---------------------------------------------------------- +// Decoder Ring for DatapathRawMapInsertFail +// [ dp] XDP map mode: failed to insert XSK sockets into map, status:%d +// QuicTraceLogVerbose( + DatapathRawMapInsertFail, + "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d", + Status); +// arg2 = arg2 = Status = arg2 +----------------------------------------------------------*/ +#ifndef _clog_3_ARGS_TRACE_DatapathRawMapInsertFail +#define _clog_3_ARGS_TRACE_DatapathRawMapInsertFail(uniqueId, encoded_arg_string, arg2)\ +tracepoint(CLOG_DATAPATH_RAW_C, DatapathRawMapInsertFail , arg2);\ + +#endif + + + + /*---------------------------------------------------------- // Decoder Ring for AllocFailure // Allocation of '%s' failed. (%llu bytes) diff --git a/src/generated/linux/datapath_raw.c.clog.h.lttng.h b/src/generated/linux/datapath_raw.c.clog.h.lttng.h index 4edb7a0fb6..5031a7e859 100644 --- a/src/generated/linux/datapath_raw.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_raw.c.clog.h.lttng.h @@ -1,6 +1,25 @@ +/*---------------------------------------------------------- +// Decoder Ring for DatapathRawMapInsertFail +// [ dp] XDP map mode: failed to insert XSK sockets into map, status:%d +// QuicTraceLogVerbose( + DatapathRawMapInsertFail, + "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d", + Status); +// arg2 = arg2 = Status = arg2 +----------------------------------------------------------*/ +TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_C, DatapathRawMapInsertFail, + TP_ARGS( + int, arg2), + TP_FIELDS( + ctf_integer(int, arg2, arg2) + ) +) + + + /*---------------------------------------------------------- // Decoder Ring for AllocFailure // Allocation of '%s' failed. (%llu bytes) diff --git a/src/generated/linux/datapath_xplat.c.clog.h b/src/generated/linux/datapath_xplat.c.clog.h index c626438eea..63fb7f316c 100644 --- a/src/generated/linux/datapath_xplat.c.clog.h +++ b/src/generated/linux/datapath_xplat.c.clog.h @@ -83,24 +83,6 @@ tracepoint(CLOG_DATAPATH_XPLAT_C, DatapathRawInitFailMapMode );\ -/*---------------------------------------------------------- -// Decoder Ring for DatapathRawMapInsertFail -// [ dp] XDP map mode: failed to insert XSK sockets into map, status:%d -// QuicTraceLogVerbose( - DatapathRawMapInsertFail, - "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d", - Status); -// arg2 = arg2 = Status = arg2 -----------------------------------------------------------*/ -#ifndef _clog_3_ARGS_TRACE_DatapathRawMapInsertFail -#define _clog_3_ARGS_TRACE_DatapathRawMapInsertFail(uniqueId, encoded_arg_string, arg2)\ -tracepoint(CLOG_DATAPATH_XPLAT_C, DatapathRawMapInsertFail , arg2);\ - -#endif - - - - /*---------------------------------------------------------- // Decoder Ring for DatapathInitFail // [ dp] Failed to initialize datapath, status:%d @@ -139,8 +121,8 @@ tracepoint(CLOG_DATAPATH_XPLAT_C, SockCreateFail , arg2);\ // Decoder Ring for RawSockCreateFail // [sock] Failed to create raw socket, status:%d // QuicTraceLogVerbose( - RawSockCreateFail, - "[sock] Failed to create raw socket, status:%d", Status); + RawSockCreateFail, + "[sock] Failed to create raw socket, status:%d", Status); // arg2 = arg2 = Status = arg2 ----------------------------------------------------------*/ #ifndef _clog_3_ARGS_TRACE_RawSockCreateFail diff --git a/src/generated/linux/datapath_xplat.c.clog.h.lttng.h b/src/generated/linux/datapath_xplat.c.clog.h.lttng.h index 873cbdf9ea..a78d0467c1 100644 --- a/src/generated/linux/datapath_xplat.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_xplat.c.clog.h.lttng.h @@ -51,25 +51,6 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, DatapathRawInitFailMapMode, -/*---------------------------------------------------------- -// Decoder Ring for DatapathRawMapInsertFail -// [ dp] XDP map mode: failed to insert XSK sockets into map, status:%d -// QuicTraceLogVerbose( - DatapathRawMapInsertFail, - "[ dp] XDP map mode: failed to insert XSK sockets into map, status:%d", - Status); -// arg2 = arg2 = Status = arg2 -----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, DatapathRawMapInsertFail, - TP_ARGS( - int, arg2), - TP_FIELDS( - ctf_integer(int, arg2, arg2) - ) -) - - - /*---------------------------------------------------------- // Decoder Ring for DatapathInitFail // [ dp] Failed to initialize datapath, status:%d @@ -110,8 +91,8 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, SockCreateFail, // Decoder Ring for RawSockCreateFail // [sock] Failed to create raw socket, status:%d // QuicTraceLogVerbose( - RawSockCreateFail, - "[sock] Failed to create raw socket, status:%d", Status); + RawSockCreateFail, + "[sock] Failed to create raw socket, status:%d", Status); // arg2 = arg2 = Status = arg2 ----------------------------------------------------------*/ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, RawSockCreateFail, From c9e7b1f3d3de38a6e573a5ca860075e13a84adf9 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 15:58:27 -0700 Subject: [PATCH 29/41] remove bogus test --- src/platform/unittest/DataPathTest.cpp | 31 -------------------------- 1 file changed, 31 deletions(-) diff --git a/src/platform/unittest/DataPathTest.cpp b/src/platform/unittest/DataPathTest.cpp index 1cbc4c605c..4bd1e35464 100644 --- a/src/platform/unittest/DataPathTest.cpp +++ b/src/platform/unittest/DataPathTest.cpp @@ -1450,37 +1450,6 @@ TEST_F(DataPathTest, XdpMapMode_ZeroConfigUsesNormalPath) CxPlatWorkerPoolDelete(WorkerPool, CXPLAT_WORKER_POOL_REF_TOOL); } -TEST_F(DataPathTest, XdpMapMode_NullConfigsUsesNormalPath) -{ - // - // XdpMapConfigCount > 0 but XdpMapConfigs == NULL should fall through - // to the normal path because the condition requires both to be set. - // - QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 0, {0} }; - CXPLAT_WORKER_POOL* WorkerPool = - CxPlatWorkerPoolCreate(&ExecConfig, CXPLAT_WORKER_POOL_REF_TOOL); - ASSERT_NE(nullptr, WorkerPool); - - CXPLAT_DATAPATH_INIT_CONFIG InitConfig = {0}; - InitConfig.EnableDscpOnRecv = TRUE; - InitConfig.XdpMapConfigs = nullptr; - InitConfig.XdpMapConfigCount = 1; - - CXPLAT_DATAPATH* Datapath = nullptr; - QUIC_STATUS Status = - CxPlatDataPathInitialize( - 0, - &EmptyUdpCallbacks, - nullptr, - WorkerPool, - &InitConfig, - &Datapath); - VERIFY_QUIC_SUCCESS(Status); - ASSERT_NE(nullptr, Datapath); - - CxPlatDataPathUninitialize(Datapath); - CxPlatWorkerPoolDelete(WorkerPool, CXPLAT_WORKER_POOL_REF_TOOL); -} TEST_F(DataPathTest, XdpMapMode_InitFailsWithoutRawDatapath) { From c1fa28c5b826ff977175c0a608def6df2e0787a3 Mon Sep 17 00:00:00 2001 From: Jack He Date: Fri, 22 May 2026 19:44:25 -0700 Subject: [PATCH 30/41] stash changes tests all working now --- scripts/run-gtest.ps1 | 12 ++ scripts/test.ps1 | 12 ++ src/platform/datapath_raw.h | 6 + src/test/MsQuicTests.h | 2 + src/test/bin/CMakeLists.txt | 6 +- src/test/bin/quic_gtest.cpp | 279 ++++++++++++++++++++++++++++++++- src/test/lib/HandshakeTest.cpp | 167 ++++++++++++++++++++ src/test/lib/TestHelpers.h | 1 + 8 files changed, 483 insertions(+), 2 deletions(-) diff --git a/scripts/run-gtest.ps1 b/scripts/run-gtest.ps1 index 31708b483f..fa47a98db5 100644 --- a/scripts/run-gtest.ps1 +++ b/scripts/run-gtest.ps1 @@ -62,6 +62,9 @@ as necessary. .PARAMETER DuoNic Uses DuoNic instead of loopback. +.PARAMETER XdpMapMode + Uses XDP map mode with DuoNic. + #> param ( @@ -133,6 +136,9 @@ param ( [Parameter(Mandatory = $false)] [switch]$DuoNic = $false, + [Parameter(Mandatory = $false)] + [switch]$XdpMapMode = $false, + [Parameter(Mandatory = $false)] [string]$OsRunner = "", @@ -403,6 +409,9 @@ function Start-TestCase([String]$Name) { if ($DuoNic) { $Arguments += " --duoNic" } + if ($XdpMapMode) { + $Arguments += " --xdpMapMode" + } if ($UseQtip) { $Arguments += " --useQTIP" } @@ -450,6 +459,9 @@ function Start-AllTestCases { if ($DuoNic) { $Arguments += " --duoNic" } + if ($XdpMapMode) { + $Arguments += " --xdpMapMode" + } if ($UseQtip) { $Arguments += " --useQTIP" } diff --git a/scripts/test.ps1 b/scripts/test.ps1 index 74da3b7bdd..051ed8ab85 100644 --- a/scripts/test.ps1 +++ b/scripts/test.ps1 @@ -70,6 +70,9 @@ This script runs the MsQuic tests. .Parameter DuoNic Uses DuoNic instead of loopback (DuoNic must already be installed via 'prepare-machine.ps1 -InstallDuoNic'). +.Parameter XdpMapMode + Uses XDP map mode with DuoNic (Windows user-mode only, requires XDP + DuoNic). + .Parameter NumIterations Number of times to run this particular command. Catches tricky edge cases due to random nature of networks. @@ -178,6 +181,9 @@ param ( [Parameter(Mandatory = $false)] [switch]$DuoNic = $false, + [Parameter(Mandatory = $false)] + [switch]$XdpMapMode = $false, + [Parameter(Mandatory = $false)] [switch]$UseXdp = $false, @@ -298,6 +304,12 @@ $TestArguments = "-IsolationMode $IsolationMode -PfxPath $PfxFile" if ($DuoNic) { $TestArguments += " -DuoNic" } +if ($XdpMapMode) { + $TestArguments += " -XdpMapMode" + if (!$DuoNic) { + $TestArguments += " -DuoNic" + } +} if ($Kernel) { $TestArguments += " -Kernel $KernelPath" } diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index fffca30273..7469bd9149 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -353,6 +353,12 @@ CxPlatSocketCompare( // For a client socket, make sure the local IP matches and the full // remote address matches along with QTIP settings. // + // TODO: In XDP map mode the OS socket creation path is skipped, so + // the socket's local IP may remain wildcard (0.0.0.0 / ::) if the + // caller doesn't supply a specific local IP. Map mode should require + // (or resolve) a concrete local IP so that this comparison works + // correctly without special-casing wildcards here. + // CXPLAT_DBG_ASSERT(Socket->Connected); return QuicAddrCompareIp(&Socket->LocalAddress, LocalAddress) && diff --git a/src/test/MsQuicTests.h b/src/test/MsQuicTests.h index 7b361a923c..abf8f62c58 100644 --- a/src/test/MsQuicTests.h +++ b/src/test/MsQuicTests.h @@ -79,6 +79,8 @@ void QuicTestCloseConnBeforeStreamFlush(); void QuicTestGlobalParam(); #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES void QuicTestXdpMapConfigParam(); +void QuicTestXdpMapModeHandshake(_In_ int Family, _In_ uint16_t ServerPort, _In_ uint16_t ClientPort); +void QuicTestXdpMapModeDataTransfer(_In_ int Family, _In_ uint16_t ServerPort, _In_ uint16_t ClientPort); #endif void QuicTestCommonParam(); void QuicTestRegistrationParam(); diff --git a/src/test/bin/CMakeLists.txt b/src/test/bin/CMakeLists.txt index 3cfb210aff..1e0352d5b6 100644 --- a/src/test/bin/CMakeLists.txt +++ b/src/test/bin/CMakeLists.txt @@ -10,6 +10,10 @@ add_executable(msquictest ${SOURCES}) target_include_directories(msquictest PRIVATE ${PROJECT_SOURCE_DIR}/src/test) +if (WIN32) + target_include_directories(msquictest PRIVATE ${PROJECT_SOURCE_DIR}/submodules/xdp-for-windows/published/external) +endif() + set_property(TARGET msquictest PROPERTY FOLDER "${QUIC_FOLDER_PREFIX}tests") set_property(TARGET msquictest APPEND PROPERTY BUILD_RPATH "$ORIGIN") @@ -22,7 +26,7 @@ endif() target_link_libraries(msquictest inc gtest logging base_link) if (WIN32) - target_link_libraries(msquictest oldnames) + target_link_libraries(msquictest oldnames iphlpapi) endif() # At least /W3 must be used on all windows builds to pass compliance diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 3665957166..a7512a4fe3 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -14,6 +14,14 @@ #include +#if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +#include +#define XDP_API_VERSION 3 +#define XDP_INCLUDE_WINCOMMON +#include +#include +#endif + #ifdef QUIC_TEST_DATAPATH_HOOKS_ENABLED #pragma message("Test compiled with datapath hooks enabled") #endif @@ -25,6 +33,7 @@ bool TestingKernelMode = false; bool PrivateTestLibrary = false; bool UseDuoNic = false; +bool UseXdpMapMode = false; CXPLAT_WORKER_POOL* WorkerPool; #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) bool UseQTIP = false; @@ -37,6 +46,65 @@ QUIC_CREDENTIAL_CONFIG ServerSelfSignedCredConfigClientAuth; QUIC_CREDENTIAL_CONFIG ClientCertCredConfig; QuicDriverClient DriverClient; +#if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +// +// XDP map mode state. Populated during SetUp when --xdpMapMode is used. +// +#define XDP_MAP_MODE_MAX_INTERFACES 2 +#define XDP_MAP_MODE_MAX_QUEUES 64 +struct XdpMapModeState { + uint32_t InterfaceCount; + uint32_t IfIndices[XDP_MAP_MODE_MAX_INTERFACES]; + HANDLE XskMaps[XDP_MAP_MODE_MAX_INTERFACES]; +} XdpMapState = {}; + +// +// Discover DuoNic interface indices by enumerating Ethernet adapters with +// known DuoNic IPv4 addresses (192.168.1.11 and 192.168.1.12). +// +static bool +DiscoverDuoNicInterfaces( + _Out_writes_(XDP_MAP_MODE_MAX_INTERFACES) uint32_t* IfIndices, + _Out_ uint32_t* Count + ) +{ + *Count = 0; + ULONG Flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; + ULONG BufSize = 0; + GetAdaptersAddresses(AF_INET, Flags, NULL, NULL, &BufSize); + if (BufSize == 0) return false; + + auto Adapters = (PIP_ADAPTER_ADDRESSES)malloc(BufSize); + if (!Adapters) return false; + + if (GetAdaptersAddresses(AF_INET, Flags, NULL, Adapters, &BufSize) != NO_ERROR) { + free(Adapters); + return false; + } + + for (auto Adapter = Adapters; Adapter && *Count < XDP_MAP_MODE_MAX_INTERFACES; Adapter = Adapter->Next) { + if (Adapter->IfType != IF_TYPE_ETHERNET_CSMACD || + Adapter->OperStatus != IfOperStatusUp) { + continue; + } + for (auto Unicast = Adapter->FirstUnicastAddress; Unicast; Unicast = Unicast->Next) { + if (Unicast->Address.lpSockaddr->sa_family != AF_INET) continue; + auto* Sin = (SOCKADDR_IN*)Unicast->Address.lpSockaddr; + ULONG Addr = ntohl(Sin->sin_addr.S_un.S_addr); + // 192.168.1.11 or 192.168.1.12 + if (Addr == 0xC0A8010B || Addr == 0xC0A8010C) { + IfIndices[*Count] = Adapter->IfIndex; + (*Count)++; + break; + } + } + } + free(Adapters); + return *Count > 0; +} +#endif // _WIN32 && QUIC_API_ENABLE_PREVIEW_FEATURES + // // These are explicitly passed in as the name of the GitHub/Azure runners. // @@ -123,7 +191,40 @@ class QuicTestEnvironment : public ::testing::Environment { Settings.SetQtipEnabled(true); ASSERT_TRUE(QUIC_SUCCEEDED(Settings.SetGlobal())); } -#endif +#if defined(_WIN32) + if (UseXdpMapMode) { + // + // Discover DuoNic interfaces and create XSKMAPs. + // + ASSERT_TRUE( + DiscoverDuoNicInterfaces( + XdpMapState.IfIndices, + &XdpMapState.InterfaceCount)); + printf("XDP Map Mode: discovered %u DuoNic interface(s)\n", + XdpMapState.InterfaceCount); + + QUIC_XDP_MAP_CONFIG MapConfigs[XDP_MAP_MODE_MAX_INTERFACES]; + for (uint32_t i = 0; i < XdpMapState.InterfaceCount; i++) { + ASSERT_TRUE(SUCCEEDED( + XdpMapCreate(&XdpMapState.XskMaps[i], XDP_MAP_TYPE_XSKMAP))); + MapConfigs[i].InterfaceIndex = XdpMapState.IfIndices[i]; + MapConfigs[i].MapHandle = (QUIC_XDP_MAP_HANDLE)XdpMapState.XskMaps[i]; + printf(" IfIndex=%u, XskMap=%p\n", + XdpMapState.IfIndices[i], XdpMapState.XskMaps[i]); + } + + // + // Set the XDP map config before any registration is opened. + // This must happen before LazyInitComplete. + // + ASSERT_TRUE(QUIC_SUCCEEDED(MsQuic->SetParam( + nullptr, + QUIC_PARAM_GLOBAL_XDP_MAP_CONFIG, + XdpMapState.InterfaceCount * sizeof(QUIC_XDP_MAP_CONFIG), + MapConfigs))); + } +#endif // _WIN32 +#endif // QUIC_API_ENABLE_PREVIEW_FEATURES // // Enable DSCP on the receive path. This is needed to test DSCP Send path. // @@ -143,6 +244,19 @@ class QuicTestEnvironment : public ::testing::Environment { ClientCertCredConfig.Flags |= QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION; QuicTestInitialize(); +#if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (UseXdpMapMode) { + // + // Force lazy initialization so MsQuic creates XSK sockets + // and inserts them into the XSKMAPs we provided. + // + { + MsQuicRegistration TempReg("XdpMapModeInit"); + ASSERT_TRUE(TempReg.IsValid()); + } + } +#endif // _WIN32 && QUIC_API_ENABLE_PREVIEW_FEATURES + #ifdef _WIN32 ASSERT_NE(GetCurrentDirectoryA(sizeof(CurrentWorkingDirectory), CurrentWorkingDirectory), 0); #else @@ -158,6 +272,16 @@ class QuicTestEnvironment : public ::testing::Environment { QuicTestUninitialize(); delete MsQuic; } +#if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + if (UseXdpMapMode) { + for (uint32_t i = 0; i < XdpMapState.InterfaceCount; i++) { + if (XdpMapState.XskMaps[i]) { + CloseHandle(XdpMapState.XskMaps[i]); + XdpMapState.XskMaps[i] = nullptr; + } + } + } +#endif CxPlatFreeSelfSignedCert(SelfSignedCertParams); CxPlatFreeSelfSignedCert(ClientCertParams); @@ -3101,6 +3225,151 @@ INSTANTIATE_TEST_SUITE_P( WithFamilyArgs, ::testing::ValuesIn(WithFamilyArgs::Generate())); +#if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) +// +// XDP Map Mode tests. These only run when --xdpMapMode is passed. +// Each test reserves its own ports (keeping OS sockets open to prevent +// reuse), creates XDP programs for those ports, and tears them down. +// + +struct XdpMapMode : public ::testing::TestWithParam { + SOCKET PortSocks[2] = {INVALID_SOCKET, INVALID_SOCKET}; + uint16_t ServerPort = 0; + uint16_t ClientPort = 0; + HANDLE XdpPrograms[XDP_MAP_MODE_MAX_INTERFACES][XDP_MAP_MODE_MAX_QUEUES] = {}; + uint32_t QueueCounts[XDP_MAP_MODE_MAX_INTERFACES] = {}; + bool WsaInitialized = false; + + void SetUp() override { + if (!UseXdpMapMode) return; + + WSADATA WsaData; + ASSERT_EQ(WSAStartup(MAKEWORD(2, 2), &WsaData), 0); + WsaInitialized = true; + + // + // Reserve 2 ports (server + client) with OS sockets that stay open. + // + for (int i = 0; i < 2; i++) { + PortSocks[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + ASSERT_NE(PortSocks[i], INVALID_SOCKET) + << "Failed to create socket: WSAGetLastError=" << WSAGetLastError(); + struct sockaddr_in Addr = {}; + Addr.sin_family = AF_INET; + ASSERT_EQ(bind(PortSocks[i], (struct sockaddr*)&Addr, sizeof(Addr)), 0); + int AddrLen = sizeof(Addr); + ASSERT_EQ(getsockname(PortSocks[i], (struct sockaddr*)&Addr, &AddrLen), 0); + uint16_t Port = ntohs(Addr.sin_port); + ASSERT_NE(Port, (uint16_t)0); + if (i == 0) ServerPort = Port; else ClientPort = Port; + } + + printf("XDP Map Mode [%s]: ports Server=%u Client=%u\n", + ::testing::UnitTest::GetInstance()->current_test_info()->name(), + ServerPort, ClientPort); + + // + // Create per-queue XDP programs mirroring MsQuic's internal + // CxPlatDpRawInterfaceUpdateRules: one program per queue with + // flags=0, targeting the XSKMAP for redirect-by-queue-id. + // + static const XDP_HOOK_ID RxHook = { + XDP_HOOK_L2, + XDP_HOOK_RX, + XDP_HOOK_INSPECT, + }; + + for (uint32_t i = 0; i < XdpMapState.InterfaceCount; i++) { + for (uint32_t q = 0; q < XDP_MAP_MODE_MAX_QUEUES; q++) { + XDP_RULE Rules[2] = {}; + + Rules[0].Match = XDP_MATCH_UDP_DST; + Rules[0].Pattern.Port = htons(ServerPort); + Rules[0].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[0].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[0].Redirect.Target = XdpMapState.XskMaps[i]; + + Rules[1].Match = XDP_MATCH_UDP_DST; + Rules[1].Pattern.Port = htons(ClientPort); + Rules[1].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[1].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[1].Redirect.Target = XdpMapState.XskMaps[i]; + + HRESULT Hr = XdpCreateProgram( + XdpMapState.IfIndices[i], + &RxHook, + q, + XDP_CREATE_PROGRAM_FLAG_NONE, + Rules, + 2, + &XdpPrograms[i][q]); + if (FAILED(Hr)) { + // + // No more queues on this interface. + // + QueueCounts[i] = q; + break; + } + } + printf("XDP Map Mode: IfIndex=%u created %u per-queue programs\n", + XdpMapState.IfIndices[i], QueueCounts[i]); + ASSERT_GT(QueueCounts[i], (uint32_t)0) + << "Failed to create any XDP programs for IfIndex=" + << XdpMapState.IfIndices[i]; + } + } + + void TearDown() override { + if (!UseXdpMapMode) return; + + for (uint32_t i = 0; i < XdpMapState.InterfaceCount; i++) { + for (uint32_t q = 0; q < QueueCounts[i]; q++) { + if (XdpPrograms[i][q]) { + CloseHandle(XdpPrograms[i][q]); + XdpPrograms[i][q] = nullptr; + } + } + } + for (int i = 0; i < 2; i++) { + if (PortSocks[i] != INVALID_SOCKET) { + closesocket(PortSocks[i]); + PortSocks[i] = INVALID_SOCKET; + } + } + if (WsaInitialized) { + WSACleanup(); + } + } +}; + +TEST_P(XdpMapMode, Handshake) { + if (!UseXdpMapMode) { + GTEST_SKIP() << "XDP Map Mode not enabled (use --xdpMapMode)"; + } + TestLogger Logger("QuicTestXdpMapModeHandshake"); + QuicTestXdpMapModeHandshake( + GetParam(), + ServerPort, + ClientPort); +} + +TEST_P(XdpMapMode, DataTransfer) { + if (!UseXdpMapMode) { + GTEST_SKIP() << "XDP Map Mode not enabled (use --xdpMapMode)"; + } + TestLogger Logger("QuicTestXdpMapModeDataTransfer"); + QuicTestXdpMapModeDataTransfer( + GetParam(), + ServerPort, + ClientPort); +} + +INSTANTIATE_TEST_SUITE_P( + XdpMapMode, + XdpMapMode, + ::testing::Values(4, 6)); +#endif // _WIN32 && QUIC_API_ENABLE_PREVIEW_FEATURES + int main(int argc, char** argv) { #ifdef _WIN32 // @@ -3124,6 +3393,14 @@ int main(int argc, char** argv) { } } else if (strcmp("--duoNic", argv[i]) == 0) { UseDuoNic = true; + } else if (strcmp("--xdpMapMode", argv[i]) == 0) { +#if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + UseXdpMapMode = true; + UseDuoNic = true; // Map mode implies DuoNic +#else + printf("XDP Map Mode is only supported on Windows with preview features.\n"); + return -1; +#endif } else if (strcmp("--useQTIP", argv[i]) == 0) { #if defined(QUIC_API_ENABLE_PREVIEW_FEATURES) UseQTIP = true; diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index 9da03a0fec..39f20d9138 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -4915,4 +4915,171 @@ QuicTestConnectionPoolCreate( } } +void +QuicTestXdpMapModeHandshake( + _In_ int Family, + _In_ uint16_t ServerPort, + _In_ uint16_t ClientPort + ) +{ + QUIC_ADDRESS_FAMILY QuicAddrFamily = + (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; + + MsQuicRegistration Registration; + TEST_TRUE(Registration.IsValid()); + + MsQuicAlpn Alpn("MsQuicTest"); + + MsQuicSettings Settings; + Settings.SetPeerBidiStreamCount(1); + Settings.SetIdleTimeoutMs(10000); + + MsQuicConfiguration ServerConfiguration(Registration, Alpn, Settings, ServerSelfSignedCredConfig); + TEST_TRUE(ServerConfiguration.IsValid()); + + MsQuicCredentialConfig ClientCredConfig; + MsQuicConfiguration ClientConfiguration(Registration, Alpn, Settings, ClientCredConfig); + TEST_TRUE(ClientConfiguration.IsValid()); + + { + TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); + TEST_TRUE(Listener.IsValid()); + + QuicAddr ServerLocalAddr(QuicAddrFamily); + QuicAddrSetPort(&ServerLocalAddr.SockAddr, ServerPort); + TEST_QUIC_SUCCEEDED(Listener.Start(Alpn, &ServerLocalAddr.SockAddr)); + TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr)); + + { + UniquePtr Server; + ServerAcceptContext ServerAcceptCtx((TestConnection**)&Server); + Listener.Context = &ServerAcceptCtx; + + { + TestConnection Client(Registration); + TEST_TRUE(Client.IsValid()); + + QuicAddr ClientLocalAddr(QuicAddrFamily); + QuicAddrSetToDuoNicClient(&ClientLocalAddr.SockAddr); + QuicAddrSetPort(&ClientLocalAddr.SockAddr, ClientPort); + TEST_QUIC_SUCCEEDED(Client.SetLocalAddr(ClientLocalAddr)); + + QuicAddr RemoteAddr{QuicAddrGetFamily(&ServerLocalAddr.SockAddr), ServerLocalAddr.GetPort()}; + QuicAddrSetToDuoNic(&RemoteAddr.SockAddr); + TEST_QUIC_SUCCEEDED(Client.SetRemoteAddr(RemoteAddr)); + + TEST_QUIC_SUCCEEDED( + Client.Start( + ClientConfiguration, + QuicAddrFamily, + QUIC_LOCALHOST_FOR_AF(QuicAddrFamily), + ServerLocalAddr.GetPort())); + + if (!Client.WaitForConnectionComplete()) { + return; + } + TEST_TRUE(Client.GetIsConnected()); + + TEST_NOT_EQUAL(nullptr, Server); + if (!Server->WaitForConnectionComplete()) { + return; + } + TEST_TRUE(Server->GetIsConnected()); + + Client.Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); + } + } + } +} + +void +QuicTestXdpMapModeDataTransfer( + _In_ int Family, + _In_ uint16_t ServerPort, + _In_ uint16_t ClientPort + ) +{ + QUIC_ADDRESS_FAMILY QuicAddrFamily = + (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; + + MsQuicRegistration Registration; + TEST_TRUE(Registration.IsValid()); + + MsQuicAlpn Alpn("MsQuicTest"); + + MsQuicSettings Settings; + Settings.SetPeerBidiStreamCount(1); + Settings.SetIdleTimeoutMs(10000); + + MsQuicConfiguration ServerConfiguration(Registration, Alpn, Settings, ServerSelfSignedCredConfig); + TEST_TRUE(ServerConfiguration.IsValid()); + + MsQuicCredentialConfig ClientCredConfig; + MsQuicConfiguration ClientConfiguration(Registration, Alpn, Settings, ClientCredConfig); + TEST_TRUE(ClientConfiguration.IsValid()); + + { + TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); + TEST_TRUE(Listener.IsValid()); + + QuicAddr ServerLocalAddr(QuicAddrFamily); + QuicAddrSetPort(&ServerLocalAddr.SockAddr, ServerPort); + TEST_QUIC_SUCCEEDED(Listener.Start(Alpn, &ServerLocalAddr.SockAddr)); + TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr)); + + { + UniquePtr Server; + ServerAcceptContext ServerAcceptCtx((TestConnection**)&Server); + Listener.Context = &ServerAcceptCtx; + + { + TestConnection Client(Registration); + TEST_TRUE(Client.IsValid()); + + QuicAddr ClientLocalAddr(QuicAddrFamily); + QuicAddrSetToDuoNicClient(&ClientLocalAddr.SockAddr); + QuicAddrSetPort(&ClientLocalAddr.SockAddr, ClientPort); + TEST_QUIC_SUCCEEDED(Client.SetLocalAddr(ClientLocalAddr)); + + QuicAddr RemoteAddr{QuicAddrGetFamily(&ServerLocalAddr.SockAddr), ServerLocalAddr.GetPort()}; + QuicAddrSetToDuoNic(&RemoteAddr.SockAddr); + TEST_QUIC_SUCCEEDED(Client.SetRemoteAddr(RemoteAddr)); + + TEST_QUIC_SUCCEEDED( + Client.Start( + ClientConfiguration, + QuicAddrFamily, + QUIC_LOCALHOST_FOR_AF(QuicAddrFamily), + ServerLocalAddr.GetPort())); + + if (!Client.WaitForConnectionComplete()) { + return; + } + TEST_TRUE(Client.GetIsConnected()); + + TEST_NOT_EQUAL(nullptr, Server); + if (!Server->WaitForConnectionComplete()) { + return; + } + TEST_TRUE(Server->GetIsConnected()); + + // + // Open a bidirectional stream and send a small ping payload. + // + auto Stream = + Client.NewStream( + nullptr, + QUIC_STREAM_OPEN_FLAG_NONE); + TEST_TRUE(Stream != nullptr); + TEST_TRUE(Stream->StartPing(64)); + Stream->WaitForSendShutdownComplete(); + + delete Stream; + + Client.Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); + } + } + } +} + #endif // QUIC_API_ENABLE_PREVIEW_FEATURES diff --git a/src/test/lib/TestHelpers.h b/src/test/lib/TestHelpers.h index 67212cb71f..2b56e442f3 100644 --- a/src/test/lib/TestHelpers.h +++ b/src/test/lib/TestHelpers.h @@ -16,6 +16,7 @@ #endif extern bool UseDuoNic; +extern bool UseXdpMapMode; // // Connect to the duonic address (if using duonic) or localhost (if not). From 3b85f5e27ae0a66925ee9f8675469f3c8340cdd8 Mon Sep 17 00:00:00 2001 From: Jack He Date: Tue, 26 May 2026 12:46:49 -0700 Subject: [PATCH 31/41] improve tests --- src/test/lib/DataTest.cpp | 90 ++++++++++++++++++++++++++++++++++ src/test/lib/DatagramTest.cpp | 28 ++--------- src/test/lib/HandshakeTest.cpp | 90 ---------------------------------- src/test/lib/precomp.h | 27 ++++++++++ 4 files changed, 120 insertions(+), 115 deletions(-) diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index be4fe1c2a6..e73548a5fa 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -5479,4 +5479,94 @@ QuicTestStreamAppProvidedBuffersOutOfSpace_ServerSend_ProvideMoreBuffer( TEST_EQUAL(0, memcmp(Buffers.SendDataBuffer.get(), Buffers.ReceiveDataBuffer.get(), Buffers.SendDataSize)); } +void +QuicTestXdpMapModeDataTransfer( + _In_ int Family, + _In_ uint16_t ServerPort, + _In_ uint16_t ClientPort + ) +{ + QUIC_ADDRESS_FAMILY QuicAddrFamily = + (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; + + MsQuicRegistration Registration; + TEST_TRUE(Registration.IsValid()); + + MsQuicAlpn Alpn("MsQuicTest"); + + MsQuicSettings Settings; + Settings.SetPeerBidiStreamCount(1); + Settings.SetIdleTimeoutMs(10000); + + MsQuicConfiguration ServerConfiguration(Registration, Alpn, Settings, ServerSelfSignedCredConfig); + TEST_TRUE(ServerConfiguration.IsValid()); + + MsQuicCredentialConfig ClientCredConfig; + MsQuicConfiguration ClientConfiguration(Registration, Alpn, Settings, ClientCredConfig); + TEST_TRUE(ClientConfiguration.IsValid()); + + { + TestListener Listener(Registration, ListenerAcceptConnectionBasic, ServerConfiguration); + TEST_TRUE(Listener.IsValid()); + + QuicAddr ServerLocalAddr(QuicAddrFamily); + QuicAddrSetPort(&ServerLocalAddr.SockAddr, ServerPort); + TEST_QUIC_SUCCEEDED(Listener.Start(Alpn, &ServerLocalAddr.SockAddr)); + TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr)); + + { + UniquePtr Server; + ServerAcceptContext ServerAcceptCtx((TestConnection**)&Server); + Listener.Context = &ServerAcceptCtx; + + { + TestConnection Client(Registration); + TEST_TRUE(Client.IsValid()); + + QuicAddr ClientLocalAddr(QuicAddrFamily); + QuicAddrSetToDuoNicClient(&ClientLocalAddr.SockAddr); + QuicAddrSetPort(&ClientLocalAddr.SockAddr, ClientPort); + TEST_QUIC_SUCCEEDED(Client.SetLocalAddr(ClientLocalAddr)); + + QuicAddr RemoteAddr{QuicAddrGetFamily(&ServerLocalAddr.SockAddr), ServerLocalAddr.GetPort()}; + QuicAddrSetToDuoNic(&RemoteAddr.SockAddr); + TEST_QUIC_SUCCEEDED(Client.SetRemoteAddr(RemoteAddr)); + + TEST_QUIC_SUCCEEDED( + Client.Start( + ClientConfiguration, + QuicAddrFamily, + QUIC_LOCALHOST_FOR_AF(QuicAddrFamily), + ServerLocalAddr.GetPort())); + + if (!Client.WaitForConnectionComplete()) { + return; + } + TEST_TRUE(Client.GetIsConnected()); + + TEST_NOT_EQUAL(nullptr, Server); + if (!Server->WaitForConnectionComplete()) { + return; + } + TEST_TRUE(Server->GetIsConnected()); + + // + // Open a bidirectional stream and send a small ping payload. + // + auto Stream = + Client.NewStream( + nullptr, + QUIC_STREAM_OPEN_FLAG_NONE); + TEST_TRUE(Stream != nullptr); + TEST_TRUE(Stream->StartPing(64)); + Stream->WaitForSendShutdownComplete(); + + delete Stream; + + Client.Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); + } + } + } +} + #endif // QUIC_API_ENABLE_PREVIEW_FEATURES diff --git a/src/test/lib/DatagramTest.cpp b/src/test/lib/DatagramTest.cpp index 1e49ee76f1..325d0e5174 100644 --- a/src/test/lib/DatagramTest.cpp +++ b/src/test/lib/DatagramTest.cpp @@ -14,28 +14,6 @@ #include "DatagramTest.cpp.clog.h" #endif -_Function_class_(NEW_CONNECTION_CALLBACK) -static -bool -QUIC_API -ListenerAcceptConnection( - _In_ TestListener* Listener, - _In_ HQUIC ConnectionHandle - ) -{ - ServerAcceptContext* AcceptContext = (ServerAcceptContext*)Listener->Context; - *AcceptContext->NewConnection = new(std::nothrow) TestConnection(ConnectionHandle); - if (*AcceptContext->NewConnection == nullptr || !(*AcceptContext->NewConnection)->IsValid()) { - TEST_FAILURE("Failed to accept new TestConnection."); - delete *AcceptContext->NewConnection; - *AcceptContext->NewConnection = nullptr; - return false; - } - (*AcceptContext->NewConnection)->SetHasRandomLoss(Listener->GetHasRandomLoss()); - CxPlatEventSet(AcceptContext->NewConnectionReady); - return true; -} - void QuicTestDatagramNegotiation( const DatagramNegotiationArgs& Params @@ -63,7 +41,7 @@ QuicTestDatagramNegotiation( QUIC_BUFFER DatagramBuffer = { sizeof(RawBuffer), RawBuffer }; { - TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); + TestListener Listener(Registration, ListenerAcceptConnectionBasic, ServerConfiguration); TEST_TRUE(Listener.IsValid()); QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; @@ -164,7 +142,7 @@ QuicTestDatagramSend( SelectiveLossHelper LossHelper; { - TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); + TestListener Listener(Registration, ListenerAcceptConnectionBasic, ServerConfiguration); TEST_TRUE(Listener.IsValid()); QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; @@ -298,7 +276,7 @@ QuicTestDatagramDrop( SelectiveLossHelper LossHelper; { - TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); + TestListener Listener(Registration, ListenerAcceptConnectionBasic, ServerConfiguration); TEST_TRUE(Listener.IsValid()); QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index 39f20d9138..6232ab0b0c 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -4992,94 +4992,4 @@ QuicTestXdpMapModeHandshake( } } -void -QuicTestXdpMapModeDataTransfer( - _In_ int Family, - _In_ uint16_t ServerPort, - _In_ uint16_t ClientPort - ) -{ - QUIC_ADDRESS_FAMILY QuicAddrFamily = - (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; - - MsQuicRegistration Registration; - TEST_TRUE(Registration.IsValid()); - - MsQuicAlpn Alpn("MsQuicTest"); - - MsQuicSettings Settings; - Settings.SetPeerBidiStreamCount(1); - Settings.SetIdleTimeoutMs(10000); - - MsQuicConfiguration ServerConfiguration(Registration, Alpn, Settings, ServerSelfSignedCredConfig); - TEST_TRUE(ServerConfiguration.IsValid()); - - MsQuicCredentialConfig ClientCredConfig; - MsQuicConfiguration ClientConfiguration(Registration, Alpn, Settings, ClientCredConfig); - TEST_TRUE(ClientConfiguration.IsValid()); - - { - TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); - TEST_TRUE(Listener.IsValid()); - - QuicAddr ServerLocalAddr(QuicAddrFamily); - QuicAddrSetPort(&ServerLocalAddr.SockAddr, ServerPort); - TEST_QUIC_SUCCEEDED(Listener.Start(Alpn, &ServerLocalAddr.SockAddr)); - TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr)); - - { - UniquePtr Server; - ServerAcceptContext ServerAcceptCtx((TestConnection**)&Server); - Listener.Context = &ServerAcceptCtx; - - { - TestConnection Client(Registration); - TEST_TRUE(Client.IsValid()); - - QuicAddr ClientLocalAddr(QuicAddrFamily); - QuicAddrSetToDuoNicClient(&ClientLocalAddr.SockAddr); - QuicAddrSetPort(&ClientLocalAddr.SockAddr, ClientPort); - TEST_QUIC_SUCCEEDED(Client.SetLocalAddr(ClientLocalAddr)); - - QuicAddr RemoteAddr{QuicAddrGetFamily(&ServerLocalAddr.SockAddr), ServerLocalAddr.GetPort()}; - QuicAddrSetToDuoNic(&RemoteAddr.SockAddr); - TEST_QUIC_SUCCEEDED(Client.SetRemoteAddr(RemoteAddr)); - - TEST_QUIC_SUCCEEDED( - Client.Start( - ClientConfiguration, - QuicAddrFamily, - QUIC_LOCALHOST_FOR_AF(QuicAddrFamily), - ServerLocalAddr.GetPort())); - - if (!Client.WaitForConnectionComplete()) { - return; - } - TEST_TRUE(Client.GetIsConnected()); - - TEST_NOT_EQUAL(nullptr, Server); - if (!Server->WaitForConnectionComplete()) { - return; - } - TEST_TRUE(Server->GetIsConnected()); - - // - // Open a bidirectional stream and send a small ping payload. - // - auto Stream = - Client.NewStream( - nullptr, - QUIC_STREAM_OPEN_FLAG_NONE); - TEST_TRUE(Stream != nullptr); - TEST_TRUE(Stream->StartPing(64)); - Stream->WaitForSendShutdownComplete(); - - delete Stream; - - Client.Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); - } - } - } -} - #endif // QUIC_API_ENABLE_PREVIEW_FEATURES diff --git a/src/test/lib/precomp.h b/src/test/lib/precomp.h index f7dba0df46..76be376a94 100644 --- a/src/test/lib/precomp.h +++ b/src/test/lib/precomp.h @@ -49,6 +49,33 @@ #include "TestListener.h" #include "DrillDescriptor.h" +// +// Basic listener accept callback that creates a TestConnection and signals +// readiness. For handshake-specific features (cert validation, TLS secrets, +// etc.), use the full ListenerAcceptConnection in HandshakeTest.cpp. +// +_Function_class_(NEW_CONNECTION_CALLBACK) +inline +bool +QUIC_API +ListenerAcceptConnectionBasic( + _In_ TestListener* Listener, + _In_ HQUIC ConnectionHandle + ) +{ + ServerAcceptContext* AcceptContext = (ServerAcceptContext*)Listener->Context; + *AcceptContext->NewConnection = new(std::nothrow) TestConnection(ConnectionHandle); + if (*AcceptContext->NewConnection == nullptr || !(*AcceptContext->NewConnection)->IsValid()) { + TEST_FAILURE("Failed to accept new TestConnection."); + delete *AcceptContext->NewConnection; + *AcceptContext->NewConnection = nullptr; + return false; + } + (*AcceptContext->NewConnection)->SetHasRandomLoss(Listener->GetHasRandomLoss()); + CxPlatEventSet(AcceptContext->NewConnectionReady); + return true; +} + #if defined(_ARM64_) || defined(_ARM64EC_) #pragma optimize("", off) #endif From 815ff66ca450bc730a58d542e37615c6aba806ba Mon Sep 17 00:00:00 2001 From: Jack He Date: Tue, 26 May 2026 14:50:58 -0700 Subject: [PATCH 32/41] add cibir/qtip matrix --- src/test/MsQuicTests.h | 4 +- src/test/bin/quic_gtest.cpp | 290 ++++++++++++++++++++++++++++----- src/test/lib/DataTest.cpp | 30 +++- src/test/lib/HandshakeTest.cpp | 30 +++- src/test/lib/TestListener.h | 2 + 5 files changed, 311 insertions(+), 45 deletions(-) diff --git a/src/test/MsQuicTests.h b/src/test/MsQuicTests.h index abf8f62c58..e56ac9a1cc 100644 --- a/src/test/MsQuicTests.h +++ b/src/test/MsQuicTests.h @@ -79,8 +79,8 @@ void QuicTestCloseConnBeforeStreamFlush(); void QuicTestGlobalParam(); #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES void QuicTestXdpMapConfigParam(); -void QuicTestXdpMapModeHandshake(_In_ int Family, _In_ uint16_t ServerPort, _In_ uint16_t ClientPort); -void QuicTestXdpMapModeDataTransfer(_In_ int Family, _In_ uint16_t ServerPort, _In_ uint16_t ClientPort); +void QuicTestXdpMapModeHandshake(_In_ int Family, _In_ uint16_t ServerPort, _In_ uint16_t ClientPort, _In_ bool UseCibir, _In_ bool UseQtip); +void QuicTestXdpMapModeDataTransfer(_In_ int Family, _In_ uint16_t ServerPort, _In_ uint16_t ClientPort, _In_ bool UseCibir, _In_ bool UseQtip); #endif void QuicTestCommonParam(); void QuicTestRegistrationParam(); diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index a7512a4fe3..95e8ad6f85 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -13,6 +13,7 @@ #include #include +#include #if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) #include @@ -3231,47 +3232,139 @@ INSTANTIATE_TEST_SUITE_P( // Each test reserves its own ports (keeping OS sockets open to prevent // reuse), creates XDP programs for those ports, and tears them down. // +// Test matrix: {IPv4, IPv6} x {no CIBIR, CIBIR} x {no QTIP, QTIP} +// + +struct XdpMapModeParams { + int Family; + bool UseCibir; + bool UseQtip; +}; -struct XdpMapMode : public ::testing::TestWithParam { - SOCKET PortSocks[2] = {INVALID_SOCKET, INVALID_SOCKET}; +std::ostream& operator << (std::ostream& o, const XdpMapModeParams& p) { + o << (p.Family == 4 ? "v4" : "v6"); + if (p.UseCibir) o << "/CIBIR"; + if (p.UseQtip) o << "/QTIP"; + if (!p.UseCibir && !p.UseQtip) o << "/Plain"; + return o; +} + +struct XdpMapMode : public ::testing::TestWithParam { + static constexpr int PortCount = 2; // server + client + SOCKET PortSocksUdp[PortCount] = {INVALID_SOCKET, INVALID_SOCKET}; + SOCKET PortSocksTcp[PortCount] = {INVALID_SOCKET, INVALID_SOCKET}; uint16_t ServerPort = 0; uint16_t ClientPort = 0; HANDLE XdpPrograms[XDP_MAP_MODE_MAX_INTERFACES][XDP_MAP_MODE_MAX_QUEUES] = {}; uint32_t QueueCounts[XDP_MAP_MODE_MAX_INTERFACES] = {}; bool WsaInitialized = false; + bool PreviousQtipSetting = false; + + // + // CIBIR test constants matching existing CIBIR tests. + // API buffer format: {offset, id_byte0, id_byte1, ...} + // + static constexpr uint8_t CibirApiId[] = { 0 /* offset */, 4, 3, 2, 1 }; + static constexpr uint8_t CibirApiIdLength = sizeof(CibirApiId); + // + // Internal XDP rule format: just the ID bytes, with offset computed as + // MsQuicLib.CidServerIdLength + 2. Default CidServerIdLength=0, so offset=2. + // + static constexpr uint8_t CibirIdData[] = { 4, 3, 2, 1 }; + static constexpr uint8_t CibirIdDataLength = sizeof(CibirIdData); + static constexpr uint8_t CibirCidOffset = 2; // CidServerIdLength(0) + 2 void SetUp() override { if (!UseXdpMapMode) return; + auto Params = GetParam(); + WSADATA WsaData; ASSERT_EQ(WSAStartup(MAKEWORD(2, 2), &WsaData), 0); WsaInitialized = true; // - // Reserve 2 ports (server + client) with OS sockets that stay open. + // Reserve ports (server + client) with OS sockets that stay open. + // Always reserve both UDP and TCP to prevent port stealing. + // Retry if the OS-assigned UDP port collides with an existing TCP + // binding, since UDP and TCP port spaces are independent. // - for (int i = 0; i < 2; i++) { - PortSocks[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - ASSERT_NE(PortSocks[i], INVALID_SOCKET) - << "Failed to create socket: WSAGetLastError=" << WSAGetLastError(); - struct sockaddr_in Addr = {}; - Addr.sin_family = AF_INET; - ASSERT_EQ(bind(PortSocks[i], (struct sockaddr*)&Addr, sizeof(Addr)), 0); - int AddrLen = sizeof(Addr); - ASSERT_EQ(getsockname(PortSocks[i], (struct sockaddr*)&Addr, &AddrLen), 0); - uint16_t Port = ntohs(Addr.sin_port); - ASSERT_NE(Port, (uint16_t)0); - if (i == 0) ServerPort = Port; else ClientPort = Port; + static const int MaxPortRetries = 1000; + for (int i = 0; i < PortCount; i++) { + bool PortReserved = false; + for (int Retry = 0; Retry < MaxPortRetries; Retry++) { + PortSocksUdp[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + ASSERT_NE(PortSocksUdp[i], INVALID_SOCKET) + << "Failed to create UDP socket: WSAGetLastError=" << WSAGetLastError(); + struct sockaddr_in Addr = {}; + Addr.sin_family = AF_INET; + ASSERT_EQ(bind(PortSocksUdp[i], (struct sockaddr*)&Addr, sizeof(Addr)), 0); + int AddrLen = sizeof(Addr); + ASSERT_EQ(getsockname(PortSocksUdp[i], (struct sockaddr*)&Addr, &AddrLen), 0); + uint16_t Port = ntohs(Addr.sin_port); + ASSERT_NE(Port, (uint16_t)0); + + // + // Reserve the same port number on TCP. This prevents another + // process from binding a TCP socket to this port, which is + // required for QTIP (QUIC-over-TCP) tests. + // + PortSocksTcp[i] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + ASSERT_NE(PortSocksTcp[i], INVALID_SOCKET) + << "Failed to create TCP socket: WSAGetLastError=" << WSAGetLastError(); + struct sockaddr_in TcpAddr = {}; + TcpAddr.sin_family = AF_INET; + TcpAddr.sin_port = htons(Port); + if (bind(PortSocksTcp[i], (struct sockaddr*)&TcpAddr, sizeof(TcpAddr)) == 0) { + if (i == 0) ServerPort = Port; else ClientPort = Port; + PortReserved = true; + break; + } + // + // TCP port collision. Close both sockets and retry with a + // new OS-assigned port. + // + printf("XDP Map Mode: port %u TCP collision (attempt %d/%d), retrying\n", + Port, Retry + 1, MaxPortRetries); + closesocket(PortSocksTcp[i]); + PortSocksTcp[i] = INVALID_SOCKET; + closesocket(PortSocksUdp[i]); + PortSocksUdp[i] = INVALID_SOCKET; + } + ASSERT_TRUE(PortReserved) + << "Failed to reserve a UDP+TCP port pair after " + << MaxPortRetries << " attempts"; } - printf("XDP Map Mode [%s]: ports Server=%u Client=%u\n", + printf("XDP Map Mode [%s]: ports Server=%u Client=%u CIBIR=%d QTIP=%d\n", ::testing::UnitTest::GetInstance()->current_test_info()->name(), - ServerPort, ClientPort); + ServerPort, ClientPort, Params.UseCibir, Params.UseQtip); + + // + // If QTIP is needed, enable it globally before creating XDP programs. + // + if (Params.UseQtip) { + MsQuicSettings Settings; + uint32_t SettingsSize = sizeof(Settings); + MsQuic->GetParam(nullptr, QUIC_PARAM_GLOBAL_SETTINGS, &SettingsSize, &Settings); + PreviousQtipSetting = Settings.QTIPEnabled; + if (!PreviousQtipSetting) { + Settings.SetQtipEnabled(true); + ASSERT_TRUE(QUIC_SUCCEEDED(Settings.SetGlobal())); + } + } // - // Create per-queue XDP programs mirroring MsQuic's internal - // CxPlatDpRawInterfaceUpdateRules: one program per queue with - // flags=0, targeting the XSKMAP for redirect-by-queue-id. + // Build XDP rules based on CIBIR/QTIP mode. + // + // Server (listener/wildcard) rules: + // - No CIBIR: XDP_MATCH_UDP_DST (+ XDP_MATCH_TCP_DST if QTIP) + // - CIBIR: QUIC_FLOW_SRC_CID + QUIC_FLOW_DST_CID + // (+ TCP_QUIC_FLOW_SRC_CID + TCP_QUIC_FLOW_DST_CID + TCP_CONTROL_DST if QTIP) + // + // Client (non-wildcard) rules: + // - Always: XDP_MATCH_UDP_DST (no QTIP) or XDP_MATCH_TCP_DST (QTIP) + // - CIBIR does NOT change client XDP matching. // static const XDP_HOOK_ID RxHook = { XDP_HOOK_L2, @@ -3281,19 +3374,98 @@ struct XdpMapMode : public ::testing::TestWithParam { for (uint32_t i = 0; i < XdpMapState.InterfaceCount; i++) { for (uint32_t q = 0; q < XDP_MAP_MODE_MAX_QUEUES; q++) { - XDP_RULE Rules[2] = {}; + XDP_RULE Rules[8] = {}; + uint8_t RulesSize = 0; - Rules[0].Match = XDP_MATCH_UDP_DST; - Rules[0].Pattern.Port = htons(ServerPort); - Rules[0].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[0].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[0].Redirect.Target = XdpMapState.XskMaps[i]; + // + // Server port rules. + // + if (Params.UseCibir) { + Rules[RulesSize].Match = XDP_MATCH_QUIC_FLOW_SRC_CID; + Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); + Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; + Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; + memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + + Rules[RulesSize].Match = XDP_MATCH_QUIC_FLOW_DST_CID; + Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); + Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; + Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; + memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + + if (Params.UseQtip) { + Rules[RulesSize].Match = XDP_MATCH_TCP_QUIC_FLOW_SRC_CID; + Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); + Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; + Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; + memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + + Rules[RulesSize].Match = XDP_MATCH_TCP_QUIC_FLOW_DST_CID; + Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); + Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; + Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; + memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + + Rules[RulesSize].Match = XDP_MATCH_TCP_CONTROL_DST; + Rules[RulesSize].Pattern.Port = htons(ServerPort); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + } + } else { + Rules[RulesSize].Match = XDP_MATCH_UDP_DST; + Rules[RulesSize].Pattern.Port = htons(ServerPort); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + + if (Params.UseQtip) { + Rules[RulesSize].Match = XDP_MATCH_TCP_DST; + Rules[RulesSize].Pattern.Port = htons(ServerPort); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + } + } - Rules[1].Match = XDP_MATCH_UDP_DST; - Rules[1].Pattern.Port = htons(ClientPort); - Rules[1].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[1].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[1].Redirect.Target = XdpMapState.XskMaps[i]; + // + // Client port rules. CIBIR does not change client XDP matching. + // QTIP clients use TCP-only; non-QTIP clients use UDP-only. + // + if (Params.UseQtip) { + Rules[RulesSize].Match = XDP_MATCH_TCP_DST; + Rules[RulesSize].Pattern.Port = htons(ClientPort); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + } else { + Rules[RulesSize].Match = XDP_MATCH_UDP_DST; + Rules[RulesSize].Pattern.Port = htons(ClientPort); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + } HRESULT Hr = XdpCreateProgram( XdpMapState.IfIndices[i], @@ -3301,7 +3473,7 @@ struct XdpMapMode : public ::testing::TestWithParam { q, XDP_CREATE_PROGRAM_FLAG_NONE, Rules, - 2, + RulesSize, &XdpPrograms[i][q]); if (FAILED(Hr)) { // @@ -3330,44 +3502,80 @@ struct XdpMapMode : public ::testing::TestWithParam { } } } - for (int i = 0; i < 2; i++) { - if (PortSocks[i] != INVALID_SOCKET) { - closesocket(PortSocks[i]); - PortSocks[i] = INVALID_SOCKET; + for (int i = 0; i < PortCount; i++) { + if (PortSocksUdp[i] != INVALID_SOCKET) { + closesocket(PortSocksUdp[i]); + PortSocksUdp[i] = INVALID_SOCKET; + } + if (PortSocksTcp[i] != INVALID_SOCKET) { + closesocket(PortSocksTcp[i]); + PortSocksTcp[i] = INVALID_SOCKET; } } + // + // Restore QTIP global setting. + // + if (GetParam().UseQtip && !PreviousQtipSetting) { + MsQuicSettings Settings; + Settings.SetQtipEnabled(false); + Settings.SetGlobal(); + } if (WsaInitialized) { WSACleanup(); } } }; +constexpr uint8_t XdpMapMode::CibirApiId[]; +constexpr uint8_t XdpMapMode::CibirIdData[]; + TEST_P(XdpMapMode, Handshake) { if (!UseXdpMapMode) { GTEST_SKIP() << "XDP Map Mode not enabled (use --xdpMapMode)"; } + auto Params = GetParam(); TestLogger Logger("QuicTestXdpMapModeHandshake"); QuicTestXdpMapModeHandshake( - GetParam(), + Params.Family, ServerPort, - ClientPort); + ClientPort, + Params.UseCibir, + Params.UseQtip); } TEST_P(XdpMapMode, DataTransfer) { if (!UseXdpMapMode) { GTEST_SKIP() << "XDP Map Mode not enabled (use --xdpMapMode)"; } + auto Params = GetParam(); TestLogger Logger("QuicTestXdpMapModeDataTransfer"); QuicTestXdpMapModeDataTransfer( - GetParam(), + Params.Family, ServerPort, - ClientPort); + ClientPort, + Params.UseCibir, + Params.UseQtip); +} + +static std::vector GenerateXdpMapModeParams() { + std::vector list; + for (int Family : { 4, 6 }) + for (bool UseCibir : { false, true }) + for (bool UseQtip : { false, true }) + list.push_back({ Family, UseCibir, UseQtip }); + return list; } INSTANTIATE_TEST_SUITE_P( XdpMapMode, XdpMapMode, - ::testing::Values(4, 6)); + ::testing::ValuesIn(GenerateXdpMapModeParams()), + [](const ::testing::TestParamInfo& info) { + std::string name = (info.param.Family == 4 ? "v4" : "v6"); + name += info.param.UseCibir ? "_CIBIR" : "_NoCIBIR"; + name += info.param.UseQtip ? "_QTIP" : "_NoQTIP"; + return name; + }); #endif // _WIN32 && QUIC_API_ENABLE_PREVIEW_FEATURES int main(int argc, char** argv) { diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index e73548a5fa..4826b11985 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -5483,9 +5483,12 @@ void QuicTestXdpMapModeDataTransfer( _In_ int Family, _In_ uint16_t ServerPort, - _In_ uint16_t ClientPort + _In_ uint16_t ClientPort, + _In_ bool UseCibir, + _In_ bool UseQtip ) { + UNREFERENCED_PARAMETER(UseQtip); // QTIP is configured globally by the fixture QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; @@ -5505,10 +5508,25 @@ QuicTestXdpMapModeDataTransfer( MsQuicConfiguration ClientConfiguration(Registration, Alpn, Settings, ClientCredConfig); TEST_TRUE(ClientConfiguration.IsValid()); + // + // CIBIR ID in API format: {offset, id_byte0, ...} + // + const uint8_t CibirId[] = { 0 /* offset */, 4, 3, 2, 1 }; + const uint8_t CibirIdLength = sizeof(CibirId); + { TestListener Listener(Registration, ListenerAcceptConnectionBasic, ServerConfiguration); TEST_TRUE(Listener.IsValid()); + if (UseCibir) { + TEST_QUIC_SUCCEEDED( + MsQuic->SetParam( + Listener.GetListener(), + QUIC_PARAM_LISTENER_CIBIR_ID, + CibirIdLength, + CibirId)); + } + QuicAddr ServerLocalAddr(QuicAddrFamily); QuicAddrSetPort(&ServerLocalAddr.SockAddr, ServerPort); TEST_QUIC_SUCCEEDED(Listener.Start(Alpn, &ServerLocalAddr.SockAddr)); @@ -5523,6 +5541,16 @@ QuicTestXdpMapModeDataTransfer( TestConnection Client(Registration); TEST_TRUE(Client.IsValid()); + if (UseCibir) { + TEST_QUIC_SUCCEEDED(Client.SetShareUdpBinding(true)); + TEST_QUIC_SUCCEEDED( + MsQuic->SetParam( + Client.GetConnection(), + QUIC_PARAM_CONN_CIBIR_ID, + CibirIdLength, + CibirId)); + } + QuicAddr ClientLocalAddr(QuicAddrFamily); QuicAddrSetToDuoNicClient(&ClientLocalAddr.SockAddr); QuicAddrSetPort(&ClientLocalAddr.SockAddr, ClientPort); diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index 6232ab0b0c..c1ea71ab74 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -4919,9 +4919,12 @@ void QuicTestXdpMapModeHandshake( _In_ int Family, _In_ uint16_t ServerPort, - _In_ uint16_t ClientPort + _In_ uint16_t ClientPort, + _In_ bool UseCibir, + _In_ bool UseQtip ) { + UNREFERENCED_PARAMETER(UseQtip); // QTIP is configured globally by the fixture QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; @@ -4941,10 +4944,25 @@ QuicTestXdpMapModeHandshake( MsQuicConfiguration ClientConfiguration(Registration, Alpn, Settings, ClientCredConfig); TEST_TRUE(ClientConfiguration.IsValid()); + // + // CIBIR ID in API format: {offset, id_byte0, ...} + // + const uint8_t CibirId[] = { 0 /* offset */, 4, 3, 2, 1 }; + const uint8_t CibirIdLength = sizeof(CibirId); + { TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); TEST_TRUE(Listener.IsValid()); + if (UseCibir) { + TEST_QUIC_SUCCEEDED( + MsQuic->SetParam( + Listener.GetListener(), + QUIC_PARAM_LISTENER_CIBIR_ID, + CibirIdLength, + CibirId)); + } + QuicAddr ServerLocalAddr(QuicAddrFamily); QuicAddrSetPort(&ServerLocalAddr.SockAddr, ServerPort); TEST_QUIC_SUCCEEDED(Listener.Start(Alpn, &ServerLocalAddr.SockAddr)); @@ -4959,6 +4977,16 @@ QuicTestXdpMapModeHandshake( TestConnection Client(Registration); TEST_TRUE(Client.IsValid()); + if (UseCibir) { + TEST_QUIC_SUCCEEDED(Client.SetShareUdpBinding(true)); + TEST_QUIC_SUCCEEDED( + MsQuic->SetParam( + Client.GetConnection(), + QUIC_PARAM_CONN_CIBIR_ID, + CibirIdLength, + CibirId)); + } + QuicAddr ClientLocalAddr(QuicAddrFamily); QuicAddrSetToDuoNicClient(&ClientLocalAddr.SockAddr); QuicAddrSetPort(&ClientLocalAddr.SockAddr, ClientPort); diff --git a/src/test/lib/TestListener.h b/src/test/lib/TestListener.h index 6842612a1a..3e7783f260 100644 --- a/src/test/lib/TestListener.h +++ b/src/test/lib/TestListener.h @@ -106,6 +106,8 @@ class TestListener QUIC_STATUS GetLocalAddr(_Out_ QuicAddr &localAddr); QUIC_STATUS GetStatistics(_Out_ QUIC_LISTENER_STATISTICS &stats); + HQUIC GetListener() const { return QuicListener; } + bool GetHasRandomLoss() const { return HasRandomLoss; } void SetHasRandomLoss(bool Value) { HasRandomLoss = Value; } }; From 39ce0e6545c6639a2e01f04d6d8aba48ef5abbe8 Mon Sep 17 00:00:00 2001 From: Jack He Date: Tue, 26 May 2026 14:58:43 -0700 Subject: [PATCH 33/41] add CI to target map mode explicitly --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 62bac83c91..2964b4513d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -143,6 +143,7 @@ jobs: { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", sanitize: "-SanitizeAddress", build: "-Test", log: "Full.Verbose" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", sanitize: "-SanitizeAddress", build: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", qtip: "-UseQtip", sanitize: "-SanitizeAddress", build: "-Test" }, + { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", xdpmapmode: "-XdpMapMode", sanitize: "-SanitizeAddress", build: "-Test", testfilter: "XdpMapMode*" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "quictls", build: "-Test", log: "Full.Verbose" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "quictls", xdp: "-UseXdp", build: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "quictls", xdp: "-UseXdp", qtip: "-UseQtip", build: "-Test" }, @@ -196,17 +197,17 @@ jobs: if: matrix.vec.os == 'WinServerPrerelease' shell: pwsh timeout-minutes: 120 - run: scripts/test.ps1 -Config ${{ matrix.vec.config }} -Arch ${{ matrix.vec.arch }} -Tls ${{ matrix.vec.tls }} -GHA -LogProfile ${{ matrix.vec.log || (inputs.log_level || 'Full.Light') }} -GenerateXmlResults ${{ matrix.vec.xdp }} ${{ matrix.vec.qtip }} ${{ inputs.filter && '-Filter' }} ${{ inputs.filter || '' }} + run: scripts/test.ps1 -Config ${{ matrix.vec.config }} -Arch ${{ matrix.vec.arch }} -Tls ${{ matrix.vec.tls }} -GHA -LogProfile ${{ matrix.vec.log || (inputs.log_level || 'Full.Light') }} -GenerateXmlResults ${{ matrix.vec.xdp }} ${{ matrix.vec.qtip }} ${{ matrix.vec.xdpmapmode }} ${{ (matrix.vec.testfilter || inputs.filter) && '-Filter' || '' }} ${{ matrix.vec.testfilter || inputs.filter || '' }} - name: Test if: matrix.vec.os != 'WinServerPrerelease' shell: pwsh timeout-minutes: 120 - run: scripts/test.ps1 -Config ${{ matrix.vec.config }} -Arch ${{ matrix.vec.arch }} -Tls ${{ matrix.vec.tls }} -OsRunner ${{ matrix.vec.os }} -GHA -LogProfile ${{ matrix.vec.log || (inputs.log_level || 'Full.Light') }} -GenerateXmlResults ${{ matrix.vec.xdp }} ${{ matrix.vec.qtip }} ${{ inputs.filter && '-Filter' }} ${{ inputs.filter || '' }} + run: scripts/test.ps1 -Config ${{ matrix.vec.config }} -Arch ${{ matrix.vec.arch }} -Tls ${{ matrix.vec.tls }} -OsRunner ${{ matrix.vec.os }} -GHA -LogProfile ${{ matrix.vec.log || (inputs.log_level || 'Full.Light') }} -GenerateXmlResults ${{ matrix.vec.xdp }} ${{ matrix.vec.qtip }} ${{ matrix.vec.xdpmapmode }} ${{ (matrix.vec.testfilter || inputs.filter) && '-Filter' || '' }} ${{ matrix.vec.testfilter || inputs.filter || '' }} - name: Upload on Failure or Cancellation uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a if: failure() || cancelled() with: - name: BVT-${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.xdp }}${{ matrix.vec.iouring }}${{ matrix.vec.qtip }}${{ matrix.vec.systemcrypto }}${{ matrix.vec.sanitize }} + name: BVT-${{ matrix.vec.config }}-${{ matrix.vec.plat }}-${{ matrix.vec.os }}-${{ matrix.vec.arch }}-${{ matrix.vec.tls }}${{ matrix.vec.xdp }}${{ matrix.vec.iouring }}${{ matrix.vec.qtip }}${{ matrix.vec.xdpmapmode }}${{ matrix.vec.systemcrypto }}${{ matrix.vec.sanitize }} path: artifacts bvt-kernel: From 3d9d478f395d6f1eea072d202a10b180e595e63f Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 28 May 2026 16:34:01 -0700 Subject: [PATCH 34/41] comment out map job --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2964b4513d..7b158c4703 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -143,7 +143,7 @@ jobs: { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", sanitize: "-SanitizeAddress", build: "-Test", log: "Full.Verbose" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", sanitize: "-SanitizeAddress", build: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", qtip: "-UseQtip", sanitize: "-SanitizeAddress", build: "-Test" }, - { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", xdpmapmode: "-XdpMapMode", sanitize: "-SanitizeAddress", build: "-Test", testfilter: "XdpMapMode*" }, + # { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", xdpmapmode: "-XdpMapMode", sanitize: "-SanitizeAddress", build: "-Test", testfilter: "XdpMapMode*" }, # Disabled until CI has XDP driver with map mode support { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "quictls", build: "-Test", log: "Full.Verbose" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "quictls", xdp: "-UseXdp", build: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "quictls", xdp: "-UseXdp", qtip: "-UseQtip", build: "-Test" }, From d5246669eec4be6ce110dd26d902eb34e9fb2ab6 Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 11 Jun 2026 13:03:55 -0700 Subject: [PATCH 35/41] move stuff out of the test harness --- src/test/bin/CMakeLists.txt | 4 + src/test/bin/XdpMapModeHelpers.cpp | 339 +++++++++++++++++++++++++++ src/test/bin/XdpMapModeHelpers.h | 76 +++++++ src/test/bin/quic_gtest.cpp | 352 +---------------------------- 4 files changed, 431 insertions(+), 340 deletions(-) create mode 100644 src/test/bin/XdpMapModeHelpers.cpp create mode 100644 src/test/bin/XdpMapModeHelpers.h diff --git a/src/test/bin/CMakeLists.txt b/src/test/bin/CMakeLists.txt index 1e0352d5b6..7f1960d32d 100644 --- a/src/test/bin/CMakeLists.txt +++ b/src/test/bin/CMakeLists.txt @@ -6,6 +6,10 @@ set(SOURCES quic_gtest.h ) +if (WIN32) + list(APPEND SOURCES XdpMapModeHelpers.cpp XdpMapModeHelpers.h) +endif() + add_executable(msquictest ${SOURCES}) target_include_directories(msquictest PRIVATE ${PROJECT_SOURCE_DIR}/src/test) diff --git a/src/test/bin/XdpMapModeHelpers.cpp b/src/test/bin/XdpMapModeHelpers.cpp new file mode 100644 index 0000000000..c18a191d96 --- /dev/null +++ b/src/test/bin/XdpMapModeHelpers.cpp @@ -0,0 +1,339 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + Implementation of the XDP map mode rule configuration helpers. Keeping this + out of the test list file (quic_gtest.cpp) makes the per-test XDP rule setup + explicit at the start of each test rather than hidden inside a fixture. + +--*/ + +#include "quic_platform.h" +#include "MsQuicTests.h" +#include "msquichelper.h" +#undef min // gtest headers conflict with previous definitions of min/max. +#undef max +#include "gtest/gtest.h" +#include "XdpMapModeHelpers.h" + +#if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + +#include +#define XDP_API_VERSION 3 +#define XDP_INCLUDE_WINCOMMON +#include +#include + +extern const MsQuicApi* MsQuic; + +XdpMapModeState XdpMapState = {}; + +namespace { + +// +// CIBIR test constants matching the CIBIR id used by the map mode tests. +// Internal XDP rule format: just the id bytes, with offset computed as +// MsQuicLib.CidServerIdLength + 2. Default CidServerIdLength=0, so offset=2. +// +constexpr uint8_t CibirIdData[] = { 4, 3, 2, 1 }; +constexpr uint8_t CibirIdDataLength = sizeof(CibirIdData); +constexpr uint8_t CibirCidOffset = 2; // CidServerIdLength(0) + 2 + +} // namespace + +bool +DiscoverDuoNicInterfaces( + _Out_writes_(XDP_MAP_MODE_MAX_INTERFACES) uint32_t* IfIndices, + _Out_ uint32_t* Count + ) +{ + *Count = 0; + ULONG Flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | + GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; + ULONG BufSize = 0; + GetAdaptersAddresses(AF_INET, Flags, NULL, NULL, &BufSize); + if (BufSize == 0) return false; + + auto Adapters = (PIP_ADAPTER_ADDRESSES)malloc(BufSize); + if (!Adapters) return false; + + if (GetAdaptersAddresses(AF_INET, Flags, NULL, Adapters, &BufSize) != NO_ERROR) { + free(Adapters); + return false; + } + + for (auto Adapter = Adapters; Adapter && *Count < XDP_MAP_MODE_MAX_INTERFACES; Adapter = Adapter->Next) { + if (Adapter->IfType != IF_TYPE_ETHERNET_CSMACD || + Adapter->OperStatus != IfOperStatusUp) { + continue; + } + for (auto Unicast = Adapter->FirstUnicastAddress; Unicast; Unicast = Unicast->Next) { + if (Unicast->Address.lpSockaddr->sa_family != AF_INET) continue; + auto* Sin = (SOCKADDR_IN*)Unicast->Address.lpSockaddr; + ULONG Addr = ntohl(Sin->sin_addr.S_un.S_addr); + // 192.168.1.11 or 192.168.1.12 + if (Addr == 0xC0A8010B || Addr == 0xC0A8010C) { + IfIndices[*Count] = Adapter->IfIndex; + (*Count)++; + break; + } + } + } + free(Adapters); + return *Count > 0; +} + +void +XdpMapModeRuleScope::Setup(bool UseCibirParam, bool UseQtipParam) +{ + UseQtip = UseQtipParam; + + WSADATA WsaData; + ASSERT_EQ(WSAStartup(MAKEWORD(2, 2), &WsaData), 0); + WsaInitialized = true; + + // + // Reserve ports (server + client) with OS sockets that stay open. + // Always reserve both UDP and TCP to prevent port stealing. + // Retry if the OS-assigned UDP port collides with an existing TCP + // binding, since UDP and TCP port spaces are independent. + // + static const int MaxPortRetries = 1000; + for (int i = 0; i < PortCount; i++) { + bool PortReserved = false; + for (int Retry = 0; Retry < MaxPortRetries; Retry++) { + PortSocksUdp[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + ASSERT_NE(PortSocksUdp[i], INVALID_SOCKET) + << "Failed to create UDP socket: WSAGetLastError=" << WSAGetLastError(); + struct sockaddr_in Addr = {}; + Addr.sin_family = AF_INET; + ASSERT_EQ(bind(PortSocksUdp[i], (struct sockaddr*)&Addr, sizeof(Addr)), 0); + int AddrLen = sizeof(Addr); + ASSERT_EQ(getsockname(PortSocksUdp[i], (struct sockaddr*)&Addr, &AddrLen), 0); + uint16_t Port = ntohs(Addr.sin_port); + ASSERT_NE(Port, (uint16_t)0); + + // + // Reserve the same port number on TCP. This prevents another + // process from binding a TCP socket to this port, which is + // required for QTIP (QUIC-over-TCP) tests. + // + PortSocksTcp[i] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + ASSERT_NE(PortSocksTcp[i], INVALID_SOCKET) + << "Failed to create TCP socket: WSAGetLastError=" << WSAGetLastError(); + struct sockaddr_in TcpAddr = {}; + TcpAddr.sin_family = AF_INET; + TcpAddr.sin_port = htons(Port); + if (bind(PortSocksTcp[i], (struct sockaddr*)&TcpAddr, sizeof(TcpAddr)) == 0) { + if (i == 0) ServerPort = Port; else ClientPort = Port; + PortReserved = true; + break; + } + // + // TCP port collision. Close both sockets and retry with a + // new OS-assigned port. + // + printf("XDP Map Mode: port %u TCP collision (attempt %d/%d), retrying\n", + Port, Retry + 1, MaxPortRetries); + closesocket(PortSocksTcp[i]); + PortSocksTcp[i] = INVALID_SOCKET; + closesocket(PortSocksUdp[i]); + PortSocksUdp[i] = INVALID_SOCKET; + } + ASSERT_TRUE(PortReserved) + << "Failed to reserve a UDP+TCP port pair after " + << MaxPortRetries << " attempts"; + } + + printf("XDP Map Mode [%s]: ports Server=%u Client=%u CIBIR=%d QTIP=%d\n", + ::testing::UnitTest::GetInstance()->current_test_info()->name(), + ServerPort, ClientPort, UseCibirParam, UseQtipParam); + + // + // If QTIP is needed, enable it globally before creating XDP programs. + // + if (UseQtip) { + MsQuicSettings Settings; + uint32_t SettingsSize = sizeof(Settings); + MsQuic->GetParam(nullptr, QUIC_PARAM_GLOBAL_SETTINGS, &SettingsSize, &Settings); + PreviousQtipSetting = Settings.QTIPEnabled; + if (!PreviousQtipSetting) { + Settings.SetQtipEnabled(true); + ASSERT_TRUE(QUIC_SUCCEEDED(Settings.SetGlobal())); + } + } + + // + // Build XDP rules based on CIBIR/QTIP mode. + // + // Server (listener/wildcard) rules: + // - No CIBIR: XDP_MATCH_UDP_DST (+ XDP_MATCH_TCP_DST if QTIP) + // - CIBIR: QUIC_FLOW_SRC_CID + QUIC_FLOW_DST_CID + // (+ TCP_QUIC_FLOW_SRC_CID + TCP_QUIC_FLOW_DST_CID + TCP_CONTROL_DST if QTIP) + // + // Client (non-wildcard) rules: + // - Always: XDP_MATCH_UDP_DST (no QTIP) or XDP_MATCH_TCP_DST (QTIP) + // - CIBIR does NOT change client XDP matching. + // + static const XDP_HOOK_ID RxHook = { + XDP_HOOK_L2, + XDP_HOOK_RX, + XDP_HOOK_INSPECT, + }; + + for (uint32_t i = 0; i < XdpMapState.InterfaceCount; i++) { + for (uint32_t q = 0; q < XDP_MAP_MODE_MAX_QUEUES; q++) { + XDP_RULE Rules[8] = {}; + uint8_t RulesSize = 0; + + // + // Server port rules. + // + if (UseCibirParam) { + Rules[RulesSize].Match = XDP_MATCH_QUIC_FLOW_SRC_CID; + Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); + Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; + Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; + memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + + Rules[RulesSize].Match = XDP_MATCH_QUIC_FLOW_DST_CID; + Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); + Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; + Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; + memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + + if (UseQtip) { + Rules[RulesSize].Match = XDP_MATCH_TCP_QUIC_FLOW_SRC_CID; + Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); + Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; + Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; + memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + + Rules[RulesSize].Match = XDP_MATCH_TCP_QUIC_FLOW_DST_CID; + Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); + Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; + Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; + memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + + Rules[RulesSize].Match = XDP_MATCH_TCP_CONTROL_DST; + Rules[RulesSize].Pattern.Port = htons(ServerPort); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + } + } else { + Rules[RulesSize].Match = XDP_MATCH_UDP_DST; + Rules[RulesSize].Pattern.Port = htons(ServerPort); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + + if (UseQtip) { + Rules[RulesSize].Match = XDP_MATCH_TCP_DST; + Rules[RulesSize].Pattern.Port = htons(ServerPort); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + } + } + + // + // Client port rules. CIBIR does not change client XDP matching. + // QTIP clients use TCP-only; non-QTIP clients use UDP-only. + // + if (UseQtip) { + Rules[RulesSize].Match = XDP_MATCH_TCP_DST; + Rules[RulesSize].Pattern.Port = htons(ClientPort); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + } else { + Rules[RulesSize].Match = XDP_MATCH_UDP_DST; + Rules[RulesSize].Pattern.Port = htons(ClientPort); + Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; + Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; + Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; + RulesSize++; + } + + HRESULT Hr = XdpCreateProgram( + XdpMapState.IfIndices[i], + &RxHook, + q, + XDP_CREATE_PROGRAM_FLAG_NONE, + Rules, + RulesSize, + &XdpPrograms[i][q]); + if (FAILED(Hr)) { + // + // No more queues on this interface. + // + QueueCounts[i] = q; + break; + } + } + printf("XDP Map Mode: IfIndex=%u created %u per-queue programs\n", + XdpMapState.IfIndices[i], QueueCounts[i]); + ASSERT_GT(QueueCounts[i], (uint32_t)0) + << "Failed to create any XDP programs for IfIndex=" + << XdpMapState.IfIndices[i]; + } +} + +XdpMapModeRuleScope::~XdpMapModeRuleScope() +{ + for (uint32_t i = 0; i < XdpMapState.InterfaceCount; i++) { + for (uint32_t q = 0; q < QueueCounts[i]; q++) { + if (XdpPrograms[i][q]) { + CloseHandle(XdpPrograms[i][q]); + XdpPrograms[i][q] = nullptr; + } + } + } + for (int i = 0; i < PortCount; i++) { + if (PortSocksUdp[i] != INVALID_SOCKET) { + closesocket(PortSocksUdp[i]); + PortSocksUdp[i] = INVALID_SOCKET; + } + if (PortSocksTcp[i] != INVALID_SOCKET) { + closesocket(PortSocksTcp[i]); + PortSocksTcp[i] = INVALID_SOCKET; + } + } + // + // Restore QTIP global setting. + // + if (UseQtip && !PreviousQtipSetting) { + MsQuicSettings Settings; + Settings.SetQtipEnabled(false); + Settings.SetGlobal(); + } + if (WsaInitialized) { + WSACleanup(); + } +} + +#endif // _WIN32 && QUIC_API_ENABLE_PREVIEW_FEATURES diff --git a/src/test/bin/XdpMapModeHelpers.h b/src/test/bin/XdpMapModeHelpers.h new file mode 100644 index 0000000000..bbacabaada --- /dev/null +++ b/src/test/bin/XdpMapModeHelpers.h @@ -0,0 +1,76 @@ +/*++ + + Copyright (c) Microsoft Corporation. + Licensed under the MIT License. + +Abstract: + + Helpers for configuring the XDP redirect rules used by the XDP map mode + tests. The rule setup is intentionally kept out of the test list file and is + invoked explicitly from the start of each test through an RAII scope, so the + important per-test setup stays visible in the test body instead of being + hidden in a fixture. + +--*/ + +#pragma once + +#if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) + +#define XDP_MAP_MODE_MAX_INTERFACES 2 +#define XDP_MAP_MODE_MAX_QUEUES 64 + +struct XdpMapModeState { + uint32_t InterfaceCount; + uint32_t IfIndices[XDP_MAP_MODE_MAX_INTERFACES]; + HANDLE XskMaps[XDP_MAP_MODE_MAX_INTERFACES]; +}; + +extern XdpMapModeState XdpMapState; + +// +// Discover DuoNic interface indices by enumerating Ethernet adapters with +// known DuoNic IPv4 addresses (192.168.1.11 and 192.168.1.12). +// +bool +DiscoverDuoNicInterfaces( + _Out_writes_(XDP_MAP_MODE_MAX_INTERFACES) uint32_t* IfIndices, + _Out_ uint32_t* Count + ); + +// +// RAII helper that reserves the server/client ports and installs the per-queue +// XDP redirect programs for a single XDP map mode test. Construct it at the +// start of the test body and call Setup() via ASSERT_NO_FATAL_FAILURE(...) so +// the setup is visible; the destructor tears everything back down. +// +class XdpMapModeRuleScope { +public: + XdpMapModeRuleScope() = default; + ~XdpMapModeRuleScope(); + + XdpMapModeRuleScope(const XdpMapModeRuleScope&) = delete; + XdpMapModeRuleScope& operator=(const XdpMapModeRuleScope&) = delete; + + // + // Reserves the ports and creates the XDP programs for the given mode. + // + void Setup(bool UseCibir, bool UseQtip); + + uint16_t GetServerPort() const { return ServerPort; } + uint16_t GetClientPort() const { return ClientPort; } + +private: + static constexpr int PortCount = 2; // server + client + SOCKET PortSocksUdp[PortCount] = {INVALID_SOCKET, INVALID_SOCKET}; + SOCKET PortSocksTcp[PortCount] = {INVALID_SOCKET, INVALID_SOCKET}; + uint16_t ServerPort = 0; + uint16_t ClientPort = 0; + HANDLE XdpPrograms[XDP_MAP_MODE_MAX_INTERFACES][XDP_MAP_MODE_MAX_QUEUES] = {}; + uint32_t QueueCounts[XDP_MAP_MODE_MAX_INTERFACES] = {}; + bool WsaInitialized = false; + bool UseQtip = false; + bool PreviousQtipSetting = false; +}; + +#endif // _WIN32 && QUIC_API_ENABLE_PREVIEW_FEATURES diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 95e8ad6f85..3702ba8909 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -21,6 +21,7 @@ #define XDP_INCLUDE_WINCOMMON #include #include +#include "XdpMapModeHelpers.h" #endif #ifdef QUIC_TEST_DATAPATH_HOOKS_ENABLED @@ -47,65 +48,6 @@ QUIC_CREDENTIAL_CONFIG ServerSelfSignedCredConfigClientAuth; QUIC_CREDENTIAL_CONFIG ClientCertCredConfig; QuicDriverClient DriverClient; -#if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) -// -// XDP map mode state. Populated during SetUp when --xdpMapMode is used. -// -#define XDP_MAP_MODE_MAX_INTERFACES 2 -#define XDP_MAP_MODE_MAX_QUEUES 64 -struct XdpMapModeState { - uint32_t InterfaceCount; - uint32_t IfIndices[XDP_MAP_MODE_MAX_INTERFACES]; - HANDLE XskMaps[XDP_MAP_MODE_MAX_INTERFACES]; -} XdpMapState = {}; - -// -// Discover DuoNic interface indices by enumerating Ethernet adapters with -// known DuoNic IPv4 addresses (192.168.1.11 and 192.168.1.12). -// -static bool -DiscoverDuoNicInterfaces( - _Out_writes_(XDP_MAP_MODE_MAX_INTERFACES) uint32_t* IfIndices, - _Out_ uint32_t* Count - ) -{ - *Count = 0; - ULONG Flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | - GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; - ULONG BufSize = 0; - GetAdaptersAddresses(AF_INET, Flags, NULL, NULL, &BufSize); - if (BufSize == 0) return false; - - auto Adapters = (PIP_ADAPTER_ADDRESSES)malloc(BufSize); - if (!Adapters) return false; - - if (GetAdaptersAddresses(AF_INET, Flags, NULL, Adapters, &BufSize) != NO_ERROR) { - free(Adapters); - return false; - } - - for (auto Adapter = Adapters; Adapter && *Count < XDP_MAP_MODE_MAX_INTERFACES; Adapter = Adapter->Next) { - if (Adapter->IfType != IF_TYPE_ETHERNET_CSMACD || - Adapter->OperStatus != IfOperStatusUp) { - continue; - } - for (auto Unicast = Adapter->FirstUnicastAddress; Unicast; Unicast = Unicast->Next) { - if (Unicast->Address.lpSockaddr->sa_family != AF_INET) continue; - auto* Sin = (SOCKADDR_IN*)Unicast->Address.lpSockaddr; - ULONG Addr = ntohl(Sin->sin_addr.S_un.S_addr); - // 192.168.1.11 or 192.168.1.12 - if (Addr == 0xC0A8010B || Addr == 0xC0A8010C) { - IfIndices[*Count] = Adapter->IfIndex; - (*Count)++; - break; - } - } - } - free(Adapters); - return *Count > 0; -} -#endif // _WIN32 && QUIC_API_ENABLE_PREVIEW_FEATURES - // // These are explicitly passed in as the name of the GitHub/Azure runners. // @@ -3249,296 +3191,24 @@ std::ostream& operator << (std::ostream& o, const XdpMapModeParams& p) { return o; } +// +// Struct needed for GTEST parameterization. +// struct XdpMapMode : public ::testing::TestWithParam { - static constexpr int PortCount = 2; // server + client - SOCKET PortSocksUdp[PortCount] = {INVALID_SOCKET, INVALID_SOCKET}; - SOCKET PortSocksTcp[PortCount] = {INVALID_SOCKET, INVALID_SOCKET}; - uint16_t ServerPort = 0; - uint16_t ClientPort = 0; - HANDLE XdpPrograms[XDP_MAP_MODE_MAX_INTERFACES][XDP_MAP_MODE_MAX_QUEUES] = {}; - uint32_t QueueCounts[XDP_MAP_MODE_MAX_INTERFACES] = {}; - bool WsaInitialized = false; - bool PreviousQtipSetting = false; - - // - // CIBIR test constants matching existing CIBIR tests. - // API buffer format: {offset, id_byte0, id_byte1, ...} - // - static constexpr uint8_t CibirApiId[] = { 0 /* offset */, 4, 3, 2, 1 }; - static constexpr uint8_t CibirApiIdLength = sizeof(CibirApiId); - // - // Internal XDP rule format: just the ID bytes, with offset computed as - // MsQuicLib.CidServerIdLength + 2. Default CidServerIdLength=0, so offset=2. - // - static constexpr uint8_t CibirIdData[] = { 4, 3, 2, 1 }; - static constexpr uint8_t CibirIdDataLength = sizeof(CibirIdData); - static constexpr uint8_t CibirCidOffset = 2; // CidServerIdLength(0) + 2 - - void SetUp() override { - if (!UseXdpMapMode) return; - - auto Params = GetParam(); - - WSADATA WsaData; - ASSERT_EQ(WSAStartup(MAKEWORD(2, 2), &WsaData), 0); - WsaInitialized = true; - - // - // Reserve ports (server + client) with OS sockets that stay open. - // Always reserve both UDP and TCP to prevent port stealing. - // Retry if the OS-assigned UDP port collides with an existing TCP - // binding, since UDP and TCP port spaces are independent. - // - static const int MaxPortRetries = 1000; - for (int i = 0; i < PortCount; i++) { - bool PortReserved = false; - for (int Retry = 0; Retry < MaxPortRetries; Retry++) { - PortSocksUdp[i] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - ASSERT_NE(PortSocksUdp[i], INVALID_SOCKET) - << "Failed to create UDP socket: WSAGetLastError=" << WSAGetLastError(); - struct sockaddr_in Addr = {}; - Addr.sin_family = AF_INET; - ASSERT_EQ(bind(PortSocksUdp[i], (struct sockaddr*)&Addr, sizeof(Addr)), 0); - int AddrLen = sizeof(Addr); - ASSERT_EQ(getsockname(PortSocksUdp[i], (struct sockaddr*)&Addr, &AddrLen), 0); - uint16_t Port = ntohs(Addr.sin_port); - ASSERT_NE(Port, (uint16_t)0); - - // - // Reserve the same port number on TCP. This prevents another - // process from binding a TCP socket to this port, which is - // required for QTIP (QUIC-over-TCP) tests. - // - PortSocksTcp[i] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - ASSERT_NE(PortSocksTcp[i], INVALID_SOCKET) - << "Failed to create TCP socket: WSAGetLastError=" << WSAGetLastError(); - struct sockaddr_in TcpAddr = {}; - TcpAddr.sin_family = AF_INET; - TcpAddr.sin_port = htons(Port); - if (bind(PortSocksTcp[i], (struct sockaddr*)&TcpAddr, sizeof(TcpAddr)) == 0) { - if (i == 0) ServerPort = Port; else ClientPort = Port; - PortReserved = true; - break; - } - // - // TCP port collision. Close both sockets and retry with a - // new OS-assigned port. - // - printf("XDP Map Mode: port %u TCP collision (attempt %d/%d), retrying\n", - Port, Retry + 1, MaxPortRetries); - closesocket(PortSocksTcp[i]); - PortSocksTcp[i] = INVALID_SOCKET; - closesocket(PortSocksUdp[i]); - PortSocksUdp[i] = INVALID_SOCKET; - } - ASSERT_TRUE(PortReserved) - << "Failed to reserve a UDP+TCP port pair after " - << MaxPortRetries << " attempts"; - } - - printf("XDP Map Mode [%s]: ports Server=%u Client=%u CIBIR=%d QTIP=%d\n", - ::testing::UnitTest::GetInstance()->current_test_info()->name(), - ServerPort, ClientPort, Params.UseCibir, Params.UseQtip); - - // - // If QTIP is needed, enable it globally before creating XDP programs. - // - if (Params.UseQtip) { - MsQuicSettings Settings; - uint32_t SettingsSize = sizeof(Settings); - MsQuic->GetParam(nullptr, QUIC_PARAM_GLOBAL_SETTINGS, &SettingsSize, &Settings); - PreviousQtipSetting = Settings.QTIPEnabled; - if (!PreviousQtipSetting) { - Settings.SetQtipEnabled(true); - ASSERT_TRUE(QUIC_SUCCEEDED(Settings.SetGlobal())); - } - } - - // - // Build XDP rules based on CIBIR/QTIP mode. - // - // Server (listener/wildcard) rules: - // - No CIBIR: XDP_MATCH_UDP_DST (+ XDP_MATCH_TCP_DST if QTIP) - // - CIBIR: QUIC_FLOW_SRC_CID + QUIC_FLOW_DST_CID - // (+ TCP_QUIC_FLOW_SRC_CID + TCP_QUIC_FLOW_DST_CID + TCP_CONTROL_DST if QTIP) - // - // Client (non-wildcard) rules: - // - Always: XDP_MATCH_UDP_DST (no QTIP) or XDP_MATCH_TCP_DST (QTIP) - // - CIBIR does NOT change client XDP matching. - // - static const XDP_HOOK_ID RxHook = { - XDP_HOOK_L2, - XDP_HOOK_RX, - XDP_HOOK_INSPECT, - }; - - for (uint32_t i = 0; i < XdpMapState.InterfaceCount; i++) { - for (uint32_t q = 0; q < XDP_MAP_MODE_MAX_QUEUES; q++) { - XDP_RULE Rules[8] = {}; - uint8_t RulesSize = 0; - - // - // Server port rules. - // - if (Params.UseCibir) { - Rules[RulesSize].Match = XDP_MATCH_QUIC_FLOW_SRC_CID; - Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); - Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; - Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; - memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); - Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; - RulesSize++; - - Rules[RulesSize].Match = XDP_MATCH_QUIC_FLOW_DST_CID; - Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); - Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; - Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; - memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); - Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; - RulesSize++; - - if (Params.UseQtip) { - Rules[RulesSize].Match = XDP_MATCH_TCP_QUIC_FLOW_SRC_CID; - Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); - Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; - Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; - memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); - Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; - RulesSize++; - - Rules[RulesSize].Match = XDP_MATCH_TCP_QUIC_FLOW_DST_CID; - Rules[RulesSize].Pattern.QuicFlow.UdpPort = htons(ServerPort); - Rules[RulesSize].Pattern.QuicFlow.CidLength = CibirIdDataLength; - Rules[RulesSize].Pattern.QuicFlow.CidOffset = CibirCidOffset; - memcpy(Rules[RulesSize].Pattern.QuicFlow.CidData, CibirIdData, CibirIdDataLength); - Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; - RulesSize++; - - Rules[RulesSize].Match = XDP_MATCH_TCP_CONTROL_DST; - Rules[RulesSize].Pattern.Port = htons(ServerPort); - Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; - RulesSize++; - } - } else { - Rules[RulesSize].Match = XDP_MATCH_UDP_DST; - Rules[RulesSize].Pattern.Port = htons(ServerPort); - Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; - RulesSize++; - - if (Params.UseQtip) { - Rules[RulesSize].Match = XDP_MATCH_TCP_DST; - Rules[RulesSize].Pattern.Port = htons(ServerPort); - Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; - RulesSize++; - } - } - - // - // Client port rules. CIBIR does not change client XDP matching. - // QTIP clients use TCP-only; non-QTIP clients use UDP-only. - // - if (Params.UseQtip) { - Rules[RulesSize].Match = XDP_MATCH_TCP_DST; - Rules[RulesSize].Pattern.Port = htons(ClientPort); - Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; - RulesSize++; - } else { - Rules[RulesSize].Match = XDP_MATCH_UDP_DST; - Rules[RulesSize].Pattern.Port = htons(ClientPort); - Rules[RulesSize].Action = XDP_PROGRAM_ACTION_REDIRECT; - Rules[RulesSize].Redirect.TargetType = XDP_REDIRECT_TARGET_TYPE_XSKMAP_BY_QUEUEID; - Rules[RulesSize].Redirect.Target = XdpMapState.XskMaps[i]; - RulesSize++; - } - - HRESULT Hr = XdpCreateProgram( - XdpMapState.IfIndices[i], - &RxHook, - q, - XDP_CREATE_PROGRAM_FLAG_NONE, - Rules, - RulesSize, - &XdpPrograms[i][q]); - if (FAILED(Hr)) { - // - // No more queues on this interface. - // - QueueCounts[i] = q; - break; - } - } - printf("XDP Map Mode: IfIndex=%u created %u per-queue programs\n", - XdpMapState.IfIndices[i], QueueCounts[i]); - ASSERT_GT(QueueCounts[i], (uint32_t)0) - << "Failed to create any XDP programs for IfIndex=" - << XdpMapState.IfIndices[i]; - } - } - - void TearDown() override { - if (!UseXdpMapMode) return; - - for (uint32_t i = 0; i < XdpMapState.InterfaceCount; i++) { - for (uint32_t q = 0; q < QueueCounts[i]; q++) { - if (XdpPrograms[i][q]) { - CloseHandle(XdpPrograms[i][q]); - XdpPrograms[i][q] = nullptr; - } - } - } - for (int i = 0; i < PortCount; i++) { - if (PortSocksUdp[i] != INVALID_SOCKET) { - closesocket(PortSocksUdp[i]); - PortSocksUdp[i] = INVALID_SOCKET; - } - if (PortSocksTcp[i] != INVALID_SOCKET) { - closesocket(PortSocksTcp[i]); - PortSocksTcp[i] = INVALID_SOCKET; - } - } - // - // Restore QTIP global setting. - // - if (GetParam().UseQtip && !PreviousQtipSetting) { - MsQuicSettings Settings; - Settings.SetQtipEnabled(false); - Settings.SetGlobal(); - } - if (WsaInitialized) { - WSACleanup(); - } - } }; -constexpr uint8_t XdpMapMode::CibirApiId[]; -constexpr uint8_t XdpMapMode::CibirIdData[]; - TEST_P(XdpMapMode, Handshake) { if (!UseXdpMapMode) { GTEST_SKIP() << "XDP Map Mode not enabled (use --xdpMapMode)"; } auto Params = GetParam(); + XdpMapModeRuleScope Scope; + ASSERT_NO_FATAL_FAILURE(Scope.Setup(Params.UseCibir, Params.UseQtip)); TestLogger Logger("QuicTestXdpMapModeHandshake"); QuicTestXdpMapModeHandshake( Params.Family, - ServerPort, - ClientPort, + Scope.GetServerPort(), + Scope.GetClientPort(), Params.UseCibir, Params.UseQtip); } @@ -3548,11 +3218,13 @@ TEST_P(XdpMapMode, DataTransfer) { GTEST_SKIP() << "XDP Map Mode not enabled (use --xdpMapMode)"; } auto Params = GetParam(); + XdpMapModeRuleScope Scope; + ASSERT_NO_FATAL_FAILURE(Scope.Setup(Params.UseCibir, Params.UseQtip)); TestLogger Logger("QuicTestXdpMapModeDataTransfer"); QuicTestXdpMapModeDataTransfer( Params.Family, - ServerPort, - ClientPort, + Scope.GetServerPort(), + Scope.GetClientPort(), Params.UseCibir, Params.UseQtip); } From cb6db819c81c573b00b028981a4b84ee95b49f0b Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 11 Jun 2026 16:13:58 -0700 Subject: [PATCH 36/41] test harness refactors --- .github/workflows/test.yml | 1 - src/test/MsQuicTests.h | 9 ++- src/test/bin/quic_gtest.cpp | 22 +----- src/test/lib/DataTest.cpp | 118 --------------------------------- src/test/lib/DatagramTest.cpp | 28 +++++++- src/test/lib/HandshakeTest.cpp | 112 ++++++++++++++----------------- src/test/lib/TestListener.cpp | 14 ++++ src/test/lib/TestListener.h | 2 +- src/test/lib/precomp.h | 27 -------- 9 files changed, 96 insertions(+), 237 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7b158c4703..17cbb4fdd8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -143,7 +143,6 @@ jobs: { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", sanitize: "-SanitizeAddress", build: "-Test", log: "Full.Verbose" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", sanitize: "-SanitizeAddress", build: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", qtip: "-UseQtip", sanitize: "-SanitizeAddress", build: "-Test" }, - # { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "schannel", xdp: "-UseXdp", xdpmapmode: "-XdpMapMode", sanitize: "-SanitizeAddress", build: "-Test", testfilter: "XdpMapMode*" }, # Disabled until CI has XDP driver with map mode support { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "quictls", build: "-Test", log: "Full.Verbose" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "quictls", xdp: "-UseXdp", build: "-Test" }, { config: "Debug", plat: "windows", os: "windows-2022", arch: "x64", tls: "quictls", xdp: "-UseXdp", qtip: "-UseQtip", build: "-Test" }, diff --git a/src/test/MsQuicTests.h b/src/test/MsQuicTests.h index e56ac9a1cc..df04dba9b5 100644 --- a/src/test/MsQuicTests.h +++ b/src/test/MsQuicTests.h @@ -79,8 +79,13 @@ void QuicTestCloseConnBeforeStreamFlush(); void QuicTestGlobalParam(); #ifdef QUIC_API_ENABLE_PREVIEW_FEATURES void QuicTestXdpMapConfigParam(); -void QuicTestXdpMapModeHandshake(_In_ int Family, _In_ uint16_t ServerPort, _In_ uint16_t ClientPort, _In_ bool UseCibir, _In_ bool UseQtip); -void QuicTestXdpMapModeDataTransfer(_In_ int Family, _In_ uint16_t ServerPort, _In_ uint16_t ClientPort, _In_ bool UseCibir, _In_ bool UseQtip); +struct XdpMapModeArgs { + int Family; + uint16_t ServerPort; + uint16_t ClientPort; + bool UseCibir; +}; +void QuicTestXdpMapModeHandshake(const XdpMapModeArgs& Params); #endif void QuicTestCommonParam(); void QuicTestRegistrationParam(); diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 3702ba8909..0256cdb632 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -3206,27 +3206,7 @@ TEST_P(XdpMapMode, Handshake) { ASSERT_NO_FATAL_FAILURE(Scope.Setup(Params.UseCibir, Params.UseQtip)); TestLogger Logger("QuicTestXdpMapModeHandshake"); QuicTestXdpMapModeHandshake( - Params.Family, - Scope.GetServerPort(), - Scope.GetClientPort(), - Params.UseCibir, - Params.UseQtip); -} - -TEST_P(XdpMapMode, DataTransfer) { - if (!UseXdpMapMode) { - GTEST_SKIP() << "XDP Map Mode not enabled (use --xdpMapMode)"; - } - auto Params = GetParam(); - XdpMapModeRuleScope Scope; - ASSERT_NO_FATAL_FAILURE(Scope.Setup(Params.UseCibir, Params.UseQtip)); - TestLogger Logger("QuicTestXdpMapModeDataTransfer"); - QuicTestXdpMapModeDataTransfer( - Params.Family, - Scope.GetServerPort(), - Scope.GetClientPort(), - Params.UseCibir, - Params.UseQtip); + { Params.Family, Scope.GetServerPort(), Scope.GetClientPort(), Params.UseCibir }); } static std::vector GenerateXdpMapModeParams() { diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index 4826b11985..be4fe1c2a6 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -5479,122 +5479,4 @@ QuicTestStreamAppProvidedBuffersOutOfSpace_ServerSend_ProvideMoreBuffer( TEST_EQUAL(0, memcmp(Buffers.SendDataBuffer.get(), Buffers.ReceiveDataBuffer.get(), Buffers.SendDataSize)); } -void -QuicTestXdpMapModeDataTransfer( - _In_ int Family, - _In_ uint16_t ServerPort, - _In_ uint16_t ClientPort, - _In_ bool UseCibir, - _In_ bool UseQtip - ) -{ - UNREFERENCED_PARAMETER(UseQtip); // QTIP is configured globally by the fixture - QUIC_ADDRESS_FAMILY QuicAddrFamily = - (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; - - MsQuicRegistration Registration; - TEST_TRUE(Registration.IsValid()); - - MsQuicAlpn Alpn("MsQuicTest"); - - MsQuicSettings Settings; - Settings.SetPeerBidiStreamCount(1); - Settings.SetIdleTimeoutMs(10000); - - MsQuicConfiguration ServerConfiguration(Registration, Alpn, Settings, ServerSelfSignedCredConfig); - TEST_TRUE(ServerConfiguration.IsValid()); - - MsQuicCredentialConfig ClientCredConfig; - MsQuicConfiguration ClientConfiguration(Registration, Alpn, Settings, ClientCredConfig); - TEST_TRUE(ClientConfiguration.IsValid()); - - // - // CIBIR ID in API format: {offset, id_byte0, ...} - // - const uint8_t CibirId[] = { 0 /* offset */, 4, 3, 2, 1 }; - const uint8_t CibirIdLength = sizeof(CibirId); - - { - TestListener Listener(Registration, ListenerAcceptConnectionBasic, ServerConfiguration); - TEST_TRUE(Listener.IsValid()); - - if (UseCibir) { - TEST_QUIC_SUCCEEDED( - MsQuic->SetParam( - Listener.GetListener(), - QUIC_PARAM_LISTENER_CIBIR_ID, - CibirIdLength, - CibirId)); - } - - QuicAddr ServerLocalAddr(QuicAddrFamily); - QuicAddrSetPort(&ServerLocalAddr.SockAddr, ServerPort); - TEST_QUIC_SUCCEEDED(Listener.Start(Alpn, &ServerLocalAddr.SockAddr)); - TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr)); - - { - UniquePtr Server; - ServerAcceptContext ServerAcceptCtx((TestConnection**)&Server); - Listener.Context = &ServerAcceptCtx; - - { - TestConnection Client(Registration); - TEST_TRUE(Client.IsValid()); - - if (UseCibir) { - TEST_QUIC_SUCCEEDED(Client.SetShareUdpBinding(true)); - TEST_QUIC_SUCCEEDED( - MsQuic->SetParam( - Client.GetConnection(), - QUIC_PARAM_CONN_CIBIR_ID, - CibirIdLength, - CibirId)); - } - - QuicAddr ClientLocalAddr(QuicAddrFamily); - QuicAddrSetToDuoNicClient(&ClientLocalAddr.SockAddr); - QuicAddrSetPort(&ClientLocalAddr.SockAddr, ClientPort); - TEST_QUIC_SUCCEEDED(Client.SetLocalAddr(ClientLocalAddr)); - - QuicAddr RemoteAddr{QuicAddrGetFamily(&ServerLocalAddr.SockAddr), ServerLocalAddr.GetPort()}; - QuicAddrSetToDuoNic(&RemoteAddr.SockAddr); - TEST_QUIC_SUCCEEDED(Client.SetRemoteAddr(RemoteAddr)); - - TEST_QUIC_SUCCEEDED( - Client.Start( - ClientConfiguration, - QuicAddrFamily, - QUIC_LOCALHOST_FOR_AF(QuicAddrFamily), - ServerLocalAddr.GetPort())); - - if (!Client.WaitForConnectionComplete()) { - return; - } - TEST_TRUE(Client.GetIsConnected()); - - TEST_NOT_EQUAL(nullptr, Server); - if (!Server->WaitForConnectionComplete()) { - return; - } - TEST_TRUE(Server->GetIsConnected()); - - // - // Open a bidirectional stream and send a small ping payload. - // - auto Stream = - Client.NewStream( - nullptr, - QUIC_STREAM_OPEN_FLAG_NONE); - TEST_TRUE(Stream != nullptr); - TEST_TRUE(Stream->StartPing(64)); - Stream->WaitForSendShutdownComplete(); - - delete Stream; - - Client.Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); - } - } - } -} - #endif // QUIC_API_ENABLE_PREVIEW_FEATURES diff --git a/src/test/lib/DatagramTest.cpp b/src/test/lib/DatagramTest.cpp index 325d0e5174..1e49ee76f1 100644 --- a/src/test/lib/DatagramTest.cpp +++ b/src/test/lib/DatagramTest.cpp @@ -14,6 +14,28 @@ #include "DatagramTest.cpp.clog.h" #endif +_Function_class_(NEW_CONNECTION_CALLBACK) +static +bool +QUIC_API +ListenerAcceptConnection( + _In_ TestListener* Listener, + _In_ HQUIC ConnectionHandle + ) +{ + ServerAcceptContext* AcceptContext = (ServerAcceptContext*)Listener->Context; + *AcceptContext->NewConnection = new(std::nothrow) TestConnection(ConnectionHandle); + if (*AcceptContext->NewConnection == nullptr || !(*AcceptContext->NewConnection)->IsValid()) { + TEST_FAILURE("Failed to accept new TestConnection."); + delete *AcceptContext->NewConnection; + *AcceptContext->NewConnection = nullptr; + return false; + } + (*AcceptContext->NewConnection)->SetHasRandomLoss(Listener->GetHasRandomLoss()); + CxPlatEventSet(AcceptContext->NewConnectionReady); + return true; +} + void QuicTestDatagramNegotiation( const DatagramNegotiationArgs& Params @@ -41,7 +63,7 @@ QuicTestDatagramNegotiation( QUIC_BUFFER DatagramBuffer = { sizeof(RawBuffer), RawBuffer }; { - TestListener Listener(Registration, ListenerAcceptConnectionBasic, ServerConfiguration); + TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); TEST_TRUE(Listener.IsValid()); QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; @@ -142,7 +164,7 @@ QuicTestDatagramSend( SelectiveLossHelper LossHelper; { - TestListener Listener(Registration, ListenerAcceptConnectionBasic, ServerConfiguration); + TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); TEST_TRUE(Listener.IsValid()); QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; @@ -276,7 +298,7 @@ QuicTestDatagramDrop( SelectiveLossHelper LossHelper; { - TestListener Listener(Registration, ListenerAcceptConnectionBasic, ServerConfiguration); + TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); TEST_TRUE(Listener.IsValid()); QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; diff --git a/src/test/lib/HandshakeTest.cpp b/src/test/lib/HandshakeTest.cpp index c1ea71ab74..8f9ccaa469 100644 --- a/src/test/lib/HandshakeTest.cpp +++ b/src/test/lib/HandshakeTest.cpp @@ -4917,16 +4917,11 @@ QuicTestConnectionPoolCreate( void QuicTestXdpMapModeHandshake( - _In_ int Family, - _In_ uint16_t ServerPort, - _In_ uint16_t ClientPort, - _In_ bool UseCibir, - _In_ bool UseQtip + const XdpMapModeArgs& Params ) { - UNREFERENCED_PARAMETER(UseQtip); // QTIP is configured globally by the fixture QUIC_ADDRESS_FAMILY QuicAddrFamily = - (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; + (Params.Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6; MsQuicRegistration Registration; TEST_TRUE(Registration.IsValid()); @@ -4950,74 +4945,63 @@ QuicTestXdpMapModeHandshake( const uint8_t CibirId[] = { 0 /* offset */, 4, 3, 2, 1 }; const uint8_t CibirIdLength = sizeof(CibirId); - { - TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); - TEST_TRUE(Listener.IsValid()); - - if (UseCibir) { - TEST_QUIC_SUCCEEDED( - MsQuic->SetParam( - Listener.GetListener(), - QUIC_PARAM_LISTENER_CIBIR_ID, - CibirIdLength, - CibirId)); - } + TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration); + TEST_TRUE(Listener.IsValid()); - QuicAddr ServerLocalAddr(QuicAddrFamily); - QuicAddrSetPort(&ServerLocalAddr.SockAddr, ServerPort); - TEST_QUIC_SUCCEEDED(Listener.Start(Alpn, &ServerLocalAddr.SockAddr)); - TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr)); + if (Params.UseCibir) { + TEST_QUIC_SUCCEEDED(Listener.SetCibirId(CibirId, CibirIdLength)); + } - { - UniquePtr Server; - ServerAcceptContext ServerAcceptCtx((TestConnection**)&Server); - Listener.Context = &ServerAcceptCtx; + QuicAddr ServerLocalAddr(QuicAddrFamily); + QuicAddrSetPort(&ServerLocalAddr.SockAddr, Params.ServerPort); + TEST_QUIC_SUCCEEDED(Listener.Start(Alpn, &ServerLocalAddr.SockAddr)); + TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr)); - { - TestConnection Client(Registration); - TEST_TRUE(Client.IsValid()); + UniquePtr Server; + ServerAcceptContext ServerAcceptCtx((TestConnection**)&Server); + Listener.Context = &ServerAcceptCtx; - if (UseCibir) { - TEST_QUIC_SUCCEEDED(Client.SetShareUdpBinding(true)); - TEST_QUIC_SUCCEEDED( - MsQuic->SetParam( - Client.GetConnection(), - QUIC_PARAM_CONN_CIBIR_ID, - CibirIdLength, - CibirId)); - } + TestConnection Client(Registration); + TEST_TRUE(Client.IsValid()); - QuicAddr ClientLocalAddr(QuicAddrFamily); - QuicAddrSetToDuoNicClient(&ClientLocalAddr.SockAddr); - QuicAddrSetPort(&ClientLocalAddr.SockAddr, ClientPort); - TEST_QUIC_SUCCEEDED(Client.SetLocalAddr(ClientLocalAddr)); + if (Params.UseCibir) { + TEST_QUIC_SUCCEEDED(Client.SetShareUdpBinding(true)); + TEST_QUIC_SUCCEEDED( + MsQuic->SetParam( + Client.GetConnection(), + QUIC_PARAM_CONN_CIBIR_ID, + CibirIdLength, + CibirId)); + } - QuicAddr RemoteAddr{QuicAddrGetFamily(&ServerLocalAddr.SockAddr), ServerLocalAddr.GetPort()}; - QuicAddrSetToDuoNic(&RemoteAddr.SockAddr); - TEST_QUIC_SUCCEEDED(Client.SetRemoteAddr(RemoteAddr)); + QuicAddr ClientLocalAddr(QuicAddrFamily); + QuicAddrSetToDuoNicClient(&ClientLocalAddr.SockAddr); + QuicAddrSetPort(&ClientLocalAddr.SockAddr, Params.ClientPort); + TEST_QUIC_SUCCEEDED(Client.SetLocalAddr(ClientLocalAddr)); - TEST_QUIC_SUCCEEDED( - Client.Start( - ClientConfiguration, - QuicAddrFamily, - QUIC_LOCALHOST_FOR_AF(QuicAddrFamily), - ServerLocalAddr.GetPort())); + QuicAddr RemoteAddr{QuicAddrGetFamily(&ServerLocalAddr.SockAddr), ServerLocalAddr.GetPort()}; + QuicAddrSetToDuoNic(&RemoteAddr.SockAddr); + TEST_QUIC_SUCCEEDED(Client.SetRemoteAddr(RemoteAddr)); - if (!Client.WaitForConnectionComplete()) { - return; - } - TEST_TRUE(Client.GetIsConnected()); + TEST_QUIC_SUCCEEDED( + Client.Start( + ClientConfiguration, + QuicAddrFamily, + QUIC_LOCALHOST_FOR_AF(QuicAddrFamily), + ServerLocalAddr.GetPort())); - TEST_NOT_EQUAL(nullptr, Server); - if (!Server->WaitForConnectionComplete()) { - return; - } - TEST_TRUE(Server->GetIsConnected()); + if (!Client.WaitForConnectionComplete()) { + return; + } + TEST_TRUE(Client.GetIsConnected()); - Client.Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); - } - } + TEST_NOT_EQUAL(nullptr, Server); + if (!Server->WaitForConnectionComplete()) { + return; } + TEST_TRUE(Server->GetIsConnected()); + + Client.Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); } #endif // QUIC_API_ENABLE_PREVIEW_FEATURES diff --git a/src/test/lib/TestListener.cpp b/src/test/lib/TestListener.cpp index cc254437bd..a518fa73d6 100644 --- a/src/test/lib/TestListener.cpp +++ b/src/test/lib/TestListener.cpp @@ -100,6 +100,20 @@ TestListener::GetStatistics( &stats); } +QUIC_STATUS +TestListener::SetCibirId( + _In_reads_(Length) const uint8_t* CibirId, + _In_ uint8_t Length + ) +{ + return + MsQuic->SetParam( + QuicListener, + QUIC_PARAM_LISTENER_CIBIR_ID, + Length, + CibirId); +} + QUIC_STATUS TestListener::HandleListenerEvent( _Inout_ QUIC_LISTENER_EVENT* Event diff --git a/src/test/lib/TestListener.h b/src/test/lib/TestListener.h index 3e7783f260..4659222004 100644 --- a/src/test/lib/TestListener.h +++ b/src/test/lib/TestListener.h @@ -106,7 +106,7 @@ class TestListener QUIC_STATUS GetLocalAddr(_Out_ QuicAddr &localAddr); QUIC_STATUS GetStatistics(_Out_ QUIC_LISTENER_STATISTICS &stats); - HQUIC GetListener() const { return QuicListener; } + QUIC_STATUS SetCibirId(_In_reads_(Length) const uint8_t* CibirId, _In_ uint8_t Length); bool GetHasRandomLoss() const { return HasRandomLoss; } void SetHasRandomLoss(bool Value) { HasRandomLoss = Value; } diff --git a/src/test/lib/precomp.h b/src/test/lib/precomp.h index 76be376a94..f7dba0df46 100644 --- a/src/test/lib/precomp.h +++ b/src/test/lib/precomp.h @@ -49,33 +49,6 @@ #include "TestListener.h" #include "DrillDescriptor.h" -// -// Basic listener accept callback that creates a TestConnection and signals -// readiness. For handshake-specific features (cert validation, TLS secrets, -// etc.), use the full ListenerAcceptConnection in HandshakeTest.cpp. -// -_Function_class_(NEW_CONNECTION_CALLBACK) -inline -bool -QUIC_API -ListenerAcceptConnectionBasic( - _In_ TestListener* Listener, - _In_ HQUIC ConnectionHandle - ) -{ - ServerAcceptContext* AcceptContext = (ServerAcceptContext*)Listener->Context; - *AcceptContext->NewConnection = new(std::nothrow) TestConnection(ConnectionHandle); - if (*AcceptContext->NewConnection == nullptr || !(*AcceptContext->NewConnection)->IsValid()) { - TEST_FAILURE("Failed to accept new TestConnection."); - delete *AcceptContext->NewConnection; - *AcceptContext->NewConnection = nullptr; - return false; - } - (*AcceptContext->NewConnection)->SetHasRandomLoss(Listener->GetHasRandomLoss()); - CxPlatEventSet(AcceptContext->NewConnectionReady); - return true; -} - #if defined(_ARM64_) || defined(_ARM64EC_) #pragma optimize("", off) #endif From 4c30ab2d6b41455dc1b9607ead9d6b4f66fbe50a Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 11 Jun 2026 17:06:55 -0700 Subject: [PATCH 37/41] more feedback refactors --- scripts/test.ps1 | 8 +++++--- src/core/settings.c | 4 +--- src/platform/unittest/DataPathTest.cpp | 6 +++--- src/test/bin/XdpMapModeHelpers.cpp | 10 +++++++--- src/test/bin/quic_gtest.cpp | 14 -------------- 5 files changed, 16 insertions(+), 26 deletions(-) diff --git a/scripts/test.ps1 b/scripts/test.ps1 index 051ed8ab85..8e960a5eed 100644 --- a/scripts/test.ps1 +++ b/scripts/test.ps1 @@ -241,6 +241,11 @@ if ($UseXdp) { $DuoNic = $true } +if ($XdpMapMode) { + # Map mode implies DuoNic + $DuoNic = $true +} + # Root directory of the project. $RootDir = Split-Path $PSScriptRoot -Parent @@ -306,9 +311,6 @@ if ($DuoNic) { } if ($XdpMapMode) { $TestArguments += " -XdpMapMode" - if (!$DuoNic) { - $TestArguments += " -DuoNic" - } } if ($Kernel) { $TestArguments += " -Kernel $KernelPath" diff --git a/src/core/settings.c b/src/core/settings.c index 3a18af8034..e76b32cd2e 100644 --- a/src/core/settings.c +++ b/src/core/settings.c @@ -717,9 +717,7 @@ QuicSettingApply( // enabled for all sockets. Reject an explicit XdpEnabled = FALSE since // it contradicts map mode — there is no OS datapath to fall back to. // - if (Source->IsSet.XdpEnabled && - !Source->XdpEnabled && - MsQuicLib.XdpMapConfigCount > 0) { + if (Source->IsSet.XdpEnabled && !Source->XdpEnabled && MsQuicLib.XdpMapConfigCount > 0) { QuicTraceLogError( SettingXdpDisabledInMapMode, "[ lib] Error: XdpEnabled cannot be set to FALSE when XDP map mode is active."); diff --git a/src/platform/unittest/DataPathTest.cpp b/src/platform/unittest/DataPathTest.cpp index 4bd1e35464..b27f740c2d 100644 --- a/src/platform/unittest/DataPathTest.cpp +++ b/src/platform/unittest/DataPathTest.cpp @@ -1465,7 +1465,7 @@ TEST_F(DataPathTest, XdpMapMode_InitFailsWithoutRawDatapath) } const uint32_t FakeIfIndex = 0xDEAD; - const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)(uintptr_t)0x1234; + const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)INVALID_HANDLE_VALUE; QUIC_XDP_MAP_CONFIG MapConfig = { FakeIfIndex, FakeHandle }; QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 0, {0} }; @@ -1507,7 +1507,7 @@ TEST_F(DataPathTest, XdpMapMode_InitSucceedsWithNonMatchingIfIndex) } const uint32_t FakeIfIndex = 0xDEAD; - const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)(uintptr_t)0x1234; + const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)INVALID_HANDLE_VALUE; QUIC_XDP_MAP_CONFIG MapConfig = { FakeIfIndex, FakeHandle }; QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 1, {0} }; @@ -1598,7 +1598,7 @@ TEST_F(DataPathTest, XdpMapMode_SocketSkipsRulePlumbing) } const uint32_t FakeIfIndex = 0xDEAD; - const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)(uintptr_t)0x1234; + const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)INVALID_HANDLE_VALUE; QUIC_XDP_MAP_CONFIG MapConfig = { FakeIfIndex, FakeHandle }; QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 1, {0} }; diff --git a/src/test/bin/XdpMapModeHelpers.cpp b/src/test/bin/XdpMapModeHelpers.cpp index c18a191d96..276755b0c2 100644 --- a/src/test/bin/XdpMapModeHelpers.cpp +++ b/src/test/bin/XdpMapModeHelpers.cpp @@ -70,12 +70,16 @@ DiscoverDuoNicInterfaces( Adapter->OperStatus != IfOperStatusUp) { continue; } + IN_ADDR DuoNicServer, DuoNicClient; + if (inet_pton(AF_INET, "192.168.1.11", &DuoNicServer) != 1 || + inet_pton(AF_INET, "192.168.1.12", &DuoNicClient) != 1) { + break; + } for (auto Unicast = Adapter->FirstUnicastAddress; Unicast; Unicast = Unicast->Next) { if (Unicast->Address.lpSockaddr->sa_family != AF_INET) continue; auto* Sin = (SOCKADDR_IN*)Unicast->Address.lpSockaddr; - ULONG Addr = ntohl(Sin->sin_addr.S_un.S_addr); - // 192.168.1.11 or 192.168.1.12 - if (Addr == 0xC0A8010B || Addr == 0xC0A8010C) { + if (Sin->sin_addr.s_addr == DuoNicServer.s_addr || + Sin->sin_addr.s_addr == DuoNicClient.s_addr) { IfIndices[*Count] = Adapter->IfIndex; (*Count)++; break; diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 0256cdb632..13f9e9cf80 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -16,7 +16,6 @@ #include #if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) -#include #define XDP_API_VERSION 3 #define XDP_INCLUDE_WINCOMMON #include @@ -187,19 +186,6 @@ class QuicTestEnvironment : public ::testing::Environment { ClientCertCredConfig.Flags |= QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION; QuicTestInitialize(); -#if defined(_WIN32) && defined(QUIC_API_ENABLE_PREVIEW_FEATURES) - if (UseXdpMapMode) { - // - // Force lazy initialization so MsQuic creates XSK sockets - // and inserts them into the XSKMAPs we provided. - // - { - MsQuicRegistration TempReg("XdpMapModeInit"); - ASSERT_TRUE(TempReg.IsValid()); - } - } -#endif // _WIN32 && QUIC_API_ENABLE_PREVIEW_FEATURES - #ifdef _WIN32 ASSERT_NE(GetCurrentDirectoryA(sizeof(CurrentWorkingDirectory), CurrentWorkingDirectory), 0); #else From d69d27d6b95020f2fb547e0622c4457c40d9932b Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 11 Jun 2026 17:44:15 -0700 Subject: [PATCH 38/41] add coverage for wildcard clients --- src/platform/datapath_raw.h | 6 ------ src/platform/datapath_winuser.c | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index 7469bd9149..fffca30273 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -353,12 +353,6 @@ CxPlatSocketCompare( // For a client socket, make sure the local IP matches and the full // remote address matches along with QTIP settings. // - // TODO: In XDP map mode the OS socket creation path is skipped, so - // the socket's local IP may remain wildcard (0.0.0.0 / ::) if the - // caller doesn't supply a specific local IP. Map mode should require - // (or resolve) a concrete local IP so that this comparison works - // correctly without special-casing wildcards here. - // CXPLAT_DBG_ASSERT(Socket->Connected); return QuicAddrCompareIp(&Socket->LocalAddress, LocalAddress) && diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index c6f4637fd6..6e9b201c22 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1296,6 +1296,24 @@ SocketCreateUdp( Status = QUIC_STATUS_INVALID_PARAMETER; goto Error; } + // + // Client (connected) sockets are demuxed on local IP + port. In map + // mode there is no OS bind to resolve a wildcard local IP into the + // concrete bound address, so a wildcard local IP would never match + // inbound packets and would silently black-hole all RX. Require a + // concrete local IP for connected sockets. Listeners legitimately + // stay wildcard and are demuxed on port alone. + // + if (!IsServerSocket && QuicAddrIsWildCard(Config->LocalAddress)) { + QuicTraceEvent( + DatapathErrorStatus, + "[data][%p] ERROR, %u, %s.", + Socket, + (uint32_t)QUIC_STATUS_INVALID_PARAMETER, + "XDP map mode requires an explicit local IP for connected sockets"); + Status = QUIC_STATUS_INVALID_PARAMETER; + goto Error; + } goto Skip; } From 3e997ffb7ad7d150c55bdb234b7ef28d1e5c8b38 Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 11 Jun 2026 18:04:59 -0700 Subject: [PATCH 39/41] rename to not ref map mode --- src/generated/linux/datapath_xplat.c.clog.h | 18 ++++++------ .../linux/datapath_xplat.c.clog.h.lttng.h | 14 +++++----- src/manifest/clog.sidecar | 12 ++++---- src/platform/datapath_raw.c | 10 +++---- src/platform/datapath_raw.h | 2 +- src/platform/datapath_raw_dummy.c | 8 +++--- src/platform/datapath_raw_xdp_win.c | 2 +- src/platform/datapath_winuser.c | 24 ++++++++-------- src/platform/datapath_xplat.c | 28 +++++++++---------- src/platform/platform_internal.h | 4 +-- 10 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/generated/linux/datapath_xplat.c.clog.h b/src/generated/linux/datapath_xplat.c.clog.h index 63fb7f316c..3c0ed3c61d 100644 --- a/src/generated/linux/datapath_xplat.c.clog.h +++ b/src/generated/linux/datapath_xplat.c.clog.h @@ -68,15 +68,15 @@ tracepoint(CLOG_DATAPATH_XPLAT_C, WarnNoXdpForCibirSockets );\ /*---------------------------------------------------------- -// Decoder Ring for DatapathRawInitFailMapMode -// [ dp] XDP map mode: raw datapath required but failed to initialize +// Decoder Ring for DatapathRawInitFailRawOnly +// [ dp] Raw-only mode: raw datapath required but failed to initialize // QuicTraceLogVerbose( - DatapathRawInitFailMapMode, - "[ dp] XDP map mode: raw datapath required but failed to initialize"); + DatapathRawInitFailRawOnly, + "[ dp] Raw-only mode: raw datapath required but failed to initialize"); ----------------------------------------------------------*/ -#ifndef _clog_2_ARGS_TRACE_DatapathRawInitFailMapMode -#define _clog_2_ARGS_TRACE_DatapathRawInitFailMapMode(uniqueId, encoded_arg_string)\ -tracepoint(CLOG_DATAPATH_XPLAT_C, DatapathRawInitFailMapMode );\ +#ifndef _clog_2_ARGS_TRACE_DatapathRawInitFailRawOnly +#define _clog_2_ARGS_TRACE_DatapathRawInitFailRawOnly(uniqueId, encoded_arg_string)\ +tracepoint(CLOG_DATAPATH_XPLAT_C, DatapathRawInitFailRawOnly );\ #endif @@ -156,9 +156,9 @@ tracepoint(CLOG_DATAPATH_XPLAT_C, ErrNoXdpForQtip );\ // QuicTraceEvent( AllocFailure, "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH (map mode)", + "CXPLAT_DATAPATH (raw-only)", sizeof(CXPLAT_DATAPATH)); -// arg2 = arg2 = "CXPLAT_DATAPATH (map mode)" = arg2 +// arg2 = arg2 = "CXPLAT_DATAPATH (raw-only)" = arg2 // arg3 = arg3 = sizeof(CXPLAT_DATAPATH) = arg3 ----------------------------------------------------------*/ #ifndef _clog_4_ARGS_TRACE_AllocFailure diff --git a/src/generated/linux/datapath_xplat.c.clog.h.lttng.h b/src/generated/linux/datapath_xplat.c.clog.h.lttng.h index a78d0467c1..441de90373 100644 --- a/src/generated/linux/datapath_xplat.c.clog.h.lttng.h +++ b/src/generated/linux/datapath_xplat.c.clog.h.lttng.h @@ -36,13 +36,13 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, WarnNoXdpForCibirSockets, /*---------------------------------------------------------- -// Decoder Ring for DatapathRawInitFailMapMode -// [ dp] XDP map mode: raw datapath required but failed to initialize +// Decoder Ring for DatapathRawInitFailRawOnly +// [ dp] Raw-only mode: raw datapath required but failed to initialize // QuicTraceLogVerbose( - DatapathRawInitFailMapMode, - "[ dp] XDP map mode: raw datapath required but failed to initialize"); + DatapathRawInitFailRawOnly, + "[ dp] Raw-only mode: raw datapath required but failed to initialize"); ----------------------------------------------------------*/ -TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, DatapathRawInitFailMapMode, +TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, DatapathRawInitFailRawOnly, TP_ARGS( ), TP_FIELDS( @@ -127,9 +127,9 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, ErrNoXdpForQtip, // QuicTraceEvent( AllocFailure, "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH (map mode)", + "CXPLAT_DATAPATH (raw-only)", sizeof(CXPLAT_DATAPATH)); -// arg2 = arg2 = "CXPLAT_DATAPATH (map mode)" = arg2 +// arg2 = arg2 = "CXPLAT_DATAPATH (raw-only)" = arg2 // arg3 = arg3 = sizeof(CXPLAT_DATAPATH) = arg3 ----------------------------------------------------------*/ TRACEPOINT_EVENT(CLOG_DATAPATH_XPLAT_C, AllocFailure, diff --git a/src/manifest/clog.sidecar b/src/manifest/clog.sidecar index 8a6da51fa6..d77185ff1b 100644 --- a/src/manifest/clog.sidecar +++ b/src/manifest/clog.sidecar @@ -2807,10 +2807,10 @@ ], "macroName": "QuicTraceLogWarning" }, - "DatapathRawInitFailMapMode": { + "DatapathRawInitFailRawOnly": { "ModuleProperites": {}, - "TraceString": "[ dp] XDP map mode: raw datapath required but failed to initialize", - "UniqueId": "DatapathRawInitFailMapMode", + "TraceString": "[ dp] Raw-only mode: raw datapath required but failed to initialize", + "UniqueId": "DatapathRawInitFailRawOnly", "splitArgs": [], "macroName": "QuicTraceLogVerbose" }, @@ -13981,9 +13981,9 @@ "EncodingString": "[data] Query for UDP_SEND_MSG_SIZE failed (async), 0x%x" }, { - "UniquenessHash": "bc85b642-06e2-35a0-f754-716b4e5484cb", - "TraceID": "DatapathRawInitFailMapMode", - "EncodingString": "[ dp] XDP map mode: raw datapath required but failed to initialize" + "UniquenessHash": "4001a3d6-c475-003a-33d3-0252c3c10417", + "TraceID": "DatapathRawInitFailRawOnly", + "EncodingString": "[ dp] Raw-only mode: raw datapath required but failed to initialize" }, { "UniquenessHash": "38cb5c9d-e612-01a0-ec5f-6af064bb0724", diff --git a/src/platform/datapath_raw.c b/src/platform/datapath_raw.c index 602e56f709..b7746f659c 100644 --- a/src/platform/datapath_raw.c +++ b/src/platform/datapath_raw.c @@ -67,7 +67,7 @@ RawDataPathInitialize( if (InitConfig->XdpMapConfigCount > 0) { CXPLAT_DBG_ASSERT(InitConfig->XdpMapConfigs != NULL); - CxPlatDpRawEnableExternalXdpMapMode(RawDataPath); + CxPlatDpRawEnableRawDatapathOnly(RawDataPath); Status = CxPlatDpRawInsertXskByMapConfigs( RawDataPath, @@ -107,19 +107,19 @@ RawDataPathInitialize( } BOOLEAN -CxPlatDpRawIsExternalXdpMapMode( +CxPlatDpRawIsRawDatapathOnly( _In_opt_ const CXPLAT_DATAPATH_RAW* RawDataPath ) { - return RawDataPath != NULL && RawDataPath->UseExternalXdpMaps; + return RawDataPath != NULL && RawDataPath->RawDatapathOnly; } void -CxPlatDpRawEnableExternalXdpMapMode( +CxPlatDpRawEnableRawDatapathOnly( _In_ CXPLAT_DATAPATH_RAW* RawDataPath ) { - RawDataPath->UseExternalXdpMaps = TRUE; + RawDataPath->RawDatapathOnly = TRUE; } _IRQL_requires_max_(PASSIVE_LEVEL) diff --git a/src/platform/datapath_raw.h b/src/platform/datapath_raw.h index fffca30273..1dcce1da86 100644 --- a/src/platform/datapath_raw.h +++ b/src/platform/datapath_raw.h @@ -63,7 +63,7 @@ typedef struct CXPLAT_DATAPATH_RAW { BOOLEAN Freed : 1; #endif BOOLEAN ReserveAuxTcpSockForQtip; // Whether or not we create an auxiliary TCP socket. - BOOLEAN UseExternalXdpMaps; // XDP map mode: app manages XDP rules externally. + BOOLEAN RawDatapathOnly; // Raw datapath is the only datapath; OS socket creation is skipped (e.g. XDP map mode, DPDK). } CXPLAT_DATAPATH_RAW; diff --git a/src/platform/datapath_raw_dummy.c b/src/platform/datapath_raw_dummy.c index 20879f3c11..0ee1126448 100644 --- a/src/platform/datapath_raw_dummy.c +++ b/src/platform/datapath_raw_dummy.c @@ -77,16 +77,16 @@ RawDataPathUninitialize( } BOOLEAN -CxPlatDpRawIsExternalXdpMapMode( +CxPlatDpRawIsRawDatapathOnly( _In_opt_ const CXPLAT_DATAPATH_RAW* RawDataPath ) { UNREFERENCED_PARAMETER(RawDataPath); - return FALSE; // Dummy raw datapath never has XDP map mode. + return FALSE; // Dummy raw datapath is never raw-only. } void -CxPlatDpRawEnableExternalXdpMapMode( +CxPlatDpRawEnableRawDatapathOnly( _In_ CXPLAT_DATAPATH_RAW* RawDataPath ) { @@ -114,7 +114,7 @@ CxPlatDpRawInsertXskByMapConfigs( { // // Stub for platforms without XDP support. Must return QUIC_STATUS_NOT_SUPPORTED - // so map mode init fails gracefully on non-Windows platforms. + // so raw-only init fails gracefully on non-Windows platforms. // UNREFERENCED_PARAMETER(RawDataPath); UNREFERENCED_PARAMETER(MapConfigs); diff --git a/src/platform/datapath_raw_xdp_win.c b/src/platform/datapath_raw_xdp_win.c index 92cadf0c75..7101570634 100644 --- a/src/platform/datapath_raw_xdp_win.c +++ b/src/platform/datapath_raw_xdp_win.c @@ -1472,7 +1472,7 @@ CxPlatDpRawPlumbRulesOnSocket( // // In external map mode, the caller manages XDP rules; nothing to do here. // - if (Xdp->UseExternalXdpMaps) { + if (Xdp->RawDatapathOnly) { return QUIC_STATUS_SUCCESS; } diff --git a/src/platform/datapath_winuser.c b/src/platform/datapath_winuser.c index 6e9b201c22..04f3267205 100644 --- a/src/platform/datapath_winuser.c +++ b/src/platform/datapath_winuser.c @@ -1227,7 +1227,7 @@ SocketCreateUdp( int Result, Option; CXPLAT_DBG_ASSERT(Datapath->UdpHandlers.Receive != NULL || Config->Flags & CXPLAT_SOCKET_FLAG_PCP); - CXPLAT_DBG_ASSERT(CxPlatDpRawIsExternalXdpMapMode(Datapath->RawDataPath) || IsServerSocket || Config->PartitionIndex < Datapath->PartitionCount); + CXPLAT_DBG_ASSERT(CxPlatDpRawIsRawDatapathOnly(Datapath->RawDataPath) || IsServerSocket || Config->PartitionIndex < Datapath->PartitionCount); CXPLAT_DBG_ASSERT(Config->CibirIdLength <= sizeof(Config->CibirId)); const uint32_t RawSocketLength = CxPlatGetRawSocketSize() + SocketCount * sizeof(CXPLAT_SOCKET_PROC); @@ -1277,11 +1277,11 @@ SocketCreateUdp( MAX_URO_PAYLOAD_LENGTH : Socket->Mtu - CXPLAT_MIN_IPV4_HEADER_SIZE - CXPLAT_UDP_HEADER_SIZE; - if (CxPlatDpRawIsExternalXdpMapMode(Datapath->RawDataPath)) { + if (CxPlatDpRawIsRawDatapathOnly(Datapath->RawDataPath)) { // - // There is no OS native datapath when XDP maps are used. The - // application must specify the local port. Skip OS socket creation - // entirely and defer to the raw (XDP) datapath. + // There is no OS native datapath in raw-only mode. The application + // must specify the local port. Skip OS socket creation entirely and + // defer to the raw (XDP) datapath. // CXPLAT_DBG_ASSERT(Datapath->RawDataPath != NULL); Socket->SkipCreatingOsSockets = TRUE; @@ -1292,16 +1292,16 @@ SocketCreateUdp( "[data][%p] ERROR, %u, %s.", Socket, (uint32_t)QUIC_STATUS_INVALID_PARAMETER, - "XDP map mode requires an explicit local port"); + "Raw-only datapath requires an explicit local port"); Status = QUIC_STATUS_INVALID_PARAMETER; goto Error; } // - // Client (connected) sockets are demuxed on local IP + port. In map - // mode there is no OS bind to resolve a wildcard local IP into the - // concrete bound address, so a wildcard local IP would never match - // inbound packets and would silently black-hole all RX. Require a - // concrete local IP for connected sockets. Listeners legitimately + // Client (connected) sockets are demuxed on local IP + port. In + // raw-only mode there is no OS bind to resolve a wildcard local IP + // into the concrete bound address, so a wildcard local IP would never + // match inbound packets and would silently black-hole all RX. Require + // a concrete local IP for connected sockets. Listeners legitimately // stay wildcard and are demuxed on port alone. // if (!IsServerSocket && QuicAddrIsWildCard(Config->LocalAddress)) { @@ -1310,7 +1310,7 @@ SocketCreateUdp( "[data][%p] ERROR, %u, %s.", Socket, (uint32_t)QUIC_STATUS_INVALID_PARAMETER, - "XDP map mode requires an explicit local IP for connected sockets"); + "Raw-only datapath requires an explicit local IP for connected sockets"); Status = QUIC_STATUS_INVALID_PARAMETER; goto Error; } diff --git a/src/platform/datapath_xplat.c b/src/platform/datapath_xplat.c index c3b50db345..f2dc40e4b8 100644 --- a/src/platform/datapath_xplat.c +++ b/src/platform/datapath_xplat.c @@ -34,8 +34,8 @@ CxPlatDataPathInitialize( if (InitConfig->XdpMapConfigCount > 0) { // - // XDP map mode: must require the raw datapath to be successfully initialized - // as we are skipping OS platform specific initializations. + // Raw-only datapath: the raw datapath must initialize successfully + // since we are skipping OS platform specific initializations. // CXPLAT_DBG_ASSERT(InitConfig->XdpMapConfigs != NULL); if (NewDataPath == NULL) { @@ -52,7 +52,7 @@ CxPlatDataPathInitialize( QuicTraceEvent( AllocFailure, "Allocation of '%s' failed. (%llu bytes)", - "CXPLAT_DATAPATH (map mode)", + "CXPLAT_DATAPATH (raw-only)", sizeof(CXPLAT_DATAPATH)); Status = QUIC_STATUS_OUT_OF_MEMORY; goto Error; @@ -71,8 +71,8 @@ CxPlatDataPathInitialize( &Datapath->RawDataPath); if (Datapath->RawDataPath == NULL) { QuicTraceLogVerbose( - DatapathRawInitFailMapMode, - "[ dp] XDP map mode: raw datapath required but failed to initialize"); + DatapathRawInitFailRawOnly, + "[ dp] Raw-only mode: raw datapath required but failed to initialize"); CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); *NewDataPath = NULL; Status = QUIC_STATUS_NOT_SUPPORTED; @@ -120,13 +120,13 @@ CxPlatDataPathUninitialize( _In_ CXPLAT_DATAPATH* Datapath ) { - BOOLEAN IsMapMode = CxPlatDpRawIsExternalXdpMapMode(Datapath->RawDataPath); + BOOLEAN IsRawDatapathOnly = CxPlatDpRawIsRawDatapathOnly(Datapath->RawDataPath); if (Datapath->RawDataPath) { RawDataPathUninitialize(Datapath->RawDataPath); } - if (IsMapMode) { + if (IsRawDatapathOnly) { // - // Map mode: no platform (WinSock) datapath was initialized, + // Raw-only mode: no platform (WinSock) datapath was initialized, // so free directly without platform uninit. // CXPLAT_FREE(Datapath, QUIC_POOL_DATAPATH); @@ -187,14 +187,14 @@ CxPlatSocketCreateUdp( ) { QUIC_STATUS Status = QUIC_STATUS_SUCCESS; - BOOLEAN IsMapMode = CxPlatDpRawIsExternalXdpMapMode(Datapath->RawDataPath); + BOOLEAN IsRawDatapathOnly = CxPlatDpRawIsRawDatapathOnly(Datapath->RawDataPath); // - // In map mode the raw (XDP) datapath is the only data path. Implicitly + // In raw-only mode the raw (XDP) datapath is the only data path. Implicitly // enable XDP for all sockets and treat any raw socket failure as fatal // (no OS fallback, no QTIP TCP port retry). // - BOOLEAN CreateRaw = IsMapMode || (Config->Flags & CXPLAT_SOCKET_FLAG_XDP); + BOOLEAN CreateRaw = IsRawDatapathOnly || (Config->Flags & CXPLAT_SOCKET_FLAG_XDP); // // In a real production (XDP/QTIP+XDP) scenario, we never have to loop more than once @@ -220,7 +220,7 @@ CxPlatSocketCreateUdp( BOOLEAN CibirRequested = (Config->CibirIdLength > 0); (*NewSocket)->RawSocketAvailable = 0; - CXPLAT_DBG_ASSERT((IsMapMode && CreateRaw && Datapath->RawDataPath) || !IsMapMode); + CXPLAT_DBG_ASSERT((IsRawDatapathOnly && CreateRaw && Datapath->RawDataPath) || !IsRawDatapathOnly); if (CreateRaw && Datapath->RawDataPath) { Status = RawSocketCreateUdp( @@ -233,9 +233,9 @@ CxPlatSocketCreateUdp( RawSockCreateFail, "[sock] Failed to create raw socket, status:%d", Status); - if (IsMapMode) { + if (IsRawDatapathOnly) { // - // Map mode: no fallback allowed. + // Raw-only mode: no fallback allowed. // CxPlatSocketDelete(*NewSocket); *NewSocket = NULL; diff --git a/src/platform/platform_internal.h b/src/platform/platform_internal.h index 45998e0408..10cd324bd1 100644 --- a/src/platform/platform_internal.h +++ b/src/platform/platform_internal.h @@ -1249,12 +1249,12 @@ RawDataPathIsPaddingPreferred( ); BOOLEAN -CxPlatDpRawIsExternalXdpMapMode( +CxPlatDpRawIsRawDatapathOnly( _In_opt_ const CXPLAT_DATAPATH_RAW* RawDataPath ); void -CxPlatDpRawEnableExternalXdpMapMode( +CxPlatDpRawEnableRawDatapathOnly( _In_ CXPLAT_DATAPATH_RAW* RawDataPath ); From f658a8be76ff1035ba5b5da8721c3a6dd185ca9b Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 11 Jun 2026 18:35:02 -0700 Subject: [PATCH 40/41] use -1 --- src/platform/unittest/DataPathTest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/unittest/DataPathTest.cpp b/src/platform/unittest/DataPathTest.cpp index b27f740c2d..cc59788799 100644 --- a/src/platform/unittest/DataPathTest.cpp +++ b/src/platform/unittest/DataPathTest.cpp @@ -1465,7 +1465,7 @@ TEST_F(DataPathTest, XdpMapMode_InitFailsWithoutRawDatapath) } const uint32_t FakeIfIndex = 0xDEAD; - const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)INVALID_HANDLE_VALUE; + const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)(intptr_t)-1; QUIC_XDP_MAP_CONFIG MapConfig = { FakeIfIndex, FakeHandle }; QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 0, {0} }; @@ -1507,7 +1507,7 @@ TEST_F(DataPathTest, XdpMapMode_InitSucceedsWithNonMatchingIfIndex) } const uint32_t FakeIfIndex = 0xDEAD; - const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)INVALID_HANDLE_VALUE; + const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)(intptr_t)-1; QUIC_XDP_MAP_CONFIG MapConfig = { FakeIfIndex, FakeHandle }; QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 1, {0} }; @@ -1598,7 +1598,7 @@ TEST_F(DataPathTest, XdpMapMode_SocketSkipsRulePlumbing) } const uint32_t FakeIfIndex = 0xDEAD; - const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)INVALID_HANDLE_VALUE; + const QUIC_XDP_MAP_HANDLE FakeHandle = (QUIC_XDP_MAP_HANDLE)(intptr_t)-1; QUIC_XDP_MAP_CONFIG MapConfig = { FakeIfIndex, FakeHandle }; QUIC_GLOBAL_EXECUTION_CONFIG ExecConfig = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 1, {0} }; From 34323225b7f0b5e658a5bcc3458d65c019f852c8 Mon Sep 17 00:00:00 2001 From: Jack He Date: Thu, 11 Jun 2026 18:42:29 -0700 Subject: [PATCH 41/41] add proper preview guards --- src/test/lib/TestListener.cpp | 2 ++ src/test/lib/TestListener.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/test/lib/TestListener.cpp b/src/test/lib/TestListener.cpp index a518fa73d6..bdaa12b0b0 100644 --- a/src/test/lib/TestListener.cpp +++ b/src/test/lib/TestListener.cpp @@ -100,6 +100,7 @@ TestListener::GetStatistics( &stats); } +#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES QUIC_STATUS TestListener::SetCibirId( _In_reads_(Length) const uint8_t* CibirId, @@ -113,6 +114,7 @@ TestListener::SetCibirId( Length, CibirId); } +#endif // QUIC_API_ENABLE_PREVIEW_FEATURES QUIC_STATUS TestListener::HandleListenerEvent( diff --git a/src/test/lib/TestListener.h b/src/test/lib/TestListener.h index 4659222004..61b7145391 100644 --- a/src/test/lib/TestListener.h +++ b/src/test/lib/TestListener.h @@ -106,7 +106,9 @@ class TestListener QUIC_STATUS GetLocalAddr(_Out_ QuicAddr &localAddr); QUIC_STATUS GetStatistics(_Out_ QUIC_LISTENER_STATISTICS &stats); +#ifdef QUIC_API_ENABLE_PREVIEW_FEATURES QUIC_STATUS SetCibirId(_In_reads_(Length) const uint8_t* CibirId, _In_ uint8_t Length); +#endif bool GetHasRandomLoss() const { return HasRandomLoss; } void SetHasRandomLoss(bool Value) { HasRandomLoss = Value; }