Skip to content

Commit b38e627

Browse files
committed
Connection abort
1 parent ff298ab commit b38e627

File tree

7 files changed

+111
-32
lines changed

7 files changed

+111
-32
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,4 +674,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
674674
<data name="Http3ErrorControlStreamFrameReceivedBeforeSettings" xml:space="preserve">
675675
<value>The client sent a {frameType} frame to a control stream before the SETTINGS frame.</value>
676676
</data>
677+
<data name="Http3ErrorControlStreamReservedSetting" xml:space="preserve">
678+
<value>The client sent a reserved setting {identifier}.</value>
679+
</data>
677680
</root>

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ internal class Http3Connection : ITimeoutHandler
3939

4040
private readonly Http3PeerSettings _serverSettings = new Http3PeerSettings();
4141
private readonly StreamCloseAwaitable _streamCompletionAwaitable = new StreamCloseAwaitable();
42-
42+
private readonly IProtocolErrorCodeFeature _errorCodeFeature;
4343

4444
public Http3Connection(Http3ConnectionContext context)
4545
{
@@ -49,6 +49,8 @@ public Http3Connection(Http3ConnectionContext context)
4949
_timeoutControl = new TimeoutControl(this);
5050
_context.TimeoutControl ??= _timeoutControl;
5151

52+
_errorCodeFeature = context.ConnectionFeatures.Get<IProtocolErrorCodeFeature>()!;
53+
5254
var httpLimits = context.ServiceContext.ServerOptions.Limits;
5355

5456
_serverSettings.HeaderTableSize = (uint)httpLimits.Http3.HeaderTableSize;
@@ -163,7 +165,7 @@ private bool TryClose()
163165
return false;
164166
}
165167

166-
public void Abort(ConnectionAbortedException ex)
168+
public void Abort(ConnectionAbortedException ex, Http3ErrorCode errorCode)
167169
{
168170
bool previousState;
169171

@@ -180,6 +182,7 @@ public void Abort(ConnectionAbortedException ex)
180182
SendGoAway(_highestOpenedStreamId);
181183
}
182184

185+
_errorCodeFeature.Error = (long)errorCode;
183186
_multiplexedContext.Abort(ex);
184187
}
185188
}
@@ -363,7 +366,7 @@ internal async Task InnerProcessStreamsAsync<TContext>(IHttpApplication<TContext
363366
}
364367
catch
365368
{
366-
Abort(connectionError);
369+
Abort(connectionError, Http3ErrorCode.NoError);
367370
throw;
368371
}
369372
}

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Buffers;
66
using System.Collections.Generic;
7+
using System.Globalization;
78
using System.IO.Pipelines;
89
using System.Net.Http;
910
using System.Threading;
@@ -214,8 +215,9 @@ private async Task HandleControlStream()
214215
return;
215216
}
216217
}
217-
catch (Http3StreamErrorException)
218+
catch (Http3ConnectionErrorException ex)
218219
{
220+
_http3Connection.Abort(new ConnectionAbortedException(ex.Message, ex), ex.ErrorCode);
219221
}
220222
finally
221223
{
@@ -299,17 +301,27 @@ private void ProcessSetting(long id, long value)
299301
// These are client settings, for outbound traffic.
300302
switch (id)
301303
{
304+
case 0x0:
305+
case 0x2:
306+
case 0x3:
307+
case 0x4:
308+
case 0x5:
309+
// HTTP/2 settings are reserved.
310+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-7.2.4.1-5
311+
var message = CoreStrings.FormatHttp3ErrorControlStreamReservedSetting("0x" + id.ToString("X", CultureInfo.InvariantCulture));
312+
throw new Http3ConnectionErrorException(message, Http3ErrorCode.SettingsError);
302313
case (long)Http3SettingType.QPackMaxTableCapacity:
303314
_http3Connection.ApplyMaxTableCapacity(value);
304315
break;
305-
case (long)Http3SettingType.MaxHeaderListSize:
316+
case (long)Http3SettingType.MaxFieldSectionSize:
306317
_http3Connection.ApplyMaxHeaderListSize(value);
307318
break;
308319
case (long)Http3SettingType.QPackBlockedStreams:
309320
_http3Connection.ApplyBlockedStream(value);
310321
break;
311322
default:
312323
// Ignore all unknown settings.
324+
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html#section-7.2.4
313325
break;
314326
}
315327
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal List<Http3PeerSetting> GetNonProtocolDefaults()
2828

2929
if (MaxRequestHeaderFieldSize != DefaultMaxRequestHeaderFieldSize)
3030
{
31-
list.Add(new Http3PeerSetting(Http3SettingType.MaxHeaderListSize, MaxRequestHeaderFieldSize));
31+
list.Add(new Http3PeerSetting(Http3SettingType.MaxFieldSectionSize, MaxRequestHeaderFieldSize));
3232
}
3333

3434
return list;

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3
55
{
66
internal enum Http3SettingType : long
77
{
8+
// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-5
89
QPackMaxTableCapacity = 0x1,
910
/// <summary>
10-
/// SETTINGS_MAX_HEADER_LIST_SIZE, default is unlimited.
11+
/// SETTINGS_MAX_FIELD_SECTION_SIZE, default is unlimited.
12+
/// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-5
1113
/// </summary>
12-
MaxHeaderListSize = 0x6,
14+
MaxFieldSectionSize = 0x6,
15+
// https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-5
1316
QPackBlockedStreams = 0x7
1417
}
1518
}

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

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Linq;
76
using System.Net.Http;
87
using System.Text;
98
using System.Threading.Tasks;
109
using Microsoft.AspNetCore.Connections;
11-
using Microsoft.AspNetCore.Connections.Features;
12-
using Microsoft.AspNetCore.Http;
13-
using Microsoft.AspNetCore.Http.Features;
14-
using Microsoft.AspNetCore.Testing;
10+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
1511
using Microsoft.Net.Http.Headers;
1612
using Xunit;
1713

@@ -27,9 +23,12 @@ public async Task GoAwayReceived()
2723
var outboundcontrolStream = await CreateControlStream();
2824
var inboundControlStream = await GetInboundControlStream();
2925

30-
Connection.Abort(new ConnectionAbortedException());
26+
Connection.Abort(new ConnectionAbortedException(), Http3ErrorCode.NoError);
3127
await _closedStateReached.Task.DefaultTimeout();
32-
await WaitForConnectionErrorAsync(ignoreNonGoAwayFrames: true, expectedLastStreamId: 0, expectedErrorCode: 0);
28+
await WaitForConnectionErrorAsync(
29+
ignoreNonGoAwayFrames: true,
30+
expectedLastStreamId: 0,
31+
expectedErrorCode: Http3ErrorCode.NoError);
3332
}
3433

3534
[Fact]
@@ -83,5 +82,24 @@ public async Task GracefulServerShutdownSendsGoawayClosesConnection()
8382
MultiplexedConnectionContext.ConnectionClosingCts.Cancel();
8483
Assert.Null(await MultiplexedConnectionContext.AcceptAsync().DefaultTimeout());
8584
}
85+
86+
[Fact]
87+
public async Task SETTINGS_ReservedSettingSent_ConnectionError()
88+
{
89+
await InitializeConnectionAsync(_echoApplication);
90+
91+
var outboundcontrolStream = await CreateControlStream();
92+
await outboundcontrolStream.SendSettingsAsync(new List<Http3PeerSetting>
93+
{
94+
new Http3PeerSetting(0x0, 0) // reserved value
95+
});
96+
97+
await GetInboundControlStream();
98+
99+
await WaitForConnectionErrorAsync(
100+
ignoreNonGoAwayFrames: true,
101+
expectedLastStreamId: 0,
102+
expectedErrorCode: Http3ErrorCode.SettingsError);
103+
}
86104
}
87105
}

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

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,12 @@ internal async Task WaitForConnectionErrorAsync(bool ignoreNonGoAwayFrames, long
149149
}
150150
}
151151

152-
VerifyGoAway(frame, expectedLastStreamId, expectedErrorCode);
152+
Assert.Equal((long)expectedErrorCode, MultiplexedConnectionContext.Error);
153+
154+
VerifyGoAway(frame, expectedLastStreamId);
153155
}
154156

155-
internal void VerifyGoAway(Http3FrameWithPayload frame, long expectedLastStreamId, Http3ErrorCode expectedErrorCode)
157+
internal void VerifyGoAway(Http3FrameWithPayload frame, long expectedLastStreamId)
156158
{
157159
Assert.Equal(Http3FrameType.GoAway, frame.Type);
158160
var payload = frame.Payload;
@@ -329,6 +331,19 @@ internal async Task<Http3FrameWithPayload> ReceiveFrameAsync()
329331
}
330332
}
331333
}
334+
335+
internal async Task SendFrameAsync(Http3RawFrame frame, Memory<byte> data, bool endStream = false)
336+
{
337+
var outputWriter = _pair.Application.Output;
338+
frame.Length = data.Length;
339+
Http3FrameWriter.WriteHeader(frame, outputWriter);
340+
await SendAsync(data.Span);
341+
342+
if (endStream)
343+
{
344+
await _pair.Application.Output.CompleteAsync();
345+
}
346+
}
332347
}
333348

334349
internal class Http3RequestStream : Http3StreamBase, IHttpHeadersHandler, IProtocolErrorCodeFeature
@@ -383,19 +398,6 @@ internal async Task SendDataAsync(Memory<byte> data, bool endStream = false)
383398
await SendFrameAsync(frame, data, endStream);
384399
}
385400

386-
internal async Task SendFrameAsync(Http3RawFrame frame, Memory<byte> data, bool endStream = false)
387-
{
388-
var outputWriter = _pair.Application.Output;
389-
frame.Length = data.Length;
390-
Http3FrameWriter.WriteHeader(frame, outputWriter);
391-
await SendAsync(data.Span);
392-
393-
if (endStream)
394-
{
395-
await _pair.Application.Output.CompleteAsync();
396-
}
397-
}
398-
399401
internal async Task<Dictionary<string, string>> ExpectHeadersAsync()
400402
{
401403
var http3WithPayload = await ReceiveFrameAsync();
@@ -483,7 +485,7 @@ public Http3ControlStream(Http3TestBase testBase)
483485
var inputPipeOptions = GetInputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool);
484486
var outputPipeOptions = GetOutputPipeOptions(_testBase._serviceContext, _testBase._memoryPool, PipeScheduler.ThreadPool);
485487
_pair = DuplexPipe.CreateConnectionPair(inputPipeOptions, outputPipeOptions);
486-
StreamContext = new TestStreamContext(canRead: false, canWrite: true, _pair, this);
488+
StreamContext = new TestStreamContext(canRead: true, canWrite: false, _pair, this);
487489
}
488490

489491
public Http3ControlStream(ConnectionContext streamContext)
@@ -507,6 +509,41 @@ void WriteSpan(PipeWriter pw)
507509
await FlushAsync(writableBuffer);
508510
}
509511

512+
internal async Task SendSettingsAsync(List<Http3PeerSetting> settings, bool endStream = false)
513+
{
514+
var frame = new Http3RawFrame();
515+
frame.PrepareSettings();
516+
517+
var settingsLength = CalculateSettingsSize(settings);
518+
var buffer = new byte[settingsLength];
519+
WriteSettings(settings, buffer);
520+
521+
await SendFrameAsync(frame, buffer, endStream);
522+
}
523+
524+
internal static int CalculateSettingsSize(List<Http3PeerSetting> settings)
525+
{
526+
var length = 0;
527+
foreach (var setting in settings)
528+
{
529+
length += VariableLengthIntegerHelper.GetByteCount((long)setting.Parameter);
530+
length += VariableLengthIntegerHelper.GetByteCount(setting.Value);
531+
}
532+
return length;
533+
}
534+
535+
internal static void WriteSettings(List<Http3PeerSetting> settings, Span<byte> destination)
536+
{
537+
foreach (var setting in settings)
538+
{
539+
var parameterLength = VariableLengthIntegerHelper.WriteInteger(destination, (long)setting.Parameter);
540+
destination = destination.Slice(parameterLength);
541+
542+
var valueLength = VariableLengthIntegerHelper.WriteInteger(destination, (long)setting.Value);
543+
destination = destination.Slice(valueLength);
544+
}
545+
}
546+
510547
public async ValueTask<long> TryReadStreamIdAsync()
511548
{
512549
while (true)
@@ -540,7 +577,7 @@ public async ValueTask<long> TryReadStreamIdAsync()
540577
}
541578
}
542579

543-
public class TestMultiplexedConnectionContext : MultiplexedConnectionContext, IConnectionLifetimeNotificationFeature, IConnectionLifetimeFeature, IConnectionHeartbeatFeature
580+
public class TestMultiplexedConnectionContext : MultiplexedConnectionContext, IConnectionLifetimeNotificationFeature, IConnectionLifetimeFeature, IConnectionHeartbeatFeature, IProtocolErrorCodeFeature
544581
{
545582
public readonly Channel<ConnectionContext> ToServerAcceptQueue = Channel.CreateUnbounded<ConnectionContext>(new UnboundedChannelOptions
546583
{
@@ -562,6 +599,7 @@ public TestMultiplexedConnectionContext(Http3TestBase testBase)
562599
Features = new FeatureCollection();
563600
Features.Set<IConnectionLifetimeNotificationFeature>(this);
564601
Features.Set<IConnectionHeartbeatFeature>(this);
602+
Features.Set<IProtocolErrorCodeFeature>(this);
565603
ConnectionClosedRequested = ConnectionClosingCts.Token;
566604
}
567605

@@ -575,6 +613,8 @@ public TestMultiplexedConnectionContext(Http3TestBase testBase)
575613

576614
public CancellationTokenSource ConnectionClosingCts { get; set; } = new CancellationTokenSource();
577615

616+
public long Error { get; set; }
617+
578618
public override void Abort()
579619
{
580620
Abort(new ConnectionAbortedException());

0 commit comments

Comments
 (0)