Skip to content

Commit f70f8aa

Browse files
weissiLukasa
authored andcommitted
tolerate multiple RST_STREAMs for same stream (#171)
Motivation: The HTTP/2 RFC does not explicitly forbid sending more than one RST_STREAM for the same stream. Given that we have seen this behaviour in the wild we are going to ignore any additional RST_STREAMs. A further indicative that this is the right call is that the RFC explicitly mentions that sending RST_STREAMs in the idle state is not acceptable. Modifications: Ignore RST_STREAM frames for streams in the closed state. Result: More compatibility.
1 parent 7cc32a9 commit f70f8aa

File tree

4 files changed

+31
-4
lines changed

4 files changed

+31
-4
lines changed

Sources/NIOHTTP2/ConnectionStateMachine/ConnectionStreamsState.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ struct ConnectionStreamState {
168168
/// - parameters:
169169
/// - streamID: The ID of the stream to modify.
170170
/// - ignoreRecentlyReset: Whether a recently reset stream should be ignored. Should be set to `true` when receiving frames.
171-
/// - ignoreClosed: Whether a closed stream should be ignored. Should be set to `true` when receiving window update frames.
171+
/// - ignoreClosed: Whether a closed stream should be ignored. Should be set to `true` when receiving window update or reset stream frames.
172172
/// - modifier: A block that will be invoked to modify the stream state, if present.
173173
/// - returns: The result of the state modification, as well as any state change that occurred to the stream.
174174
@inline(__always)

Sources/NIOHTTP2/ConnectionStateMachine/FrameReceivingStates/ReceivingRstStreamState.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ protocol ReceivingRstStreamState: HasFlowControlWindows {
2323
extension ReceivingRstStreamState {
2424
/// Called to receive a RST_STREAM frame.
2525
mutating func receiveRstStream(streamID: HTTP2StreamID, reason: HTTP2ErrorCode) -> StateMachineResultWithEffect {
26-
let result = self.streamState.modifyStreamState(streamID: streamID, ignoreRecentlyReset: true) {
26+
// RFC 7540 § 6.4 <https://httpwg.org/specs/rfc7540.html#RST_STREAM> does not explicitly forbid a peer sending
27+
// multiple RST_STREAMs for the same stream which means we should ignore subsequent RST_STREAMs.
28+
let result = self.streamState.modifyStreamState(streamID: streamID, ignoreRecentlyReset: true, ignoreClosed: true) {
2729
$0.receiveRstStream(reason: reason)
2830
}
2931
return StateMachineResultWithEffect(result, connectionState: self)

Tests/NIOHTTP2Tests/ConnectionStateMachineTests+XCTest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ extension ConnectionStateMachineTests {
139139
("testNoPolicingMissingContentLengthForResponsesWhenValidationDisabled", testNoPolicingMissingContentLengthForResponsesWhenValidationDisabled),
140140
("testNoPolicingInvalidContentLengthForRequestsWithEndStreamWhenValidationDisabled", testNoPolicingInvalidContentLengthForRequestsWithEndStreamWhenValidationDisabled),
141141
("testNoPolicingInvalidContentLengthForResponsesWithEndStreamWhenValidationDisabled", testNoPolicingInvalidContentLengthForResponsesWithEndStreamWhenValidationDisabled),
142+
("testWeTolerateOneStreamBeingResetTwice", testWeTolerateOneStreamBeingResetTwice),
142143
]
143144
}
144145
}

Tests/NIOHTTP2Tests/ConnectionStateMachineTests.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,8 @@ class ConnectionStateMachineTests: XCTestCase {
455455
var temporaryServer = self.server!
456456
var temporaryClient = self.client!
457457
assertConnectionError(type: .streamClosed, temporaryServer.sendRstStream(streamID: streamFive, reason: .noError))
458-
assertConnectionError(type: .streamClosed, temporaryClient.receiveRstStream(streamID: streamFive, reason: .noError))
458+
// We ignore RST_STREAM in the closed state because the RFC does not explicitly forbid that.
459+
assertIgnored(temporaryClient.receiveRstStream(streamID: streamFive, reason: .noError))
459460

460461
temporaryServer = self.server!
461462
temporaryClient = self.client!
@@ -583,7 +584,8 @@ class ConnectionStateMachineTests: XCTestCase {
583584
temporaryServer = self.server!
584585
temporaryClient = self.client!
585586
assertConnectionError(type: .streamClosed, temporaryClient.sendRstStream(streamID: streamFour, reason: .noError))
586-
assertConnectionError(type: .streamClosed, temporaryServer.receiveRstStream(streamID: streamFour, reason: .noError))
587+
// We ignore RST_STREAM in the closed state because the RFC does not explicitly forbid that.
588+
assertIgnored(temporaryServer.receiveRstStream(streamID: streamFour, reason: .noError))
587589
}
588590

589591
func testServerMayNotInitiateNewStreamAfterClientGoaway() {
@@ -2878,6 +2880,28 @@ class ConnectionStateMachineTests: XCTestCase {
28782880
assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: true))
28792881
assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: true))
28802882
}
2883+
2884+
func testWeTolerateOneStreamBeingResetTwice() {
2885+
self.exchangePreamble()
2886+
2887+
let streamOne = HTTP2StreamID(1)
2888+
// Set up the connection
2889+
assertSucceeds(self.client.sendHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true))
2890+
assertSucceeds(self.server.receiveHeaders(streamID: streamOne, headers: ConnectionStateMachineTests.requestHeaders, isEndStreamSet: true))
2891+
2892+
// The server succeeds
2893+
let responseHeaders = HPACKHeaders([(":status", "200"), ("content-length", "25")])
2894+
assertSucceeds(self.server.sendHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: false))
2895+
assertSucceeds(self.client.receiveHeaders(streamID: streamOne, headers: responseHeaders, isEndStreamSet: false))
2896+
2897+
// Let's test that we cannot emit two RST_STREAMs for the same stream.
2898+
assertSucceeds(self.client.sendRstStream(streamID: streamOne, reason: .cancel))
2899+
assertConnectionError(type: .streamClosed, self.client.sendRstStream(streamID: streamOne, reason: .streamClosed))
2900+
2901+
// But let's also verify that we're happily ignoring this should the other peer do so.
2902+
assertSucceeds(self.server.receiveRstStream(streamID: streamOne, reason: .cancel))
2903+
assertIgnored(self.server.receiveRstStream(streamID: streamOne, reason: .streamClosed))
2904+
}
28812905
}
28822906

28832907

0 commit comments

Comments
 (0)