Skip to content

Commit 90d0346

Browse files
fix(xdp): propagate XDP rule plumbing failures to caller
Fixes #5935 CxPlatDpRawPlumbRulesOnSocket and CxPlatDpRawInterfaceAddRules now return QUIC_STATUS to propagate XDP rule plumbing failures (XdpCreateProgram, allocation, rule overflow) to the caller. On install failure, CxPlatDpRawPlumbRulesOnSocket stops at the first failed interface and returns the error. The caller (RawSocketCreateUdp) performs best-effort cleanup by re-invoking PlumbRulesOnSocket with IsCreated=FALSE before returning failure. RawSocketCreateUdp checks the return value and rolls back the socket registration on failure. RawSocketDelete deletion is best-effort: logs a warning on failure. Co-authored-by: Jack He <jackhe@microsoft.com>
1 parent 81f96ff commit 90d0346

8 files changed

Lines changed: 192 additions & 20 deletions

File tree

src/generated/linux/datapath_raw.c.clog.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,26 @@ tracepoint(CLOG_DATAPATH_RAW_C, AllocFailure , arg2, arg3);\
4141

4242

4343

44+
/*----------------------------------------------------------
45+
// Decoder Ring for LibraryErrorStatus
46+
// [ lib] ERROR, %u, %s.
47+
// QuicTraceEvent(
48+
LibraryErrorStatus,
49+
"[ lib] ERROR, %u, %s.",
50+
PlumbStatus,
51+
"CxPlatDpRawPlumbRulesOnSocket (delete)");
52+
// arg2 = arg2 = PlumbStatus = arg2
53+
// arg3 = arg3 = "CxPlatDpRawPlumbRulesOnSocket (delete)" = arg3
54+
----------------------------------------------------------*/
55+
#ifndef _clog_4_ARGS_TRACE_LibraryErrorStatus
56+
#define _clog_4_ARGS_TRACE_LibraryErrorStatus(uniqueId, encoded_arg_string, arg2, arg3)\
57+
tracepoint(CLOG_DATAPATH_RAW_C, LibraryErrorStatus , arg2, arg3);\
58+
59+
#endif
60+
61+
62+
63+
4464
/*----------------------------------------------------------
4565
// Decoder Ring for DatapathRecv
4666
// [data][%p] Recv %u bytes (segment=%hu) Src=%!ADDR! Dst=%!ADDR!

src/generated/linux/datapath_raw.c.clog.h.lttng.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,29 @@ TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_C, AllocFailure,
2424

2525

2626

27+
/*----------------------------------------------------------
28+
// Decoder Ring for LibraryErrorStatus
29+
// [ lib] ERROR, %u, %s.
30+
// QuicTraceEvent(
31+
LibraryErrorStatus,
32+
"[ lib] ERROR, %u, %s.",
33+
PlumbStatus,
34+
"CxPlatDpRawPlumbRulesOnSocket (delete)");
35+
// arg2 = arg2 = PlumbStatus = arg2
36+
// arg3 = arg3 = "CxPlatDpRawPlumbRulesOnSocket (delete)" = arg3
37+
----------------------------------------------------------*/
38+
TRACEPOINT_EVENT(CLOG_DATAPATH_RAW_C, LibraryErrorStatus,
39+
TP_ARGS(
40+
unsigned int, arg2,
41+
const char *, arg3),
42+
TP_FIELDS(
43+
ctf_integer(unsigned int, arg2, arg2)
44+
ctf_string(arg3, arg3)
45+
)
46+
)
47+
48+
49+
2750
/*----------------------------------------------------------
2851
// Decoder Ring for DatapathRecv
2952
// [data][%p] Recv %u bytes (segment=%hu) Src=%!ADDR! Dst=%!ADDR!

src/platform/datapath_raw.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,19 @@ RawSocketDelete(
174174
_In_ CXPLAT_SOCKET_RAW* Socket
175175
)
176176
{
177-
CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE);
177+
//
178+
// Rule removal on socket deletion is best-effort. A failure here means
179+
// some XDP rules may linger until the interface is reinitialized, but
180+
// there is nothing useful we can do at this point.
181+
//
182+
QUIC_STATUS PlumbStatus = CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE);
183+
if (QUIC_FAILED(PlumbStatus)) {
184+
QuicTraceEvent(
185+
LibraryErrorStatus,
186+
"[ lib] ERROR, %u, %s.",
187+
PlumbStatus,
188+
"CxPlatDpRawPlumbRulesOnSocket (delete)");
189+
}
178190
CxPlatRemoveSocket(&Socket->RawDatapath->SocketPool, Socket);
179191
CxPlatRundownReleaseAndWait(&Socket->RawRundown);
180192
if (Socket->PausedTcpSend) {

src/platform/datapath_raw.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ CxPlatDpRawUpdatePollingIdleTimeout(
148148
// that it should update any filtering rules as necessary.
149149
//
150150
_IRQL_requires_max_(PASSIVE_LEVEL)
151-
void
151+
QUIC_STATUS
152152
CxPlatDpRawPlumbRulesOnSocket(
153153
_In_ CXPLAT_SOCKET_RAW* Socket,
154154
_In_ BOOLEAN IsCreated

src/platform/datapath_raw_win.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,28 @@ RawSocketCreateUdp(
170170
goto Error;
171171
}
172172

173-
CxPlatDpRawPlumbRulesOnSocket(Socket, TRUE);
173+
Status = CxPlatDpRawPlumbRulesOnSocket(Socket, TRUE);
174+
if (QUIC_FAILED(Status)) {
175+
//
176+
// CxPlatDpRawPlumbRulesOnSocket(TRUE) stops at the first interface
177+
// where rule installation fails, so some interfaces may already have
178+
// partial state (rules installed or port bits set) that must be rolled
179+
// back. Reuse the IsCreated=FALSE path for best-effort cleanup; it is
180+
// safe to call against interfaces where nothing was installed (no-op
181+
// for those). Cleanup failures are logged but do not change the
182+
// returned error.
183+
//
184+
QUIC_STATUS CleanupStatus = CxPlatDpRawPlumbRulesOnSocket(Socket, FALSE);
185+
if (QUIC_FAILED(CleanupStatus)) {
186+
QuicTraceEvent(
187+
LibraryErrorStatus,
188+
"[ lib] ERROR, %u, %s.",
189+
CleanupStatus,
190+
"CxPlatDpRawPlumbRulesOnSocket cleanup");
191+
}
192+
CxPlatRemoveSocket(&Raw->SocketPool, Socket);
193+
goto Error;
194+
}
174195

175196
Error:
176197

src/platform/datapath_raw_xdp_win.c

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -853,7 +853,7 @@ CxPlatDpRawInterfaceInitialize(
853853

854854
_IRQL_requires_max_(PASSIVE_LEVEL)
855855
_Requires_lock_held_(Interface->RuleLock)
856-
void
856+
QUIC_STATUS
857857
CxPlatDpRawInterfaceUpdateRules(
858858
_In_ XDP_INTERFACE* Interface
859859
)
@@ -864,6 +864,8 @@ CxPlatDpRawInterfaceUpdateRules(
864864
.SubLayer = XDP_HOOK_INSPECT,
865865
};
866866

867+
QUIC_STATUS ReturnStatus = QUIC_STATUS_SUCCESS;
868+
867869
for (uint32_t i = 0; i < Interface->QueueCount; i++) {
868870

869871
CXPLAT_QUEUE* Queue = &Interface->Queues[i];
@@ -882,16 +884,14 @@ CxPlatDpRawInterfaceUpdateRules(
882884
Interface->RuleCount,
883885
&NewRxProgram);
884886
if (QUIC_FAILED(Status)) {
885-
//
886-
// TODO - Figure out how to better handle failure and revert changes.
887-
// This will likely require working with XDP to get an improved API;
888-
// possibly to update all queues at once.
889-
//
890887
QuicTraceEvent(
891888
LibraryErrorStatus,
892889
"[ lib] ERROR, %u, %s.",
893890
Status,
894891
"XdpCreateProgram");
892+
if (QUIC_SUCCEEDED(ReturnStatus)) {
893+
ReturnStatus = Status;
894+
}
895895
continue;
896896
}
897897

@@ -901,10 +901,12 @@ CxPlatDpRawInterfaceUpdateRules(
901901

902902
Queue->RxProgram = NewRxProgram;
903903
}
904+
905+
return ReturnStatus;
904906
}
905907

906908
_IRQL_requires_max_(PASSIVE_LEVEL)
907-
void
909+
QUIC_STATUS
908910
CxPlatDpRawInterfaceAddRules(
909911
_In_ XDP_INTERFACE* Interface,
910912
_In_reads_(Count) const XDP_RULE* Rules,
@@ -914,6 +916,8 @@ CxPlatDpRawInterfaceAddRules(
914916
#pragma warning(push)
915917
#pragma warning(disable:6386) // Buffer overrun while writing to 'NewRules' - FALSE POSITIVE
916918

919+
QUIC_STATUS Status;
920+
917921
CxPlatLockAcquire(&Interface->RuleLock);
918922
// TODO - Don't always allocate a new array?
919923

@@ -923,7 +927,7 @@ CxPlatDpRawInterfaceAddRules(
923927
"[ lib] ERROR, %s.",
924928
"No more room for rules");
925929
CxPlatLockRelease(&Interface->RuleLock);
926-
return;
930+
return QUIC_STATUS_BUFFER_TOO_SMALL;
927931
}
928932

929933
const size_t OldSize = sizeof(XDP_RULE) * (size_t)Interface->RuleCount;
@@ -937,7 +941,7 @@ CxPlatDpRawInterfaceAddRules(
937941
"XDP_RULE",
938942
NewSize);
939943
CxPlatLockRelease(&Interface->RuleLock);
940-
return;
944+
return QUIC_STATUS_OUT_OF_MEMORY;
941945
}
942946

943947
if (Interface->RuleCount > 0) {
@@ -952,11 +956,13 @@ CxPlatDpRawInterfaceAddRules(
952956
}
953957
Interface->Rules = NewRules;
954958

955-
CxPlatDpRawInterfaceUpdateRules(Interface);
959+
Status = CxPlatDpRawInterfaceUpdateRules(Interface);
956960

957961
CxPlatLockRelease(&Interface->RuleLock);
958962

959963
#pragma warning(pop)
964+
965+
return Status;
960966
}
961967

962968
_IRQL_requires_max_(PASSIVE_LEVEL)
@@ -1423,12 +1429,13 @@ CxPlatDpRawClearPortBit(
14231429
}
14241430

14251431
_IRQL_requires_max_(PASSIVE_LEVEL)
1426-
void
1432+
QUIC_STATUS
14271433
CxPlatDpRawPlumbRulesOnSocket(
14281434
_In_ CXPLAT_SOCKET_RAW* Socket,
14291435
_In_ BOOLEAN IsCreated
14301436
)
14311437
{
1438+
QUIC_STATUS Status = QUIC_STATUS_SUCCESS;
14321439
XDP_DATAPATH* Xdp = (XDP_DATAPATH*)Socket->RawDatapath;
14331440
if (Socket->Wildcard) {
14341441
XDP_RULE Rules[5] = {0};
@@ -1504,7 +1511,17 @@ CxPlatDpRawPlumbRulesOnSocket(
15041511
for (Entry = Xdp->Interfaces.Flink; Entry != &Xdp->Interfaces; Entry = Entry->Flink) {
15051512
XDP_INTERFACE* Interface = CONTAINING_RECORD(Entry, XDP_INTERFACE, Link);
15061513
if (IsCreated) {
1507-
CxPlatDpRawInterfaceAddRules(Interface, Rules, RulesSize);
1514+
QUIC_STATUS AddStatus = CxPlatDpRawInterfaceAddRules(Interface, Rules, RulesSize);
1515+
if (QUIC_FAILED(AddStatus)) {
1516+
//
1517+
// Stop on first failure and propagate. The caller is
1518+
// responsible for invoking this function again with
1519+
// IsCreated=FALSE to roll back any rules already
1520+
// installed on previous interfaces.
1521+
//
1522+
Status = AddStatus;
1523+
break;
1524+
}
15081525
} else {
15091526
CxPlatDpRawInterfaceRemoveRules(Interface, Rules, RulesSize);
15101527
}
@@ -1566,14 +1583,37 @@ CxPlatDpRawPlumbRulesOnSocket(
15661583
"Allocation of '%s' failed. (%llu bytes)",
15671584
"PortSet",
15681585
XDP_PORT_SET_BUFFER_SIZE);
1569-
return;
1586+
Status = QUIC_STATUS_OUT_OF_MEMORY;
1587+
break;
15701588
}
15711589
CxPlatDpRawSetPortBit(
15721590
(uint8_t*)NewRule.Pattern.IpPortSet.PortSet.PortSet,
15731591
Socket->LocalAddress.Ipv4.sin_port);
15741592
memcpy(
15751593
&NewRule.Pattern.IpPortSet.Address, IpAddress, IpAddressSize);
1576-
CxPlatDpRawInterfaceAddRules(Interface, &NewRule, 1);
1594+
QUIC_STATUS AddStatus = CxPlatDpRawInterfaceAddRules(Interface, &NewRule, 1);
1595+
if (QUIC_FAILED(AddStatus)) {
1596+
Status = AddStatus;
1597+
if (AddStatus == QUIC_STATUS_OUT_OF_MEMORY ||
1598+
AddStatus == QUIC_STATUS_BUFFER_TOO_SMALL) {
1599+
//
1600+
// The rule was not copied into Interface->Rules — we still own
1601+
// the PortSet buffer and must free it. On XdpCreateProgram failure,
1602+
// the rule was already added to Interface->Rules which now owns
1603+
// the PortSet buffer; do not free it in that case.
1604+
//
1605+
CxPlatFree(
1606+
(uint8_t*)NewRule.Pattern.IpPortSet.PortSet.PortSet,
1607+
PORT_SET_TAG);
1608+
}
1609+
//
1610+
// Stop on first failure and propagate. The caller is
1611+
// responsible for invoking this function again with
1612+
// IsCreated=FALSE to roll back any port bits already
1613+
// set on previous interfaces.
1614+
//
1615+
break;
1616+
}
15771617
}
15781618
} else {
15791619
//
@@ -1588,6 +1628,8 @@ CxPlatDpRawPlumbRulesOnSocket(
15881628
}
15891629
}
15901630
}
1631+
1632+
return Status;
15911633
}
15921634

15931635
_IRQL_requires_max_(DISPATCH_LEVEL)

src/platform/datapath_xplat.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,17 +162,18 @@ CxPlatSocketCreateUdp(
162162
QuicTraceLogVerbose(
163163
RawSockCreateFail,
164164
"[sock] Failed to create raw socket, status:%d", Status);
165-
BOOLEAN IsWildcardAddr = Config->LocalAddress == NULL || QuicAddrIsWildCard(Config->LocalAddress);
166-
if (IsWildcardAddr && RequiresQtip) {
165+
BOOLEAN IsServerSocket = !(*NewSocket)->HasFixedRemoteAddress;
166+
if (IsServerSocket && RequiresQtip) {
167167
//
168168
// This retry loop is purely for QTIP listener sockets that try to reserve both a UDP/TCP port,
169169
// which may run into a port collision for TCP if the UDP ephemeral port collides with something
170170
// in the TCP pool. So just try it again.
171171
//
172172
CxPlatSocketDelete(*NewSocket);
173+
*NewSocket = NULL;
173174
continue;
174175
}
175-
if ((!RequiresQtip && !CibirRequested) || ((*NewSocket)->HasFixedRemoteAddress && CibirRequested && !RequiresQtip)) {
176+
if ((!RequiresQtip && !CibirRequested) || (!IsServerSocket && CibirRequested && !RequiresQtip)) {
176177
//
177178
// Allow fallback to OS UDP sockets in these 2 cases only:
178179
// - XDP with no QTIP and no CIBIR.
@@ -187,6 +188,7 @@ CxPlatSocketCreateUdp(
187188
Status = QUIC_STATUS_SUCCESS;
188189
} else {
189190
CxPlatSocketDelete(*NewSocket);
191+
*NewSocket = NULL;
190192
}
191193
goto Error;
192194
}
@@ -195,6 +197,7 @@ CxPlatSocketCreateUdp(
195197
ErrNoXdpForQtip,
196198
"[sock] Error: app requested QTIP but XDP not enabled/available/initialized.");
197199
CxPlatSocketDelete(*NewSocket);
200+
*NewSocket = NULL;
198201
Status = QUIC_STATUS_INVALID_STATE;
199202
goto Error;
200203
} else if (CibirRequested) {

src/platform/unittest/DataPathTest.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,4 +1357,55 @@ TEST_P(DataPathTest, TcpDataServer)
13571357
ASSERT_TRUE(CxPlatEventWaitWithTimeout(ClientContext.ReceiveEvent, 500));
13581358
}
13591359

1360+
//
1361+
// CxPlatSetAllocFailDenominator is only available in DEBUG builds, so
1362+
// this test must be DEBUG-only.
1363+
//
1364+
#ifdef DEBUG
1365+
TEST_F(DataPathTest, XdpRuleAddOomCleanup)
1366+
{
1367+
//
1368+
// Verify the XDP rule plumbing failure path: when CxPlatDpRawInterfaceAddRules
1369+
// fails (e.g. due to allocation failure), CxPlatDpRawPlumbRulesOnSocket must
1370+
// propagate the error and RawSocketCreateUdp must invoke
1371+
// CxPlatDpRawPlumbRulesOnSocket(FALSE) for best-effort cleanup of any
1372+
// partially-installed state, then return failure to the caller without
1373+
// leaking resources or crashing. Only meaningful when running with the
1374+
// XDP/DuoNic datapath.
1375+
//
1376+
if (!UseDuoNic) {
1377+
GTEST_SKIP();
1378+
}
1379+
1380+
QUIC_GLOBAL_EXECUTION_CONFIG Config = { QUIC_GLOBAL_EXECUTION_CONFIG_FLAG_NONE, 0, 1, {0} };
1381+
CxPlatDataPath Datapath(&EmptyUdpCallbacks, nullptr, 0, &Config);
1382+
VERIFY_QUIC_SUCCESS(Datapath.GetInitStatus());
1383+
ASSERT_TRUE(Datapath.IsSupported(CXPLAT_DATAPATH_FEATURE_RAW, CXPLAT_SOCKET_FLAG_XDP));
1384+
1385+
//
1386+
// Create a client (connected) QTIP socket — providing a RemoteAddress
1387+
// makes RawSocketCreateUdp take the connected-socket path which reaches
1388+
// CxPlatDpRawPlumbRulesOnSocket. A non-wildcard local + no remote would
1389+
// be rejected with QUIC_STATUS_INVALID_STATE before rule plumbing runs.
1390+
// QTIP also disables the wildcard retry loop and the OS-socket fallback
1391+
// in CxPlatSocketCreateUdp so the rule-plumbing failure propagates
1392+
// deterministically.
1393+
//
1394+
QuicAddr RemoteAddr = GetNewLocalIPv4();
1395+
1396+
//
1397+
// Fail every 2nd allocation so the socket struct (alloc #1) succeeds and
1398+
// the XDP rule array allocation inside CxPlatDpRawInterfaceAddRules
1399+
// (alloc #2) is the one that fails, exercising both the failure-propagation
1400+
// path and the RawSocketCreateUdp-driven cleanup path.
1401+
//
1402+
CxPlatSetAllocFailDenominator(-2);
1403+
{
1404+
CxPlatSocket Socket(Datapath, nullptr, &RemoteAddr.SockAddr, nullptr, CXPLAT_SOCKET_FLAG_XDP | CXPLAT_SOCKET_FLAG_QTIP);
1405+
ASSERT_TRUE(QUIC_FAILED(Socket.GetInitStatus()));
1406+
}
1407+
CxPlatSetAllocFailDenominator(0);
1408+
}
1409+
#endif // DEBUG
1410+
13601411
INSTANTIATE_TEST_SUITE_P(DataPathTest, DataPathTest, ::testing::Values(4, 6), testing::PrintToStringParamName());

0 commit comments

Comments
 (0)