Disallow any frames after a reset is received #2154
This commit is contained in:
parent
5bd2a41517
commit
35d35f22a3
|
|
@ -587,4 +587,7 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
|
||||||
<data name="ConnectionAbortedByClient" xml:space="preserve">
|
<data name="ConnectionAbortedByClient" xml:space="preserve">
|
||||||
<value>The client closed the connection.</value>
|
<value>The client closed the connection.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Http2ErrorStreamAborted" xml:space="preserve">
|
||||||
|
<value>A frame of type {frameType} was received after stream {streamId} was reset or aborted.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|
@ -224,6 +224,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
catch (Http2StreamErrorException ex)
|
catch (Http2StreamErrorException ex)
|
||||||
{
|
{
|
||||||
Log.Http2StreamError(ConnectionId, ex);
|
Log.Http2StreamError(ConnectionId, ex);
|
||||||
|
// The client doesn't know this error is coming, allow draining additional frames for now.
|
||||||
AbortStream(_incomingFrame.StreamId, new IOException(ex.Message, ex));
|
AbortStream(_incomingFrame.StreamId, new IOException(ex.Message, ex));
|
||||||
await _frameWriter.WriteRstStreamAsync(ex.StreamId, ex.ErrorCode);
|
await _frameWriter.WriteRstStreamAsync(ex.StreamId, ex.ErrorCode);
|
||||||
}
|
}
|
||||||
|
|
@ -448,6 +449,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
|
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
|
||||||
{
|
{
|
||||||
|
if (stream.DoNotDrainRequest)
|
||||||
|
{
|
||||||
|
// Hard abort, do not allow any more frames on this stream.
|
||||||
|
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamAborted(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
|
||||||
|
}
|
||||||
if (stream.EndStreamReceived)
|
if (stream.EndStreamReceived)
|
||||||
{
|
{
|
||||||
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
|
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
|
||||||
|
|
@ -501,6 +507,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
|
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
|
||||||
{
|
{
|
||||||
|
if (stream.DoNotDrainRequest)
|
||||||
|
{
|
||||||
|
// Hard abort, do not allow any more frames on this stream.
|
||||||
|
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamAborted(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
|
||||||
|
}
|
||||||
|
|
||||||
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
|
// http://httpwg.org/specs/rfc7540.html#rfc.section.5.1
|
||||||
//
|
//
|
||||||
// ...an endpoint that receives any frames after receiving a frame with the
|
// ...an endpoint that receives any frames after receiving a frame with the
|
||||||
|
|
@ -609,7 +621,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
|
|
||||||
ThrowIfIncomingFrameSentToIdleStream();
|
ThrowIfIncomingFrameSentToIdleStream();
|
||||||
AbortStream(_incomingFrame.StreamId, new IOException(CoreStrings.Http2StreamResetByClient));
|
|
||||||
|
if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
|
||||||
|
{
|
||||||
|
// Second reset
|
||||||
|
if (stream.DoNotDrainRequest)
|
||||||
|
{
|
||||||
|
// Hard abort, do not allow any more frames on this stream.
|
||||||
|
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamAborted(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No additional inbound header or data frames are allowed for this stream after receiving a reset.
|
||||||
|
stream.DisallowAdditionalRequestFrames();
|
||||||
|
stream.Abort(new IOException(CoreStrings.Http2StreamResetByClient));
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
@ -771,6 +796,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
}
|
}
|
||||||
else if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
|
else if (_streams.TryGetValue(_incomingFrame.StreamId, out var stream))
|
||||||
{
|
{
|
||||||
|
if (stream.DoNotDrainRequest)
|
||||||
|
{
|
||||||
|
// Hard abort, do not allow any more frames on this stream.
|
||||||
|
throw new Http2ConnectionErrorException(CoreStrings.FormatHttp2ErrorStreamAborted(_incomingFrame.Type, stream.StreamId), Http2ErrorCode.STREAM_CLOSED);
|
||||||
|
}
|
||||||
|
|
||||||
if (!stream.TryUpdateOutputWindow(_incomingFrame.WindowUpdateSizeIncrement))
|
if (!stream.TryUpdateOutputWindow(_incomingFrame.WindowUpdateSizeIncrement))
|
||||||
{
|
{
|
||||||
throw new Http2StreamErrorException(_incomingFrame.StreamId, CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR);
|
throw new Http2StreamErrorException(_incomingFrame.StreamId, CoreStrings.Http2ErrorWindowUpdateSizeInvalid, Http2ErrorCode.FLOW_CONTROL_ERROR);
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
public bool RequestBodyStarted { get; private set; }
|
public bool RequestBodyStarted { get; private set; }
|
||||||
public bool EndStreamReceived => (_completionState & StreamCompletionFlags.EndStreamReceived) == StreamCompletionFlags.EndStreamReceived;
|
public bool EndStreamReceived => (_completionState & StreamCompletionFlags.EndStreamReceived) == StreamCompletionFlags.EndStreamReceived;
|
||||||
private bool IsAborted => (_completionState & StreamCompletionFlags.Aborted) == StreamCompletionFlags.Aborted;
|
private bool IsAborted => (_completionState & StreamCompletionFlags.Aborted) == StreamCompletionFlags.Aborted;
|
||||||
|
internal bool DoNotDrainRequest => (_completionState & StreamCompletionFlags.DoNotDrainRequest) == StreamCompletionFlags.DoNotDrainRequest;
|
||||||
|
|
||||||
public override bool IsUpgradableRequest => false;
|
public override bool IsUpgradableRequest => false;
|
||||||
|
|
||||||
|
|
@ -381,6 +382,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
return _context.FrameWriter.TryUpdateStreamWindow(_outputFlowControl, bytes);
|
return _context.FrameWriter.TryUpdateStreamWindow(_outputFlowControl, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DisallowAdditionalRequestFrames()
|
||||||
|
{
|
||||||
|
ApplyCompletionFlag(StreamCompletionFlags.DoNotDrainRequest);
|
||||||
|
}
|
||||||
|
|
||||||
public void Abort(IOException abortReason)
|
public void Abort(IOException abortReason)
|
||||||
{
|
{
|
||||||
var states = ApplyCompletionFlag(StreamCompletionFlags.Aborted);
|
var states = ApplyCompletionFlag(StreamCompletionFlags.Aborted);
|
||||||
|
|
@ -415,6 +421,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
private void ResetAndAbort(ConnectionAbortedException abortReason, Http2ErrorCode error)
|
private void ResetAndAbort(ConnectionAbortedException abortReason, Http2ErrorCode error)
|
||||||
{
|
{
|
||||||
|
// Future incoming frames will drain for a default grace period to avoid destabilizing the connection.
|
||||||
var states = ApplyCompletionFlag(StreamCompletionFlags.Aborted);
|
var states = ApplyCompletionFlag(StreamCompletionFlags.Aborted);
|
||||||
|
|
||||||
if (states.OldState == states.NewState)
|
if (states.OldState == states.NewState)
|
||||||
|
|
@ -507,6 +514,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
RequestProcessingEnded = 1,
|
RequestProcessingEnded = 1,
|
||||||
EndStreamReceived = 2,
|
EndStreamReceived = 2,
|
||||||
Aborted = 4,
|
Aborted = 4,
|
||||||
|
DoNotDrainRequest = 8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2198,6 +2198,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
internal static string FormatConnectionAbortedByClient()
|
internal static string FormatConnectionAbortedByClient()
|
||||||
=> GetString("ConnectionAbortedByClient");
|
=> GetString("ConnectionAbortedByClient");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A frame of type {frameType} was received after stream {streamId} was reset or aborted.
|
||||||
|
/// </summary>
|
||||||
|
internal static string Http2ErrorStreamAborted
|
||||||
|
{
|
||||||
|
get => GetString("Http2ErrorStreamAborted");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A frame of type {frameType} was received after stream {streamId} was reset or aborted.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatHttp2ErrorStreamAborted(object frameType, object streamId)
|
||||||
|
=> string.Format(CultureInfo.CurrentCulture, GetString("Http2ErrorStreamAborted", "frameType", "streamId"), frameType, streamId);
|
||||||
|
|
||||||
private static string GetString(string name, params string[] formatterNames)
|
private static string GetString(string name, params string[] formatterNames)
|
||||||
{
|
{
|
||||||
var value = _resourceManager.GetString(name);
|
var value = _resourceManager.GetString(name);
|
||||||
|
|
|
||||||
|
|
@ -2195,6 +2195,101 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1));
|
expectedErrorMessage: CoreStrings.FormatHttp2ErrorHeadersInterleaved(Http2FrameType.RST_STREAM, streamId: 1, headersStreamId: 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compare to h2spec http2/5.1/8
|
||||||
|
[Fact]
|
||||||
|
public async Task RST_STREAM_IncompleteRequest_AdditionalDataFrames_ConnectionAborted()
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(context => tcs.Task);
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: false);
|
||||||
|
await SendDataAsync(1, new byte[1], endStream: false);
|
||||||
|
await SendDataAsync(1, new byte[2], endStream: false);
|
||||||
|
await SendRstStreamAsync(1);
|
||||||
|
await SendDataAsync(1, new byte[10], endStream: false);
|
||||||
|
tcs.TrySetResult(0);
|
||||||
|
|
||||||
|
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1,
|
||||||
|
Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.DATA, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RST_STREAM_IncompleteRequest_AdditionalTrailerFrames_ConnectionAborted()
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(context => tcs.Task);
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: false);
|
||||||
|
await SendDataAsync(1, new byte[1], endStream: false);
|
||||||
|
await SendDataAsync(1, new byte[2], endStream: false);
|
||||||
|
await SendRstStreamAsync(1);
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, _requestTrailers);
|
||||||
|
tcs.TrySetResult(0);
|
||||||
|
|
||||||
|
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1,
|
||||||
|
Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.HEADERS, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RST_STREAM_IncompleteRequest_AdditionalResetFrame_ConnectionAborted()
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(context => tcs.Task);
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: false);
|
||||||
|
await SendDataAsync(1, new byte[1], endStream: false);
|
||||||
|
await SendRstStreamAsync(1);
|
||||||
|
await SendRstStreamAsync(1);
|
||||||
|
tcs.TrySetResult(0);
|
||||||
|
|
||||||
|
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1,
|
||||||
|
Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.RST_STREAM, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RST_STREAM_IncompleteRequest_AdditionalWindowUpdateFrame_ConnectionAborted()
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "POST"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(context => tcs.Task);
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: false);
|
||||||
|
await SendDataAsync(1, new byte[1], endStream: false);
|
||||||
|
await SendRstStreamAsync(1);
|
||||||
|
await SendWindowUpdateAsync(1, 1024);
|
||||||
|
tcs.TrySetResult(0);
|
||||||
|
|
||||||
|
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1,
|
||||||
|
Http2ErrorCode.STREAM_CLOSED, CoreStrings.FormatHttp2ErrorStreamAborted(Http2FrameType.WINDOW_UPDATE, 1));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SETTINGS_KestrelDefaults_Sent()
|
public async Task SETTINGS_KestrelDefaults_Sent()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests.Http2
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var dataset = new TheoryData<H2SpecTestCase>();
|
var dataset = new TheoryData<H2SpecTestCase>();
|
||||||
var toSkip = new[] { "http2/5.1/8" };
|
var toSkip = new string[] { /*"http2/5.1/8"*/ };
|
||||||
|
|
||||||
foreach (var testcase in H2SpecCommands.EnumerateTestCases())
|
foreach (var testcase in H2SpecCommands.EnumerateTestCases())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue