@@ -79,6 +79,8 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
7979 /// This object deploys heuristics to attempt to detect denial of service attacks.
8080 private var denialOfServiceValidator : DOSHeuristics < RealNIODeadlineClock >
8181
82+ private var glitchesMonitor : GlitchesMonitor
83+
8284 /// The mode this handler is operating in.
8385 private let mode : ParserMode
8486
@@ -236,6 +238,7 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
236238 maximumBufferedControlFrames: 10000 ,
237239 maximumSequentialContinuationFrames: NIOHTTP2Handler . defaultMaximumSequentialContinuationFrames,
238240 maximumRecentlyResetStreams: Self . defaultMaximumRecentlyResetFrames,
241+ maxStreamGlitches: GlitchesMonitor . defaultMaxGlitches,
239242 maximumResetFrameCount: 200 ,
240243 resetFrameCounterWindow: . seconds( 30 ) ,
241244 maximumStreamErrorCount: 200 ,
@@ -273,6 +276,7 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
273276 maximumBufferedControlFrames: maximumBufferedControlFrames,
274277 maximumSequentialContinuationFrames: NIOHTTP2Handler . defaultMaximumSequentialContinuationFrames,
275278 maximumRecentlyResetStreams: Self . defaultMaximumRecentlyResetFrames,
279+ maxStreamGlitches: GlitchesMonitor . defaultMaxGlitches,
276280 maximumResetFrameCount: 200 ,
277281 resetFrameCounterWindow: . seconds( 30 ) ,
278282 maximumStreamErrorCount: 200 ,
@@ -302,6 +306,7 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
302306 maximumBufferedControlFrames: connectionConfiguration. maximumBufferedControlFrames,
303307 maximumSequentialContinuationFrames: connectionConfiguration. maximumSequentialContinuationFrames,
304308 maximumRecentlyResetStreams: connectionConfiguration. maximumRecentlyResetStreams,
309+ maxStreamGlitches: connectionConfiguration. maxStreamGlitches,
305310 maximumResetFrameCount: streamConfiguration. streamResetFrameRateLimit. maximumCount,
306311 resetFrameCounterWindow: streamConfiguration. streamResetFrameRateLimit. windowLength,
307312 maximumStreamErrorCount: streamConfiguration. streamErrorRateLimit. maximumCount,
@@ -319,6 +324,7 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
319324 maximumBufferedControlFrames: Int ,
320325 maximumSequentialContinuationFrames: Int ,
321326 maximumRecentlyResetStreams: Int ,
327+ maxStreamGlitches: UInt ,
322328 maximumResetFrameCount: Int ,
323329 resetFrameCounterWindow: TimeAmount ,
324330 maximumStreamErrorCount: Int ,
@@ -348,6 +354,7 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
348354 self . tolerateImpossibleStateTransitionsInDebugMode = false
349355 self . inboundStreamMultiplexerState = . uninitializedLegacy
350356 self . maximumSequentialContinuationFrames = maximumSequentialContinuationFrames
357+ self . glitchesMonitor = GlitchesMonitor ( maxGlitches: maxStreamGlitches)
351358 }
352359
353360 /// Constructs a ``NIOHTTP2Handler``.
@@ -369,6 +376,7 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
369376 /// against this DoS vector we put an upper limit on this rate. Defaults to 200.
370377 /// - resetFrameCounterWindow: Controls the sliding window used to enforce the maximum permitted reset frames rate. Too many may exhaust CPU resources. To protect
371378 /// against this DoS vector we put an upper limit on this rate. 30 seconds.
379+ /// - maxStreamGlitches: Controls the maximum number of stream errors that can happen on a connection before the connection is reset. Defaults to 200.
372380 internal init (
373381 mode: ParserMode ,
374382 initialSettings: HTTP2Settings = nioDefaultSettings,
@@ -382,7 +390,8 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
382390 maximumResetFrameCount: Int = 200 ,
383391 resetFrameCounterWindow: TimeAmount = . seconds( 30 ) ,
384392 maximumStreamErrorCount: Int = 200 ,
385- streamErrorCounterWindow: TimeAmount = . seconds( 30 )
393+ streamErrorCounterWindow: TimeAmount = . seconds( 30 ) ,
394+ maxStreamGlitches: UInt = GlitchesMonitor . defaultMaxGlitches
386395 ) {
387396 self . stateMachine = HTTP2ConnectionStateMachine (
388397 role: . init( mode) ,
@@ -408,6 +417,7 @@ public final class NIOHTTP2Handler: ChannelDuplexHandler {
408417 self . tolerateImpossibleStateTransitionsInDebugMode = tolerateImpossibleStateTransitionsInDebugMode
409418 self . inboundStreamMultiplexerState = . uninitializedLegacy
410419 self . maximumSequentialContinuationFrames = maximumSequentialContinuationFrames
420+ self . glitchesMonitor = GlitchesMonitor ( maxGlitches: maxStreamGlitches)
411421 }
412422
413423 public func handlerAdded( context: ChannelHandlerContext ) {
@@ -600,32 +610,41 @@ extension NIOHTTP2Handler {
600610 self . inboundConnectionErrorTriggered (
601611 context: context,
602612 underlyingError: NIOHTTP2Errors . unableToParseFrame ( ) ,
603- reason: code
613+ reason: code,
614+ isMisbehavingPeer: false
604615 )
605616 return nil
606617 } catch is NIOHTTP2Errors . BadClientMagic {
607618 self . inboundConnectionErrorTriggered (
608619 context: context,
609620 underlyingError: NIOHTTP2Errors . badClientMagic ( ) ,
610- reason: . protocolError
621+ reason: . protocolError,
622+ isMisbehavingPeer: false
611623 )
612624 return nil
613625 } catch is NIOHTTP2Errors . ExcessivelyLargeHeaderBlock {
614626 self . inboundConnectionErrorTriggered (
615627 context: context,
616628 underlyingError: NIOHTTP2Errors . excessivelyLargeHeaderBlock ( ) ,
617- reason: . protocolError
629+ reason: . protocolError,
630+ isMisbehavingPeer: false
618631 )
619632 return nil
620633 } catch is NIOHTTP2Errors . ExcessiveContinuationFrames {
621634 self . inboundConnectionErrorTriggered (
622635 context: context,
623636 underlyingError: NIOHTTP2Errors . excessiveContinuationFrames ( ) ,
624- reason: . enhanceYourCalm
637+ reason: . enhanceYourCalm,
638+ isMisbehavingPeer: false
625639 )
626640 return nil
627641 } catch {
628- self . inboundConnectionErrorTriggered ( context: context, underlyingError: error, reason: . internalError)
642+ self . inboundConnectionErrorTriggered (
643+ context: context,
644+ underlyingError: error,
645+ reason: . internalError,
646+ isMisbehavingPeer: false
647+ )
629648 return nil
630649 }
631650 }
@@ -733,6 +752,7 @@ extension NIOHTTP2Handler {
733752 }
734753
735754 self . processDoSRisk ( frame, result: & result)
755+ self . processGlitches ( frame, result: & result)
736756 self . processStateChange ( result. effect)
737757
738758 let returnValue : FrameProcessResult
@@ -744,9 +764,14 @@ extension NIOHTTP2Handler {
744764 case . ignoreFrame:
745765 // Frame is good but no action needs to be taken.
746766 returnValue = . continue
747- case . connectionError( let underlyingError, let errorCode) :
767+ case . connectionError( let underlyingError, let errorCode, let isMisbehavingPeer ) :
748768 // We should stop parsing on received connection errors, the connection is going away anyway.
749- self . inboundConnectionErrorTriggered ( context: context, underlyingError: underlyingError, reason: errorCode)
769+ self . inboundConnectionErrorTriggered (
770+ context: context,
771+ underlyingError: underlyingError,
772+ reason: errorCode,
773+ isMisbehavingPeer: isMisbehavingPeer
774+ )
750775 returnValue = . stop
751776 case . streamError( let streamID, let underlyingError, let errorCode) :
752777 // We can continue parsing on stream errors in most cases, the frame is just ignored.
@@ -770,15 +795,20 @@ extension NIOHTTP2Handler {
770795 private func inboundConnectionErrorTriggered(
771796 context: ChannelHandlerContext ,
772797 underlyingError: Error ,
773- reason: HTTP2ErrorCode
798+ reason: HTTP2ErrorCode ,
799+ isMisbehavingPeer: Bool
774800 ) {
775801 // A connection error brings the entire connection down. We attempt to write a GOAWAY frame, and then report this
776802 // error. It's possible that we'll be unable to write the GOAWAY frame, but that also just logs the error.
777803 // Because we don't know what data the user handled before we got this, we propose that they may have seen all of it.
778804 // The user may choose to fire a more specific error if they wish.
805+
806+ // If the peer is misbehaving, set the stream ID to the minimum allowed value (0).
807+ // This will cause all open streams for this connection to be immediately terminated.
808+ let streamID = isMisbehavingPeer ? HTTP2StreamID ( 0 ) : . maxID
779809 let goAwayFrame = HTTP2Frame (
780810 streamID: . rootStream,
781- payload: . goAway( lastStreamID: . maxID , errorCode: reason, opaqueData: nil )
811+ payload: . goAway( lastStreamID: streamID , errorCode: reason, opaqueData: nil )
782812 )
783813 self . writeUnbufferedFrame ( context: context, frame: goAwayFrame)
784814 self . flushIfNecessary ( context: context)
@@ -818,7 +848,32 @@ extension NIOHTTP2Handler {
818848 ( )
819849 }
820850 } catch {
821- result. result = StateMachineResult . connectionError ( underlyingError: error, type: . enhanceYourCalm)
851+ result. result =
852+ StateMachineResult
853+ . connectionError (
854+ underlyingError: error,
855+ type: . enhanceYourCalm,
856+ isMisbehavingPeer: true
857+ )
858+ result. effect = nil
859+ }
860+ }
861+
862+ private func processGlitches( _ frame: HTTP2Frame , result: inout StateMachineResultWithEffect ) {
863+ do {
864+ switch result. result {
865+ case . streamError:
866+ try self . glitchesMonitor. processStreamError ( )
867+ case . succeed, . ignoreFrame, . connectionError:
868+ ( )
869+ }
870+ } catch {
871+ result. result =
872+ . connectionError(
873+ underlyingError: error,
874+ type: . enhanceYourCalm,
875+ isMisbehavingPeer: true
876+ )
822877 result. effect = nil
823878 }
824879 }
@@ -894,7 +949,12 @@ extension NIOHTTP2Handler {
894949 }
895950 } catch let error where error is NIOHTTP2Errors . ExcessiveOutboundFrameBuffering {
896951 self . inboundStreamMultiplexer? . processedFrame ( frame)
897- self . inboundConnectionErrorTriggered ( context: context, underlyingError: error, reason: . enhanceYourCalm)
952+ self . inboundConnectionErrorTriggered (
953+ context: context,
954+ underlyingError: error,
955+ reason: . enhanceYourCalm,
956+ isMisbehavingPeer: false
957+ )
898958 } catch {
899959 self . inboundStreamMultiplexer? . processedFrame ( frame)
900960 promise? . fail ( error)
@@ -968,7 +1028,7 @@ extension NIOHTTP2Handler {
9681028 switch result. result {
9691029 case . ignoreFrame:
9701030 preconditionFailure ( " Cannot be asked to ignore outbound frames. " )
971- case . connectionError( let underlyingError, _) :
1031+ case . connectionError( let underlyingError, _, _ ) :
9721032 self . outboundConnectionErrorTriggered ( context: context, promise: promise, underlyingError: underlyingError)
9731033 return
9741034 case . streamError( let streamID, let underlyingError, _) :
@@ -1330,6 +1390,7 @@ extension NIOHTTP2Handler {
13301390 maximumBufferedControlFrames: connectionConfiguration. maximumBufferedControlFrames,
13311391 maximumSequentialContinuationFrames: connectionConfiguration. maximumSequentialContinuationFrames,
13321392 maximumRecentlyResetStreams: connectionConfiguration. maximumRecentlyResetStreams,
1393+ maxStreamGlitches: connectionConfiguration. maxStreamGlitches,
13331394 maximumResetFrameCount: streamConfiguration. streamResetFrameRateLimit. maximumCount,
13341395 resetFrameCounterWindow: streamConfiguration. streamResetFrameRateLimit. windowLength,
13351396 maximumStreamErrorCount: streamConfiguration. streamErrorRateLimit. maximumCount,
@@ -1362,6 +1423,7 @@ extension NIOHTTP2Handler {
13621423 maximumBufferedControlFrames: connectionConfiguration. maximumBufferedControlFrames,
13631424 maximumSequentialContinuationFrames: connectionConfiguration. maximumSequentialContinuationFrames,
13641425 maximumRecentlyResetStreams: connectionConfiguration. maximumRecentlyResetStreams,
1426+ maxStreamGlitches: connectionConfiguration. maxStreamGlitches,
13651427 maximumResetFrameCount: streamConfiguration. streamResetFrameRateLimit. maximumCount,
13661428 resetFrameCounterWindow: streamConfiguration. streamResetFrameRateLimit. windowLength,
13671429 maximumStreamErrorCount: streamConfiguration. streamErrorRateLimit. maximumCount,
@@ -1386,6 +1448,7 @@ extension NIOHTTP2Handler {
13861448 public var maximumBufferedControlFrames : Int = 10000
13871449 public var maximumSequentialContinuationFrames : Int = NIOHTTP2Handler . defaultMaximumSequentialContinuationFrames
13881450 public var maximumRecentlyResetStreams : Int = NIOHTTP2Handler . defaultMaximumRecentlyResetFrames
1451+ public var maxStreamGlitches : UInt = GlitchesMonitor . defaultMaxGlitches
13891452 public init ( ) { }
13901453 }
13911454
0 commit comments