Skip to content

Fix client hang when server closes during handshake - #182

Draft
josephnoir wants to merge 2 commits into
apple:mainfrom
josephnoir:rh/error-during-handshake
Draft

josephnoir wants to merge 2 commits into
apple:mainfrom
josephnoir:rh/error-during-handshake

Conversation

@josephnoir

@josephnoir josephnoir commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

The test testConnectionError in the StreamTableTests has started failing frequently with 0.4.0. Bisec points to #157 as the change in flakiness. However, the preexisting issue is that closing during the handshake sends the wrong error code. RFC 9000 10.2.3.:

A CONNECTION_CLOSE of type 0x1d MUST be replaced by a CONNECTION_CLOSE of type 0x1c when sending the frame in Initial or Handshake packets. Otherwise, information about the application state might be revealed.

This PR adds regression tests and two fixes:

  • Send 0x000C when the packet space applicationData has not been reached.
  • close the connection when deciding not continue processing.

A server that closes with an application error before it has processed
the client's Finished still has key state .handshake. It then sends
APPLICATION_CLOSE (0x1d) in a Handshake packet, which RFC 9000 §10.2.3
forbids. Since apple#157 the client rejects that frame but drops the
resulting close, so it hangs until the idle timeout.

- Add testQUICServerApplicationCloseBeforeHandshakeComplete: drop all
  client packets after the ClientHello so the server closes at key
  state .handshake, and check that the client disconnects without
  having to reject an APPLICATION_CLOSE
- Add testQUICClientClosesOnFrameNotAllowedDuringHandshake: the server
  sends HANDSHAKE_DONE in a Handshake packet and the client must close
  right away. This covers the receiver side on its own, since fixing
  the sender keeps the first test from reaching that path
- Add waitForServerConnected to QUICTestHarness so a test can continue
  once only the client is connected

Both tests fail until the fix in the next commit.
A server that closes with an application error before it has processed
the client's Finished still has key state .handshake, so it sent
APPLICATION_CLOSE (0x1d) in a Handshake packet. Before apple#157 the client
processed that frame anyway. Since then it rejects it as a
PROTOCOL_VIOLATION, but the close is deferred during inbound processing
and was dropped when handleInboundPacket returned false, so the client
never reported the disconnect and waited for the idle timeout.

- Send CONNECTION_CLOSE (0x1c) with APPLICATION_ERROR and no reason
  instead of APPLICATION_CLOSE in Initial and Handshake packets
  (RFC 9000 §10.2.3)
- Run the deferred close when handleInboundPacket stops processing a
  datagram
@josephnoir josephnoir added the 🔨 semver/patch No public API change. label Sep 29, 2026
@agnosticdev

Copy link
Copy Markdown
Collaborator

Thanks for finding this and fixing @josephnoir!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 semver/patch No public API change.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants