Skip to content

Commit 34117e4

Browse files
authored
Convert application close frame to transport close frame during handshake. (#4169)
1 parent c5d8086 commit 34117e4

18 files changed

+376
-98
lines changed

src/core/connection.c

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,7 @@ QuicErrorCodeToStatus(
13901390
case QUIC_ERROR_NO_ERROR: return QUIC_STATUS_SUCCESS;
13911391
case QUIC_ERROR_CONNECTION_REFUSED: return QUIC_STATUS_CONNECTION_REFUSED;
13921392
case QUIC_ERROR_PROTOCOL_VIOLATION: return QUIC_STATUS_PROTOCOL_ERROR;
1393+
case QUIC_ERROR_APPLICATION_ERROR:
13931394
case QUIC_ERROR_CRYPTO_USER_CANCELED: return QUIC_STATUS_USER_CANCELED;
13941395
case QUIC_ERROR_CRYPTO_HANDSHAKE_FAILURE: return QUIC_STATUS_HANDSHAKE_FAILURE;
13951396
case QUIC_ERROR_CRYPTO_NO_APPLICATION_PROTOCOL: return QUIC_STATUS_ALPN_NEG_FAILURE;
@@ -1450,27 +1451,6 @@ QuicConnTryClose(
14501451
}
14511452
}
14521453

1453-
if (!ClosedRemotely) {
1454-
1455-
if ((Flags & QUIC_CLOSE_APPLICATION) &&
1456-
Connection->Crypto.TlsState.WriteKey < QUIC_PACKET_KEY_1_RTT) {
1457-
//
1458-
// Application close can only happen if we are using 1-RTT keys.
1459-
// Otherwise we have to send "user_canceled" TLS error code as a
1460-
// connection close. Overwrite all application provided parameters.
1461-
//
1462-
Flags &= ~QUIC_CLOSE_APPLICATION;
1463-
ErrorCode = QUIC_ERROR_CRYPTO_USER_CANCELED;
1464-
RemoteReasonPhrase = NULL;
1465-
RemoteReasonPhraseLength = 0;
1466-
1467-
QuicTraceLogConnInfo(
1468-
CloseUserCanceled,
1469-
Connection,
1470-
"Connection close using user canceled error");
1471-
}
1472-
}
1473-
14741454
BOOLEAN ResultQuicStatus = !!(Flags & QUIC_CLOSE_QUIC_STATUS);
14751455

14761456
BOOLEAN IsFirstCloseForConnection = TRUE;
@@ -3694,6 +3674,15 @@ QuicConnGetKeyOrDeferDatagram(
36943674
return FALSE;
36953675
}
36963676

3677+
if (QuicConnIsServer(Connection) && !Connection->State.HandshakeConfirmed &&
3678+
Packet->KeyType == QUIC_PACKET_KEY_1_RTT) {
3679+
//
3680+
// A server MUST NOT process incoming 1-RTT protected packets before the TLS
3681+
// handshake is complete.
3682+
//
3683+
return FALSE;
3684+
}
3685+
36973686
_Analysis_assume_(Packet->KeyType >= 0 && Packet->KeyType < QUIC_PACKET_KEY_COUNT);
36983687
if (Connection->Crypto.TlsState.ReadKeys[Packet->KeyType] == NULL) {
36993688
//
@@ -5090,12 +5079,33 @@ QuicConnRecvFrames(
50905079
if (Frame.ApplicationClosed) {
50915080
Flags |= QUIC_CLOSE_APPLICATION;
50925081
}
5093-
QuicConnTryClose(
5094-
Connection,
5095-
Flags,
5096-
Frame.ErrorCode,
5097-
Frame.ReasonPhrase,
5098-
(uint16_t)Frame.ReasonPhraseLength);
5082+
5083+
if (!Frame.ApplicationClosed && Frame.ErrorCode == QUIC_ERROR_APPLICATION_ERROR) {
5084+
//
5085+
// The APPLICATION_ERROR transport error should be sent only
5086+
// when closing the connection before the handshake is
5087+
// confirmed. In such case, we can also expect peer to send the
5088+
// application CONNECTION_CLOSE frame in a 1-RTT packet
5089+
// (presumably also in the same UDP datagram).
5090+
//
5091+
// We want to prioritize reporting the application-layer error
5092+
// code to the application, so we postpone the call to
5093+
// QuicConnTryClose and check again after processing incoming
5094+
// datagrams in case it does not arrive.
5095+
//
5096+
QuicTraceEvent(
5097+
ConnDelayCloseApplicationError,
5098+
"[conn][%p] Received APPLICATION_ERROR error, delaying close in expectation of a 1-RTT CONNECTION_CLOSE frame.",
5099+
Connection);
5100+
Connection->State.DelayedApplicationError = TRUE;
5101+
} else {
5102+
QuicConnTryClose(
5103+
Connection,
5104+
Flags,
5105+
Frame.ErrorCode,
5106+
Frame.ReasonPhrase,
5107+
(uint16_t)Frame.ReasonPhraseLength);
5108+
}
50995109

51005110
AckEliciting = TRUE;
51015111
Packet->HasNonProbingFrame = TRUE;
@@ -5126,7 +5136,7 @@ QuicConnRecvFrames(
51265136
HandshakeConfirmedFrame,
51275137
Connection,
51285138
"Handshake confirmed (frame)");
5129-
QuicCryptoHandshakeConfirmed(&Connection->Crypto);
5139+
QuicCryptoHandshakeConfirmed(&Connection->Crypto, TRUE);
51305140
}
51315141

51325142
AckEliciting = TRUE;
@@ -5628,9 +5638,7 @@ QuicConnRecvDatagrams(
56285638
Packet->BufferLength - (uint16_t)(Packet->AvailBuffer - Packet->Buffer);
56295639
}
56305640

5631-
if (Connection->Crypto.CertValidationPending ||
5632-
Connection->Crypto.TicketValidationPending ||
5633-
!QuicConnRecvHeader(
5641+
if (!QuicConnRecvHeader(
56345642
Connection,
56355643
Packet,
56365644
Cipher + BatchCount * CXPLAT_HP_SAMPLE_LENGTH)) {
@@ -5737,6 +5745,20 @@ QuicConnRecvDatagrams(
57375745
BatchCount = 0; // cppcheck-suppress unreadVariable; NOLINT
57385746
}
57395747

5748+
if (Connection->State.DelayedApplicationError && Connection->CloseStatus == 0) {
5749+
//
5750+
// We received transport APPLICATION_ERROR, but didn't receive the expected
5751+
// CONNECTION_ERROR frame, so close the connection with originally postponed
5752+
// APPLICATION_ERROR.
5753+
//
5754+
QuicConnTryClose(
5755+
Connection,
5756+
QUIC_CLOSE_REMOTE | QUIC_CLOSE_SEND_NOTIFICATION,
5757+
QUIC_ERROR_APPLICATION_ERROR,
5758+
NULL,
5759+
(uint16_t)0);
5760+
}
5761+
57405762
if (RecvState.ResetIdleTimeout) {
57415763
QuicConnResetIdleTimeout(Connection);
57425764
}

src/core/connection.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ typedef union QUIC_CONNECTION_STATE {
194194
//
195195
BOOLEAN TimestampRecvNegotiated : 1;
196196

197+
//
198+
// Indicates we received APPLICATION_ERROR transport error and are checking also
199+
// later packets in case they contain CONNECTION_CLOSE frame with application-layer error.
200+
//
201+
BOOLEAN DelayedApplicationError : 1;
202+
197203
#ifdef CxPlatVerifierEnabledByAddr
198204
//
199205
// The calling app is being verified (app or driver verifier).

src/core/crypto.c

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -485,15 +485,18 @@ QuicCryptoOnVersionChange(
485485
_IRQL_requires_max_(PASSIVE_LEVEL)
486486
void
487487
QuicCryptoHandshakeConfirmed(
488-
_In_ QUIC_CRYPTO* Crypto
488+
_In_ QUIC_CRYPTO* Crypto,
489+
_In_ BOOLEAN SignalBinding
489490
)
490491
{
491492
QUIC_CONNECTION* Connection = QuicCryptoGetConnection(Crypto);
492493
Connection->State.HandshakeConfirmed = TRUE;
493494

494-
QUIC_PATH* Path = &Connection->Paths[0];
495-
CXPLAT_DBG_ASSERT(Path->Binding != NULL);
496-
QuicBindingOnConnectionHandshakeConfirmed(Path->Binding, Connection);
495+
if (SignalBinding) {
496+
QUIC_PATH* Path = &Connection->Paths[0];
497+
CXPLAT_DBG_ASSERT(Path->Binding != NULL);
498+
QuicBindingOnConnectionHandshakeConfirmed(Path->Binding, Connection);
499+
}
497500

498501
QuicCryptoDiscardKeys(Crypto, QUIC_PACKET_KEY_HANDSHAKE);
499502
}
@@ -1578,7 +1581,13 @@ QuicCryptoProcessTlsCompletion(
15781581
Connection,
15791582
"Handshake confirmed (server)");
15801583
QuicSendSetSendFlag(&Connection->Send, QUIC_CONN_SEND_FLAG_HANDSHAKE_DONE);
1581-
QuicCryptoHandshakeConfirmed(&Connection->Crypto);
1584+
//
1585+
// Don't signal handshake confirmed to binding yet, we need to keep
1586+
// the hash entry around to be able to associate potential Handshake
1587+
// packets to this connection. The binding will be signaled when the
1588+
// HANDSHAKE_DONE frame is confirmed received by the client.
1589+
//
1590+
QuicCryptoHandshakeConfirmed(&Connection->Crypto, FALSE);
15821591

15831592
//
15841593
// Take this opportinuty to clean up the client chosen initial CID.
@@ -1720,6 +1729,13 @@ QuicCryptoCustomCertValidationComplete(
17201729
QuicCryptoGetConnection(Crypto),
17211730
"Custom cert validation succeeded");
17221731
QuicCryptoProcessDataComplete(Crypto, Crypto->PendingValidationBufferLength);
1732+
1733+
if (QuicRecvBufferHasUnreadData(&Crypto->RecvBuffer)) {
1734+
//
1735+
// More data was received while waiting for user to perform the validation.
1736+
//
1737+
QuicCryptoProcessData(Crypto, FALSE);
1738+
}
17231739
} else {
17241740
QuicTraceEvent(
17251741
ConnError,
@@ -1757,6 +1773,13 @@ QuicCryptoCustomTicketValidationComplete(
17571773
//
17581774
Crypto->TicketValidationPending = FALSE;
17591775
QuicCryptoProcessDataComplete(Crypto, Crypto->PendingValidationBufferLength);
1776+
1777+
if (QuicRecvBufferHasUnreadData(&Crypto->RecvBuffer)) {
1778+
//
1779+
// More data was received while waiting for user to perform the validation.
1780+
//
1781+
QuicCryptoProcessData(Crypto, FALSE);
1782+
}
17601783
} else {
17611784
//
17621785
// Need to rollback status before processing client's initial packet, because outgoing buffer and
@@ -1800,6 +1823,14 @@ QuicCryptoProcessData(
18001823
uint32_t BufferCount = 1;
18011824
QUIC_BUFFER Buffer;
18021825

1826+
if (Crypto->CertValidationPending ||
1827+
(Crypto->TicketValidationPending && !Crypto->TicketValidationRejecting)) {
1828+
//
1829+
// An async validation is pending, don't process any more data until it is complete.
1830+
//
1831+
return Status;
1832+
}
1833+
18031834
if (IsClientInitial) {
18041835
Buffer.Length = 0;
18051836
Buffer.Buffer = NULL;

src/core/crypto.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ QuicCryptoReset(
175175
_IRQL_requires_max_(PASSIVE_LEVEL)
176176
void
177177
QuicCryptoHandshakeConfirmed(
178-
_In_ QUIC_CRYPTO* Crypto
178+
_In_ QUIC_CRYPTO* Crypto,
179+
_In_ BOOLEAN SignalBinding
179180
);
180181

181182
//

src/core/frame.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ extern "C" {
6969
//
7070
#define QUIC_ERROR_PROTOCOL_VIOLATION 0xA
7171
//
72+
// The application or application protocol caused the connection to be closed.
73+
//
74+
#define QUIC_ERROR_APPLICATION_ERROR 0xB
75+
//
7276
// An endpoint has received more data in CRYPTO frames than it can buffer.
7377
//
7478
#define QUIC_ERROR_CRYPTO_BUFFER_EXCEEDED 0xD

src/core/loss_detection.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ QuicLossDetectionOnPacketAcknowledged(
514514
HandshakeConfirmedAck,
515515
Connection,
516516
"Handshake confirmed (ack)");
517-
QuicCryptoHandshakeConfirmed(&Connection->Crypto);
517+
QuicCryptoHandshakeConfirmed(&Connection->Crypto, TRUE);
518518
}
519519

520520
QUIC_PACKET_SPACE* PacketSpace = Connection->Packets[QUIC_ENCRYPT_LEVEL_1_RTT];
@@ -624,6 +624,10 @@ QuicLossDetectionOnPacketAcknowledged(
624624
QUIC_DATAGRAM_SEND_ACKNOWLEDGED_SPURIOUS :
625625
QUIC_DATAGRAM_SEND_ACKNOWLEDGED);
626626
break;
627+
628+
case QUIC_FRAME_HANDSHAKE_DONE:
629+
QuicCryptoHandshakeConfirmed(&Connection->Crypto, TRUE);
630+
break;
627631
}
628632
}
629633

src/core/packet_builder.c

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -481,16 +481,34 @@ QuicPacketBuilderGetPacketTypeAndKeyForControlFrames(
481481

482482
QUIC_PACKET_KEY_TYPE MaxKeyType = Connection->Crypto.TlsState.WriteKey;
483483

484-
if (QuicConnIsClient(Connection) &&
485-
!Connection->State.HandshakeConfirmed &&
486-
MaxKeyType == QUIC_PACKET_KEY_1_RTT &&
487-
(SendFlags & QUIC_CONN_SEND_FLAG_CONNECTION_CLOSE)) {
484+
if (SendFlags & (QUIC_CONN_SEND_FLAG_CONNECTION_CLOSE | QUIC_CONN_SEND_FLAG_APPLICATION_CLOSE)) {
488485
//
489-
// Server is not allowed to process 1-RTT packets until the handshake is confirmed and since we are
490-
// closing the connection, the handshake is unlikely to complete. Ensure the CONNECTION_CLOSE is sent
491-
// in a packet which server can process.
486+
// CLOSE is ready to be sent. The peer might not be able to read current
487+
// highest key, so the CLOSE frame should be sent at the current and
488+
// previous encryption levels if the handshake hasn't been confirmed.
492489
//
493-
MaxKeyType = QUIC_PACKET_KEY_HANDSHAKE;
490+
if (!Connection->State.HandshakeConfirmed && MaxKeyType >= QUIC_PACKET_KEY_HANDSHAKE) {
491+
QUIC_PACKET_KEY_TYPE PreviousKeyType =
492+
MaxKeyType == QUIC_PACKET_KEY_1_RTT
493+
? QUIC_PACKET_KEY_HANDSHAKE
494+
: QUIC_PACKET_KEY_INITIAL;
495+
496+
if ((Builder->Datagram == NULL || Builder->DatagramLength == 0) &&
497+
Connection->Crypto.TlsState.WriteKeys[PreviousKeyType] != NULL) {
498+
MaxKeyType = PreviousKeyType; // Use the lower key for the first packet in a datagram.
499+
}
500+
}
501+
502+
//
503+
// Don't use 0-RTT key for sending CLOSE frames.
504+
//
505+
if (MaxKeyType == QUIC_PACKET_KEY_0_RTT) {
506+
*PacketKeyType = QUIC_PACKET_KEY_INITIAL;
507+
} else {
508+
*PacketKeyType = MaxKeyType;
509+
}
510+
511+
return TRUE;
494512
}
495513

496514
for (QUIC_PACKET_KEY_TYPE KeyType = 0;
@@ -540,16 +558,11 @@ QuicPacketBuilderGetPacketTypeAndKeyForControlFrames(
540558
}
541559
}
542560

543-
if (SendFlags & (QUIC_CONN_SEND_FLAG_CONNECTION_CLOSE | QUIC_CONN_SEND_FLAG_PING)) {
544-
//
545-
// CLOSE or PING is ready to be sent. This is always sent with the
546-
// current write key.
561+
if (SendFlags & QUIC_CONN_SEND_FLAG_PING) {
547562
//
548-
// TODO - This logic isn't correct. The peer might not be able to read
549-
// this key, so the CLOSE frame should be sent at the current and
550-
// previous encryption level if the handshake hasn't been confirmed.
563+
// PING is ready to be sent. This is always sent with the current write key.
551564
//
552-
if (Connection->Crypto.TlsState.WriteKey == QUIC_PACKET_KEY_0_RTT) {
565+
if (MaxKeyType == QUIC_PACKET_KEY_0_RTT) {
553566
*PacketKeyType = QUIC_PACKET_KEY_INITIAL;
554567
} else {
555568
*PacketKeyType = MaxKeyType;

src/core/send.c

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -516,8 +516,7 @@ QuicSendWriteFrames(
516516
}
517517
}
518518

519-
if ((Send->SendFlags & QUIC_CONN_SEND_FLAG_CONNECTION_CLOSE) ||
520-
((Send->SendFlags & QUIC_CONN_SEND_FLAG_APPLICATION_CLOSE) && Is1RttEncryptionLevel)) {
519+
if (Send->SendFlags & (QUIC_CONN_SEND_FLAG_CONNECTION_CLOSE | QUIC_CONN_SEND_FLAG_APPLICATION_CLOSE)) {
521520
BOOLEAN IsApplicationClose =
522521
!!(Send->SendFlags & QUIC_CONN_SEND_FLAG_APPLICATION_CLOSE);
523522
if (Connection->State.ClosedRemotely) {
@@ -529,12 +528,28 @@ QuicSendWriteFrames(
529528
IsApplicationClose = FALSE;
530529
}
531530

531+
QUIC_VAR_INT CloseErrorCode = Connection->CloseErrorCode;
532+
char* CloseReasonPhrase = Connection->CloseReasonPhrase;
533+
534+
if (IsApplicationClose && ! Is1RttEncryptionLevel) {
535+
//
536+
// A CONNECTION_CLOSE of type 0x1d MUST be replaced by a CONNECTION_CLOSE of
537+
// type 0x1c when sending the frame in Initial or Handshake packets. Otherwise,
538+
// information about the application state might be revealed. Endpoints MUST
539+
// clear the value of the Reason Phrase field and SHOULD use the APPLICATION_ERROR
540+
// code when converting to a CONNECTION_CLOSE of type 0x1c.
541+
//
542+
CloseErrorCode = QUIC_ERROR_APPLICATION_ERROR;
543+
CloseReasonPhrase = NULL;
544+
IsApplicationClose = FALSE;
545+
}
546+
532547
QUIC_CONNECTION_CLOSE_EX Frame = {
533548
IsApplicationClose,
534-
Connection->State.ClosedRemotely ? 0 : Connection->CloseErrorCode,
549+
CloseErrorCode,
535550
0, // TODO - Set the FrameType field.
536-
Connection->CloseReasonPhrase == NULL ? 0 : strlen(Connection->CloseReasonPhrase),
537-
Connection->CloseReasonPhrase
551+
CloseReasonPhrase == NULL ? 0 : strlen(CloseReasonPhrase),
552+
CloseReasonPhrase
538553
};
539554

540555
if (QuicConnCloseFrameEncode(
@@ -543,7 +558,15 @@ QuicSendWriteFrames(
543558
AvailableBufferLength,
544559
Builder->Datagram->Buffer)) {
545560

546-
Send->SendFlags &= ~(QUIC_CONN_SEND_FLAG_CONNECTION_CLOSE | QUIC_CONN_SEND_FLAG_APPLICATION_CLOSE);
561+
//
562+
// We potentially send the close frame on multiple protection levels.
563+
// We send in increasing encryption level so clear the flag only once
564+
// we send on the current protection level.
565+
//
566+
if (Builder->Key->Type == Connection->Crypto.TlsState.WriteKey) {
567+
Send->SendFlags &= ~(QUIC_CONN_SEND_FLAG_CONNECTION_CLOSE | QUIC_CONN_SEND_FLAG_APPLICATION_CLOSE);
568+
}
569+
547570
(void)QuicPacketBuilderAddFrame(
548571
Builder, IsApplicationClose ? QUIC_FRAME_CONNECTION_CLOSE_1 : QUIC_FRAME_CONNECTION_CLOSE, FALSE);
549572
} else {

src/core/send.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ QuicPacketTypeToEncryptLevelV2(
187187
QUIC_CONN_SEND_FLAG_ACK | \
188188
QUIC_CONN_SEND_FLAG_CRYPTO | \
189189
QUIC_CONN_SEND_FLAG_CONNECTION_CLOSE | \
190+
QUIC_CONN_SEND_FLAG_APPLICATION_CLOSE | /* not allowed directly, but will be converted to CONNECTION_CLOSE */ \
190191
QUIC_CONN_SEND_FLAG_PING \
191192
)
192193

0 commit comments

Comments
 (0)