diff --git a/docs/api/DatagramSend.md b/docs/api/DatagramSend.md
index ba37151ff1..d0f380f18d 100644
--- a/docs/api/DatagramSend.md
+++ b/docs/api/DatagramSend.md
@@ -21,7 +21,36 @@ QUIC_STATUS
# Parameters
-**TODO**
+`Connection`
+
+The current established connection.
+
+`Buffers`
+
+An array of `QUIC_BUFFER` structs that each contain a pointer and length to app data to send on the stream. This may be `NULL` **only** if `BufferCount` is zero.
+
+`BufferCount`
+
+The number of `QUIC_BUFFER` structs in the `Buffers` array. This may be zero.
+
+`Flags`
+
+The set of flags that controls the behavior of `DatagramSend`:
+
+Value | Meaning
+--- | ---
+**QUIC_SEND_FLAG_NONE**
0 | No special behavior. Data is not allowed in 0-RTT by default.
+**QUIC_SEND_FLAG_ALLOW_0_RTT**
1 | Indicates that the data is allowed to be sent in 0-RTT (if available). Makes no guarantee the data will be sent in 0-RTT. Additionally, even if 0-RTT keys are available the data may end up being sent in 1-RTT for multiple reasons.
+**QUIC_SEND_FLAG_START**
2 | **Unused and ignored** for `DatagramSend`
+**QUIC_SEND_FLAG_FIN**
4 | **Unused and ignored** for `DatagramSend`
+**QUIC_SEND_FLAG_DGRAM_PRIORITY**
8 | Sets a priority to ensure a datagram is sent before others.
+**QUIC_SEND_FLAG_DELAY_SEND**
16 | **Unused and ignored** for `DatagramSend`
+**QUIC_SEND_FLAG_CANCEL_ON_LOSS**
32 | **Unused and ignored** for `DatagramSend`
+**QUIC_SEND_FLAG_CANCEL_ON_BLOCKED**
64 | Allows MsQuic to drop frames when all the data that could be sent has been flushed out, but there are still some frames remaining in the queue.
+
+`ClientSendContext`
+
+The app context pointer (possibly null) to be associated with the send.
# Return Value
diff --git a/docs/api/StreamSend.md b/docs/api/StreamSend.md
index c8a5b03fc4..bf662c8d3f 100644
--- a/docs/api/StreamSend.md
+++ b/docs/api/StreamSend.md
@@ -46,6 +46,7 @@ Value | Meaning
**QUIC_SEND_FLAG_DGRAM_PRIORITY**
8 | **Unused and ignored** for `StreamSend`
**QUIC_SEND_FLAG_DELAY_SEND**
16 | Provides a hint to MsQuic to indicate the data does not need to be sent immediately, likely because more is soon to follow.
**QUIC_SEND_FLAG_CANCEL_ON_LOSS**
32 | Informs MsQuic to irreversibly mark the associated stream to be canceled when packet loss has been detected on it. I.e., all sends on a given stream are subject to this behavior from the moment the flag has been supplied for the first time.
+**QUIC_SEND_FLAG_CANCEL_ON_BLOCKED**
64 | **Unused and ignored** for `StreamSend` for now
`ClientSendContext`
diff --git a/src/core/datagram.c b/src/core/datagram.c
index 0d794d85a5..4786fa1280 100644
--- a/src/core/datagram.c
+++ b/src/core/datagram.c
@@ -588,3 +588,40 @@ QuicDatagramProcessFrame(
return TRUE;
}
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+QuicDatagramCancelBlocked(
+ _In_ QUIC_CONNECTION* Connection
+ )
+{
+ QUIC_DATAGRAM* Datagram = &Connection->Datagram;
+ QUIC_SEND_REQUEST** SendQueue = &Datagram->SendQueue;
+
+ if (*SendQueue == NULL) {
+ return;
+ }
+
+ do {
+ if ((*SendQueue)->Flags & QUIC_SEND_FLAG_CANCEL_ON_BLOCKED) {
+ QUIC_SEND_REQUEST* SendRequest = *SendQueue;
+ if (Datagram->PrioritySendQueueTail == &SendRequest->Next) {
+ Datagram->PrioritySendQueueTail = SendQueue;
+ }
+ *SendQueue = SendRequest->Next;
+ QuicDatagramCancelSend(Connection, SendRequest);
+ } else {
+ SendQueue = &((*SendQueue)->Next);
+ }
+ } while (*SendQueue != NULL);
+
+ Datagram->SendQueueTail = SendQueue;
+
+ if (Datagram->SendQueue != NULL) {
+ QuicSendSetSendFlag(&Connection->Send, QUIC_CONN_SEND_FLAG_DATAGRAM);
+ } else {
+ QuicSendClearSendFlag(&Connection->Send, QUIC_CONN_SEND_FLAG_DATAGRAM);
+ }
+
+ QuicDatagramValidate(Datagram);
+}
diff --git a/src/core/datagram.h b/src/core/datagram.h
index 86f3772ff2..95caf08ceb 100644
--- a/src/core/datagram.h
+++ b/src/core/datagram.h
@@ -103,3 +103,9 @@ QuicDatagramProcessFrame(
const uint8_t * const Buffer,
_Inout_ uint16_t* Offset
);
+
+_IRQL_requires_max_(PASSIVE_LEVEL)
+void
+QuicDatagramCancelBlocked(
+ _In_ QUIC_CONNECTION* Connection
+ );
diff --git a/src/core/send.c b/src/core/send.c
index 306801d4bf..a0f155b8f3 100644
--- a/src/core/send.c
+++ b/src/core/send.c
@@ -1497,6 +1497,11 @@ QuicSendFlush(
//QuicConnUpdatePeerPacketTolerance(Connection, Builder.TotalCountDatagrams);
}
+ //
+ // Clears the SendQueue list of not sent packets if the flag is applied
+ //
+ QuicDatagramCancelBlocked(Connection);
+
return Result != QUIC_SEND_INCOMPLETE;
}
#pragma warning(pop)
diff --git a/src/cs/lib/msquic_generated.cs b/src/cs/lib/msquic_generated.cs
index 375e6897b0..3e6f9d96b7 100644
--- a/src/cs/lib/msquic_generated.cs
+++ b/src/cs/lib/msquic_generated.cs
@@ -197,6 +197,7 @@ internal enum QUIC_SEND_FLAGS
DELAY_SEND = 0x0010,
CANCEL_ON_LOSS = 0x0020,
PRIORITY_WORK = 0x0040,
+ CANCEL_ON_BLOCKED = 0x0080,
}
internal enum QUIC_DATAGRAM_SEND_STATE
diff --git a/src/ffi/linux_bindings.rs b/src/ffi/linux_bindings.rs
index 9b4f449f4a..0d50d32a73 100644
--- a/src/ffi/linux_bindings.rs
+++ b/src/ffi/linux_bindings.rs
@@ -390,6 +390,7 @@ pub const QUIC_SEND_FLAGS_QUIC_SEND_FLAG_DGRAM_PRIORITY: QUIC_SEND_FLAGS = 8;
pub const QUIC_SEND_FLAGS_QUIC_SEND_FLAG_DELAY_SEND: QUIC_SEND_FLAGS = 16;
pub const QUIC_SEND_FLAGS_QUIC_SEND_FLAG_CANCEL_ON_LOSS: QUIC_SEND_FLAGS = 32;
pub const QUIC_SEND_FLAGS_QUIC_SEND_FLAG_PRIORITY_WORK: QUIC_SEND_FLAGS = 64;
+pub const QUIC_SEND_FLAGS_QUIC_SEND_FLAG_CANCEL_ON_BLOCKED: QUIC_SEND_FLAGS = 128;
pub type QUIC_SEND_FLAGS = ::std::os::raw::c_uint;
pub const QUIC_DATAGRAM_SEND_STATE_QUIC_DATAGRAM_SEND_UNKNOWN: QUIC_DATAGRAM_SEND_STATE = 0;
pub const QUIC_DATAGRAM_SEND_STATE_QUIC_DATAGRAM_SEND_SENT: QUIC_DATAGRAM_SEND_STATE = 1;
diff --git a/src/ffi/win_bindings.rs b/src/ffi/win_bindings.rs
index 0b564fdcaf..d6ac54efcb 100644
--- a/src/ffi/win_bindings.rs
+++ b/src/ffi/win_bindings.rs
@@ -389,6 +389,7 @@ pub const QUIC_SEND_FLAGS_QUIC_SEND_FLAG_DGRAM_PRIORITY: QUIC_SEND_FLAGS = 8;
pub const QUIC_SEND_FLAGS_QUIC_SEND_FLAG_DELAY_SEND: QUIC_SEND_FLAGS = 16;
pub const QUIC_SEND_FLAGS_QUIC_SEND_FLAG_CANCEL_ON_LOSS: QUIC_SEND_FLAGS = 32;
pub const QUIC_SEND_FLAGS_QUIC_SEND_FLAG_PRIORITY_WORK: QUIC_SEND_FLAGS = 64;
+pub const QUIC_SEND_FLAGS_QUIC_SEND_FLAG_CANCEL_ON_BLOCKED: QUIC_SEND_FLAGS = 128;
pub type QUIC_SEND_FLAGS = ::std::os::raw::c_int;
pub const QUIC_DATAGRAM_SEND_STATE_QUIC_DATAGRAM_SEND_UNKNOWN: QUIC_DATAGRAM_SEND_STATE = 0;
pub const QUIC_DATAGRAM_SEND_STATE_QUIC_DATAGRAM_SEND_SENT: QUIC_DATAGRAM_SEND_STATE = 1;
diff --git a/src/inc/msquic.h b/src/inc/msquic.h
index 825bfb8a6a..b8dc5471db 100644
--- a/src/inc/msquic.h
+++ b/src/inc/msquic.h
@@ -244,6 +244,7 @@ typedef enum QUIC_SEND_FLAGS {
QUIC_SEND_FLAG_DELAY_SEND = 0x0010, // Indicates the send should be delayed because more will be queued soon.
QUIC_SEND_FLAG_CANCEL_ON_LOSS = 0x0020, // Indicates that a stream is to be cancelled when packet loss is detected.
QUIC_SEND_FLAG_PRIORITY_WORK = 0x0040, // Higher priority than other connection work.
+ QUIC_SEND_FLAG_CANCEL_ON_BLOCKED = 0x0080, // Indicates that a frame should be dropped when it can't be sent immediately.
} QUIC_SEND_FLAGS;
DEFINE_ENUM_FLAG_OPERATORS(QUIC_SEND_FLAGS)
diff --git a/src/plugins/trace/README.md b/src/plugins/trace/README.md
index c97b4825eb..5966c07df0 100644
--- a/src/plugins/trace/README.md
+++ b/src/plugins/trace/README.md
@@ -51,7 +51,9 @@ One of the built-in capabilities of WPA is the ability to analyze CPU trace info
### Linux
Linux perf command is one of the way to collect such information.
```sh
-# on Linux
+# on Linux (kernel > 5.10)
+sudo apt-get install -y linux-perf
+# on Linux (kernel <= 5.10)
sudo apt-get install -y linux-tools-`uname -r`
# use your own options
perf record -a -g -F 10 -o out.perf.data
diff --git a/src/test/MsQuicTests.h b/src/test/MsQuicTests.h
index aaae75274e..77de8d913f 100644
--- a/src/test/MsQuicTests.h
+++ b/src/test/MsQuicTests.h
@@ -642,6 +642,11 @@ QuicTestDatagramSend(
_In_ int Family
);
+void
+QuicTestDatagramDrop(
+ _In_ int Family
+ );
+
//
// Storage tests
//
@@ -1331,4 +1336,8 @@ typedef struct {
QUIC_CTL_CODE(125, METHOD_BUFFERED, FILE_WRITE_DATA)
// BOOLEAN - EnableResumption
-#define QUIC_MAX_IOCTL_FUNC_CODE 125
+#define IOCTL_QUIC_RUN_DATAGRAM_DROP \
+ QUIC_CTL_CODE(126, METHOD_BUFFERED, FILE_WRITE_DATA)
+ // int - Family
+
+#define QUIC_MAX_IOCTL_FUNC_CODE 126
diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp
index 2010fb93bd..5b01ffb796 100644
--- a/src/test/bin/quic_gtest.cpp
+++ b/src/test/bin/quic_gtest.cpp
@@ -2352,6 +2352,15 @@ TEST_P(WithFamilyArgs, DatagramSend) {
}
}
+TEST_P(WithFamilyArgs, DatagramDrop) {
+ TestLoggerT Logger("QuicTestDatagramDrop", GetParam());
+ if (TestingKernelMode) {
+ ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_DATAGRAM_DROP, GetParam().Family));
+ } else {
+ QuicTestDatagramDrop(GetParam().Family);
+ }
+}
+
#ifdef _WIN32 // Storage tests only supported on Windows
static BOOLEAN CanRunStorageTests = FALSE;
diff --git a/src/test/bin/winkernel/control.cpp b/src/test/bin/winkernel/control.cpp
index bdeab35fd3..1558f81396 100644
--- a/src/test/bin/winkernel/control.cpp
+++ b/src/test/bin/winkernel/control.cpp
@@ -524,6 +524,7 @@ size_t QUIC_IOCTL_BUFFER_SIZES[] =
0,
0,
sizeof(BOOLEAN),
+ sizeof(INT32),
};
CXPLAT_STATIC_ASSERT(
@@ -955,6 +956,13 @@ QuicTestCtlEvtIoDeviceControl(
Params->Family));
break;
+ case IOCTL_QUIC_RUN_DATAGRAM_DROP:
+ CXPLAT_FRE_ASSERT(Params != nullptr);
+ QuicTestCtlRun(
+ QuicTestDatagramDrop(
+ Params->Family));
+ break;
+
case IOCTL_QUIC_RUN_NAT_PORT_REBIND:
CXPLAT_FRE_ASSERT(Params != nullptr);
QuicTestCtlRun(
diff --git a/src/test/lib/DatagramTest.cpp b/src/test/lib/DatagramTest.cpp
index 5c19fd731e..421e022378 100644
--- a/src/test/lib/DatagramTest.cpp
+++ b/src/test/lib/DatagramTest.cpp
@@ -267,3 +267,102 @@ QuicTestDatagramSend(
}
}
}
+
+void
+QuicTestDatagramDrop(
+ _In_ int Family
+ )
+{
+ MsQuicRegistration Registration;
+ TEST_TRUE(Registration.IsValid());
+
+ MsQuicAlpn Alpn("MsQuicTest");
+
+ MsQuicSettings Settings;
+ Settings.SetDatagramReceiveEnabled(true);
+
+ MsQuicCredentialConfig ClientCredConfig;
+ MsQuicConfiguration ClientConfiguration(Registration, Alpn, Settings, ClientCredConfig);
+ TEST_TRUE(ClientConfiguration.IsValid());
+
+ MsQuicConfiguration ServerConfiguration(Registration, Alpn, Settings, ServerSelfSignedCredConfig);
+ TEST_TRUE(ServerConfiguration.IsValid());
+
+ uint8_t RawBuffer[1100] = {0};
+ QUIC_BUFFER DatagramBuffer = { sizeof(RawBuffer), RawBuffer };
+
+ SelectiveLossHelper LossHelper;
+
+ {
+ TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration);
+ TEST_TRUE(Listener.IsValid());
+
+ QUIC_ADDRESS_FAMILY QuicAddrFamily = (Family == 4) ? QUIC_ADDRESS_FAMILY_INET : QUIC_ADDRESS_FAMILY_INET6;
+ QuicAddr ServerLocalAddr(QuicAddrFamily);
+ 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());
+
+ TEST_TRUE(Client.GetDatagramSendEnabled());
+
+ for (int i = 0; i < 20; i++) {
+ TEST_QUIC_SUCCEEDED(
+ MsQuic->DatagramSend(
+ Client.GetConnection(),
+ &DatagramBuffer,
+ 1,
+ (i%2 == 0) ? QUIC_SEND_FLAG_CANCEL_ON_BLOCKED : QUIC_SEND_FLAG_NONE,
+ nullptr));
+ }
+
+ TEST_QUIC_SUCCEEDED(
+ Client.Start(
+ ClientConfiguration,
+ QuicAddrFamily,
+ QUIC_TEST_LOOPBACK_FOR_AF(QuicAddrFamily),
+ ServerLocalAddr.GetPort()));
+
+ if (!Client.WaitForConnectionComplete()) {
+ return;
+ }
+ TEST_TRUE(Client.GetIsConnected());
+
+ TEST_TRUE(Client.GetDatagramSendEnabled());
+
+ TEST_NOT_EQUAL(nullptr, Server);
+ if (!Server->WaitForConnectionComplete()) {
+ return;
+ }
+ TEST_TRUE(Server->GetIsConnected());
+
+ TEST_TRUE(Server->GetDatagramSendEnabled());
+
+ CxPlatSleep(100);
+
+ uint32_t Tries = 0;
+ while (Client.GetDatagramsSent() != 10 && Client.GetDatagramsCanceled() != 10 && ++Tries < 10) {
+ CxPlatSleep(100);
+ }
+
+ TEST_EQUAL(10, Client.GetDatagramsCanceled());
+ TEST_EQUAL(10, Client.GetDatagramsSent());
+
+ Client.Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, QUIC_TEST_NO_ERROR);
+ if (!Client.WaitForShutdownComplete()) {
+ return;
+ }
+
+ TEST_FALSE(Client.GetPeerClosed());
+ TEST_FALSE(Client.GetTransportClosed());
+ }
+ }
+ }
+}