Skip to content

Commit ff298ab

Browse files
committed
Add HTTP/3 connection exception with code
1 parent 31373aa commit ff298ab

File tree

12 files changed

+90
-67
lines changed

12 files changed

+90
-67
lines changed

src/Servers/Kestrel/Core/src/CoreStrings.resx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
654654
<value>An error occurred after the response headers were sent, a reset is being sent.</value>
655655
</data>
656656
<data name="Http3StreamErrorDataReceivedBeforeHeaders" xml:space="preserve">
657-
<value>The client sent a DATA frame before the HEADERS frame.</value>
657+
<value>The client sent a DATA frame to a request stream before the HEADERS frame.</value>
658658
</data>
659659
<data name="Http3StreamErrorFrameReceivedAfterTrailers" xml:space="preserve">
660660
<value>The client sent a {frameType} frame after trailing HEADERS.</value>
@@ -665,4 +665,13 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
665665
<data name="Http3ErrorUnsupportedFrameOnServer" xml:space="preserve">
666666
<value>The client sent a {frameType} frame to the server which isn't supported.</value>
667667
</data>
668+
<data name="Http3ErrorUnsupportedFrameOnControlStream" xml:space="preserve">
669+
<value>The client sent a {frameType} frame to a control stream which isn't supported.</value>
670+
</data>
671+
<data name="Http3ErrorControlStreamMultipleSettingsFrames" xml:space="preserve">
672+
<value>The client sent a SETTINGS frame to a control stream that already has settings.</value>
673+
</data>
674+
<data name="Http3ErrorControlStreamFrameReceivedBeforeSettings" xml:space="preserve">
675+
<value>The client sent a {frameType} frame to a control stream before the SETTINGS frame.</value>
676+
</data>
668677
</root>

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,8 @@ internal async Task InnerProcessStreamsAsync<TContext>(IHttpApplication<TContext
326326
Log.RequestProcessingError(_context.ConnectionId, ex);
327327
error = ex;
328328
}
329-
catch (Http3ConnectionException ex)
329+
catch (Http3ConnectionErrorException ex)
330330
{
331-
// TODO Connection error code?
332331
Log.Http3ConnectionError(_context.ConnectionId, ex);
333332
error = ex;
334333
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Net.Http;
6+
7+
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
8+
{
9+
internal class Http3ConnectionErrorException : Exception
10+
{
11+
public Http3ConnectionErrorException(string message, Http3ErrorCode errorCode)
12+
: base($"HTTP/3 stream error ({errorCode}): {message}")
13+
{
14+
ErrorCode = errorCode;
15+
}
16+
17+
public Http3ErrorCode ErrorCode { get; }
18+
}
19+
}

src/Servers/Kestrel/Core/src/Internal/Http3/Http3ConnectionException.cs

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
155155
{
156156
if (!_http3Connection.SetInboundControlStream(this))
157157
{
158-
// TODO propagate these errors to connection.
159-
throw new Http3ConnectionException("HTTP_STREAM_CREATION_ERROR");
158+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-6.2.1
159+
throw new Http3ConnectionErrorException("Only one inbound control stream per peer is permitted.", Http3ErrorCode.StreamCreationError);
160160
}
161161

162162
await HandleControlStream();
@@ -165,7 +165,8 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
165165
{
166166
if (!_http3Connection.SetInboundEncoderStream(this))
167167
{
168-
throw new Http3ConnectionException("HTTP_STREAM_CREATION_ERROR");
168+
// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-4.2
169+
throw new Http3ConnectionErrorException("Only one inbound encoder stream per peer is permitted.", Http3ErrorCode.StreamCreationError);
169170
}
170171

171172
await HandleEncodingDecodingTask();
@@ -174,7 +175,8 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
174175
{
175176
if (!_http3Connection.SetInboundDecoderStream(this))
176177
{
177-
throw new Http3ConnectionException("HTTP_STREAM_CREATION_ERROR");
178+
// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-4.2
179+
throw new Http3ConnectionErrorException("Only one inbound decoder stream per peer is permitted.", Http3ErrorCode.StreamCreationError);
178180
}
179181
await HandleEncodingDecodingTask();
180182
}
@@ -238,33 +240,32 @@ private async ValueTask HandleEncodingDecodingTask()
238240

239241
private ValueTask ProcessHttp3ControlStream(in ReadOnlySequence<byte> payload)
240242
{
241-
// Two things:
242-
// settings must be sent as the first frame of each control stream by each peer
243-
// Can't send more than two settings frames.
244243
switch (_incomingFrame.Type)
245244
{
246245
case Http3FrameType.Data:
247246
case Http3FrameType.Headers:
248247
case Http3FrameType.PushPromise:
249-
throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED");
248+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-7.2
249+
throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnControlStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame);
250250
case Http3FrameType.Settings:
251251
return ProcessSettingsFrameAsync(payload);
252252
case Http3FrameType.GoAway:
253-
return ProcessGoAwayFrameAsync(payload);
253+
return ProcessGoAwayFrameAsync();
254254
case Http3FrameType.CancelPush:
255255
return ProcessCancelPushFrameAsync();
256256
case Http3FrameType.MaxPushId:
257257
return ProcessMaxPushIdFrameAsync();
258258
default:
259-
return ProcessUnknownFrameAsync();
259+
return ProcessUnknownFrameAsync(_incomingFrame.Type);
260260
}
261261
}
262262

263263
private ValueTask ProcessSettingsFrameAsync(ReadOnlySequence<byte> payload)
264264
{
265265
if (_haveReceivedSettingsFrame)
266266
{
267-
throw new Http3ConnectionException("H3_SETTINGS_ERROR");
267+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-settings
268+
throw new Http3ConnectionErrorException(CoreStrings.Http3ErrorControlStreamMultipleSettingsFrames, Http3ErrorCode.UnexpectedFrame);
268269
}
269270

270271
_haveReceivedSettingsFrame = true;
@@ -313,39 +314,53 @@ private void ProcessSetting(long id, long value)
313314
}
314315
}
315316

316-
private ValueTask ProcessGoAwayFrameAsync(ReadOnlySequence<byte> payload)
317+
private ValueTask ProcessGoAwayFrameAsync()
317318
{
318-
throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED");
319+
EnsureSettingsFrame(Http3FrameType.GoAway);
320+
321+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-goaway
322+
// PUSH is not implemented so nothing to do.
323+
324+
// TODO: Double check the connection remains open.
325+
return default;
319326
}
320327

321328
private ValueTask ProcessCancelPushFrameAsync()
322329
{
323-
if (!_haveReceivedSettingsFrame)
324-
{
325-
throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED");
326-
}
330+
EnsureSettingsFrame(Http3FrameType.CancelPush);
331+
332+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-cancel_push
333+
// PUSH is not implemented so nothing to do.
327334

328335
return default;
329336
}
330337

331338
private ValueTask ProcessMaxPushIdFrameAsync()
332339
{
333-
if (!_haveReceivedSettingsFrame)
334-
{
335-
throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED");
336-
}
340+
EnsureSettingsFrame(Http3FrameType.MaxPushId);
341+
342+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-cancel_push
343+
// PUSH is not implemented so nothing to do.
337344

338345
return default;
339346
}
340347

341-
private ValueTask ProcessUnknownFrameAsync()
348+
private ValueTask ProcessUnknownFrameAsync(Http3FrameType frameType)
349+
{
350+
EnsureSettingsFrame(frameType);
351+
352+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-9
353+
// Unknown frames must be explicitly ignored.
354+
return default;
355+
}
356+
357+
private void EnsureSettingsFrame(Http3FrameType frameType)
342358
{
343359
if (!_haveReceivedSettingsFrame)
344360
{
345-
throw new Http3ConnectionException("HTTP_FRAME_UNEXPECTED");
361+
var message = CoreStrings.FormatHttp3ErrorControlStreamFrameReceivedBeforeSettings(Http3Formatting.ToFormattedType(frameType));
362+
throw new Http3ConnectionErrorException(message, Http3ErrorCode.MissingSettings);
346363
}
347-
348-
return default;
349364
}
350365

351366
public void StopProcessingNextRequest()

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,12 @@ public async Task ProcessRequestAsync<TContext>(IHttpApplication<TContext> appli
387387
error = ex;
388388
Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode);
389389
}
390+
catch (Http3ConnectionErrorException ex)
391+
{
392+
// TODO: Abort overall connection
393+
error = ex;
394+
Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode);
395+
}
390396
catch (Exception ex)
391397
{
392398
error = ex;
@@ -450,18 +456,20 @@ private Task ProcessHttp3Stream<TContext>(IHttpApplication<TContext> application
450456
case Http3FrameType.CancelPush:
451457
case Http3FrameType.GoAway:
452458
case Http3FrameType.MaxPushId:
459+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-7.2.4
453460
// These frames need to be on a control stream
454-
throw new Http3StreamErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame);
461+
throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnRequestStream(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame);
455462
case Http3FrameType.PushPromise:
456463
// The server should never receive push promise
457-
throw new Http3StreamErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame);
464+
throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3ErrorUnsupportedFrameOnServer(_incomingFrame.FormattedType), Http3ErrorCode.UnexpectedFrame);
458465
default:
459466
return ProcessUnknownFrameAsync();
460467
}
461468
}
462469

463470
private Task ProcessUnknownFrameAsync()
464471
{
472+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-9
465473
// Unknown frames must be explicitly ignored.
466474
return Task.CompletedTask;
467475
}
@@ -472,7 +480,7 @@ private Task ProcessHeadersFrameAsync<TContext>(IHttpApplication<TContext> appli
472480
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1
473481
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
474482
{
475-
throw new Http3StreamErrorException(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Headers)), Http3ErrorCode.UnexpectedFrame);
483+
throw new Http3ConnectionErrorException(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Headers)), Http3ErrorCode.UnexpectedFrame);
476484
}
477485

478486
if (_requestHeaderParsingState == RequestHeaderParsingState.Headers)
@@ -515,14 +523,15 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence<byte> payload)
515523
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1
516524
if (_requestHeaderParsingState == RequestHeaderParsingState.Ready)
517525
{
518-
throw new Http3StreamErrorException(CoreStrings.Http3StreamErrorDataReceivedBeforeHeaders, Http3ErrorCode.UnexpectedFrame);
526+
throw new Http3ConnectionErrorException(CoreStrings.Http3StreamErrorDataReceivedBeforeHeaders, Http3ErrorCode.UnexpectedFrame);
519527
}
520528

521529
// DATA frame after trailing headers is invalid.
522530
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-4.1
523531
if (_requestHeaderParsingState == RequestHeaderParsingState.Trailers)
524532
{
525-
throw new Http3StreamErrorException(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Data))), Http3ErrorCode.UnexpectedFrame);
533+
var message = CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(CoreStrings.FormatHttp3StreamErrorFrameReceivedAfterTrailers(Http3Formatting.ToFormattedType(Http3FrameType.Data)));
534+
throw new Http3ConnectionErrorException(message, Http3ErrorCode.UnexpectedFrame);
526535
}
527536

528537
if (InputRemaining.HasValue)

src/Servers/Kestrel/Core/src/Internal/Http3/Http3StreamErrorException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
88
{
9-
class Http3StreamErrorException : Exception
9+
internal class Http3StreamErrorException : Exception
1010
{
1111
public Http3StreamErrorException(string message, Http3ErrorCode errorCode)
1212
: base($"HTTP/3 stream error ({errorCode}): {message}")

src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ internal interface IKestrelTrace : ILogger
8181

8282
void InvalidResponseHeaderRemoved();
8383

84-
void Http3ConnectionError(string connectionId, Http3ConnectionException ex);
84+
void Http3ConnectionError(string connectionId, Http3ConnectionErrorException ex);
8585

8686
void Http3ConnectionClosing(string connectionId);
8787

src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ public void InvalidResponseHeaderRemoved()
329329
_invalidResponseHeaderRemoved(_logger, null);
330330
}
331331

332-
public void Http3ConnectionError(string connectionId, Http3ConnectionException ex)
332+
public void Http3ConnectionError(string connectionId, Http3ConnectionErrorException ex)
333333
{
334334
_http3ConnectionError(_logger, connectionId, ex);
335335
}

src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTrace.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void Http2FrameReceived(string connectionId, Http2Frame frame) { }
6060
public void Http2FrameSending(string connectionId, Http2Frame frame) { }
6161
public void Http2MaxConcurrentStreamsReached(string connectionId) { }
6262
public void InvalidResponseHeaderRemoved() { }
63-
public void Http3ConnectionError(string connectionId, Http3ConnectionException ex) { }
63+
public void Http3ConnectionError(string connectionId, Http3ConnectionErrorException ex) { }
6464
public void Http3ConnectionClosing(string connectionId) { }
6565
public void Http3ConnectionClosed(string connectionId, long highestOpenedStreamId) { }
6666
public void Http3StreamAbort(string traceIdentifier, Http3ErrorCode error, ConnectionAbortedException abortReason) { }

src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ public void InvalidResponseHeaderRemoved()
245245
_trace2.InvalidResponseHeaderRemoved();
246246
}
247247

248-
public void Http3ConnectionError(string connectionId, Http3ConnectionException ex)
248+
public void Http3ConnectionError(string connectionId, Http3ConnectionErrorException ex)
249249
{
250250
_trace1.Http3ConnectionError(connectionId, ex);
251251
_trace2.Http3ConnectionError(connectionId, ex);

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1631,7 +1631,7 @@ public async Task DataBeforeHeaders_UnexpectedFrameError()
16311631

16321632
await requestStream.WaitForStreamErrorAsync(
16331633
Http3ErrorCode.UnexpectedFrame,
1634-
expectedErrorMessage: "The client sent a DATA frame before the HEADERS frame.");
1634+
expectedErrorMessage: CoreStrings.Http3StreamErrorDataReceivedBeforeHeaders);
16351635
}
16361636

16371637
[Fact]

0 commit comments

Comments
 (0)