Indicate the datagram send state to server applications - #6179
Open
Masahiro Kozuka (masa-koz) wants to merge 1 commit into
Open
Indicate the datagram send state to server applications#6179Masahiro Kozuka (masa-koz) wants to merge 1 commit into
Masahiro Kozuka (masa-koz) wants to merge 1 commit into
Conversation
A server application never receives QUIC_CONNECTION_EVENT_DATAGRAM_STATE_CHANGED unless path MTU discovery happens to change the max send length, so on a path where it does not, the application waits forever and never sends a datagram - while receiving them perfectly well. QuicDatagramOnSendStateChanged committed the new state to the QUIC_DATAGRAM unconditionally, but indicated it only when State.ExternalOwner was set. On a server the peer's transport parameters are processed before the listener hands the connection to the application, so that evaluation recorded a state with nobody to report it to, and nothing re-evaluates afterwards. The only rescue is a later evaluation that computes a different length, which in practice means MTU discovery moving Path->Mtu - hence the same build working on one network and hanging on another. Fix it in two parts. QuicConnSetConfiguration now re-evaluates the send state for servers, right after State.Started is set. That is the first point at which a server connection has an owner to indicate to, and Started is an input to the max send length (derived from QUIC_DPLPMTUD_MIN_MTU before it and from the path's MTU after), so the value has to be recomputed there in any case. That alone would still be swallowed whenever the recomputed values happen to match what was recorded while unowned - the peer using an 8 byte connection ID over a 1280 byte path makes them identical. Whether the live state moved and whether the application's view of it is stale are separate questions, so QUIC_DATAGRAM now tracks both: the early return consults the indicated state, the indicated state is updated only where an indication is actually made, and the side effects of a real state change - send shutdown, max length requeue - stay tied to the live state. The indicated state starts out as whatever the application can be assumed to know already. A client opens the connection itself and so knows the documented initial state; a server is handed a connection whose send state was settled before it ever saw it, and assumes it cannot send. This keeps client behaviour identical and adds exactly one new indication: a server learns, once, that it may send datagrams. A server whose peer does not support them is still told nothing, matching what it already assumes. QUIC_DATAGRAM is internal to core, so the added fields are not an API or ABI change. QuicTestDatagramNegotiation now checks that the server was told it can send, rather than only that it can. TestConnection records the state carried by the last DATAGRAM_STATE_CHANGED to make that observable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Masahiro Kozuka (masa-koz)
added a commit
to seera-networks/msquic
that referenced
this pull request
Jul 29, 2026
Description Brings this branch in line with the upstream version of the #47 fix, microsoft#6179. #47 fixed the real bug — a server application is never told it may send datagrams unless path MTU discovery happens to change the max send length — but it changed client behaviour on the way. Preparing the same fix for upstream showed that the client-side part is neither needed nor desirable, so this narrows it. What #47 did to clients #47 re-evaluated the send state for every connection at QuicConnSetConfiguration, and started the indicated state at (FALSE, 0) for both roles. On a client that produces an extra DATAGRAM_STATE_CHANGED at ConnectionStart, carrying SendEnabled = TRUE before the peer's transport parameters are known. That indication is speculative: when the peer turns out not to support datagrams it is reversed a moment later. It also required changing thirteen ordered event expectations in EventTest.cpp, which is the test suite telling us the client's event sequence changed. What this changes Two changes make the fix server-only. The re-evaluation at SetConfiguration runs only for servers. A client's send state changes always have an owner to indicate to, so its indicated state never goes stale and it needs nothing there. The indicated state starts out as what the application can be assumed to know already, which differs by role. A client opens the connection itself, so it knows the documented initial state of (TRUE, UINT16_MAX). A server is handed a connection whose send state was settled before it ever saw it, so it knows nothing and assumes the conservative default of not being able to send. Result Client behaviour is bit-for-bit identical to before Track the datagram send state last indicated to the application #47. Servers gain exactly one indication: the application learns, once, that it may send datagrams. A server whose peer does not support datagrams is still told nothing, matching what it already assumes. No spurious event is introduced. EventTest.cpp needs no changes, which is the check that client behaviour is untouched. The core of #47 — tracking the state last indicated separately from the live state, so a change made while unowned is not mistaken for one already reported — is unchanged and still what makes the fix work when the recomputed values happen to match those recorded while unowned. Testing Also carried over from upstream: QuicTestDatagramNegotiation now checks that the server was told it can send, rather than only that it can. TestConnection records the count and contents of the DATAGRAM_STATE_CHANGED events indicated to it, since GetParam(QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED) reports the live state whether or not the application ever heard about it. With the server re-evaluation removed, DatagramNegotiation fails on all four parameterisations, so the assertion covers the original bug. *Datagram*:*Event*:*ValidateConn*:*Basic*:*Mtu*:*Resumption* passes in full: 529 tests, no failures. No compiler warnings. Documentation No documentation change. docs/API.md and docs/api/QUIC_CONNECTION_EVENT.md already describe the behaviour this implements.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
A server application never receives
QUIC_CONNECTION_EVENT_DATAGRAM_STATE_CHANGEDunless path MTU discovery happens to change the max send length. On a path where it does not, the application waits forever for the event and never sends a datagram, while receiving them perfectly well.docs/API.mdstates that the app receives this event to learn whether the peer supports receiving datagrams, so this is the code not matching the documented behaviour.Cause
QuicDatagramOnSendStateChangedcommits the new state to theQUIC_DATAGRAMunconditionally, but indicates it only whenState.ExternalOwneris set:On a server the peer's transport parameters are processed before the listener hands the connection to the application. That evaluation runs unowned: it records a state that nobody can be told about, and from then on every evaluation compares equal to it and returns early. The change has been accounted for as if it were reported.
The only rescue is a later evaluation computing a different length, which in practice means MTU discovery moving
Path->Mtu. Hence the same build working on one network and hanging on another.Fix
QuicConnSetConfigurationevaluates the send state for servers, right afterState.Startedis set. That is the first point at which both inputs to the state are settled and there is an owner to indicate the result to —Startedselects which MTU the max send length is derived from (QUIC_DPLPMTUD_MIN_MTUbefore it,Path->Mtuafter).That alone is not enough, because the evaluation is still swallowed whenever the recomputed values happen to equal what was recorded while unowned. They are identical when the peer uses an 8 byte connection ID over an IPv6 path with a 1280 byte MTU, which is exactly the pre-
Startedassumption. So whether the live state moved and whether the application's view of it is stale are now tracked separately: the early return consults the indicated state, the indicated state is updated only where an indication is actually made, and the side effects of a real state change — send shutdown, max length requeue — stay tied to the live state.The indicated state starts out as what the application can be assumed to know already. A client opens the connection itself, so it knows the initial state; a server is handed a connection whose send state is settled before it ever sees it, so it knows nothing and assumes the conservative default of not being able to send.
Scope of the behaviour change
QuicDatagramValidate's!SendEnabled ⇒ MaxSendLength == 0invariant holds on every path.QUIC_DATAGRAMis internal tosrc/core, so the two added fields are not an API or ABI change.Testing
QuicTestDatagramNegotiationcovered this path but only asserted that the server can send (GetParam(QUIC_PARAM_CONN_DATAGRAM_SEND_ENABLED)), which was true even while the application was never told. It now also asserts that the server was told:TestConnectionrecords the count and contents of theDATAGRAM_STATE_CHANGEDevents indicated to it.*Datagram*:*Event*:*Mtu*:*Resumption*:*ValidateConn*:*Basic*:*Handshake*set passes: 797 tests, no failures. In particular the ordered event validators inEventTest.cppneeded no changes, confirming client behaviour is untouched.Documentation
No documentation change.
docs/API.mdanddocs/api/QUIC_CONNECTION_EVENT.mdalready describe the behaviour this change implements.