From 48b5c15f9a0e49e7abbe25f965a091e62ed2e999 Mon Sep 17 00:00:00 2001 From: Edsko de Vries Date: Thu, 4 Jul 2024 12:54:25 +0200 Subject: [PATCH] Allow overriding `http2` rate limits --- src/Network/GRPC/Client/Connection.hs | 22 +++-- src/Network/GRPC/Common/HTTP2Settings.hs | 106 ++++++++++++++++++----- util/Network/GRPC/Util/HTTP2.hs | 63 ++++++++++---- 3 files changed, 147 insertions(+), 44 deletions(-) diff --git a/src/Network/GRPC/Client/Connection.hs b/src/Network/GRPC/Client/Connection.hs index 1e1aff73..a3f28c81 100644 --- a/src/Network/GRPC/Client/Connection.hs +++ b/src/Network/GRPC/Client/Connection.hs @@ -545,7 +545,7 @@ connectInsecure connParams attempt addr = do } clientConfig :: HTTP2.Client.ClientConfig - clientConfig = overridePingRateLimit connParams $ + clientConfig = overrideRateLimits connParams $ HTTP2.Client.defaultClientConfig { HTTP2.Client.authority = authority addr , HTTP2.Client.settings = settings @@ -588,7 +588,7 @@ connectSecure connParams attempt validation sslKeyLog addr = do } clientConfig :: HTTP2.Client.ClientConfig - clientConfig = overridePingRateLimit connParams $ + clientConfig = overrideRateLimits connParams $ HTTP2.TLS.Client.defaultClientConfig settings (authority addr) @@ -618,16 +618,28 @@ authority addr = Nothing -> addressHost addr Just auth -> auth --- | Override ping rate limit -overridePingRateLimit :: +-- | Override rate limits imposed by @http2@ +overrideRateLimits :: ConnParams -> HTTP2.Client.ClientConfig -> HTTP2.Client.ClientConfig -overridePingRateLimit connParams clientConfig = clientConfig { +overrideRateLimits connParams clientConfig = clientConfig { HTTP2.Client.settings = settings { HTTP2.Client.pingRateLimit = case http2OverridePingRateLimit (connHTTP2Settings connParams) of Nothing -> HTTP2.Client.pingRateLimit settings Just limit -> limit + , HTTP2.Client.emptyFrameRateLimit = + case http2OverrideEmptyFrameRateLimit (connHTTP2Settings connParams) of + Nothing -> HTTP2.Client.emptyFrameRateLimit settings + Just limit -> limit + , HTTP2.Client.settingsRateLimit = + case http2OverrideSettingsRateLimit (connHTTP2Settings connParams) of + Nothing -> HTTP2.Client.settingsRateLimit settings + Just limit -> limit + , HTTP2.Client.rstRateLimit = + case http2OverrideRstRateLimit (connHTTP2Settings connParams) of + Nothing -> HTTP2.Client.rstRateLimit settings + Just limit -> limit } } where diff --git a/src/Network/GRPC/Common/HTTP2Settings.hs b/src/Network/GRPC/Common/HTTP2Settings.hs index 5538ec9d..df4528f1 100644 --- a/src/Network/GRPC/Common/HTTP2Settings.hs +++ b/src/Network/GRPC/Common/HTTP2Settings.hs @@ -39,24 +39,6 @@ data HTTP2Settings = HTTP2Settings { -- information. , http2ConnectionWindowSize :: Word32 - -- | Ping rate limit - -- - -- This setting is specific to the [@http2@ - -- package's](https://hackage.haskell.org/package/http2) implementation of - -- the HTTP\/2 specification. In particular, the library imposes a ping - -- rate limit as a security measure against - -- [CVE-2019-9512](https://www.cve.org/CVERecord?id=CVE-2019-9512). By - -- default (as of version 5.1.2) it sets this limit at 10 pings/second. If - -- you find yourself being disconnected from a gRPC peer because that peer - -- is sending too many pings (you will see an - -- [EnhanceYourCalm](https://hackage.haskell.org/package/http2-5.1.2/docs/Network-HTTP2-Client.html#t:ErrorCode) - -- exception, corresponding to the - -- [ENHANCE_YOUR_CALM](https://www.rfc-editor.org/rfc/rfc9113#ErrorCodes) - -- HTTP\/2 error code), you may wish to increase this limit. If you are - -- connecting to a peer that you trust, you can set this limit to - -- 'maxBound' (effectively turning off protecting against ping flooding). - , http2OverridePingRateLimit :: Maybe Int - -- | Enable @TCP_NODELAY@ -- -- Send out TCP segments as soon as possible, even if there is only a @@ -87,6 +69,81 @@ data HTTP2Settings = HTTP2Settings { -- -- TL;DR: leave this at the default unless you know what you are doing. , http2TcpNoDelay :: Bool + + -- | Ping rate limit + -- + -- This setting is specific to the [@http2@ + -- package's](https://hackage.haskell.org/package/http2) implementation of + -- the HTTP\/2 specification. In particular, the library imposes a ping + -- rate limit as a security measure against + -- [CVE-2019-9512](https://www.cve.org/CVERecord?id=CVE-2019-9512). By + -- default (as of version 5.1.2) it sets this limit at 10 pings/second. If + -- you find yourself being disconnected from a gRPC peer because that peer + -- is sending too many pings (you will see an + -- [EnhanceYourCalm](https://hackage.haskell.org/package/http2-5.1.2/docs/Network-HTTP2-Client.html#t:ErrorCode) + -- exception, corresponding to the + -- [ENHANCE_YOUR_CALM](https://www.rfc-editor.org/rfc/rfc9113#ErrorCodes) + -- HTTP\/2 error code), you may wish to increase this limit. If you are + -- connecting to a peer that you trust, you can set this limit to + -- 'maxBound' (effectively turning off protection against ping flooding). + , http2OverridePingRateLimit :: Maybe Int + + -- | Empty DATA frame rate limit + -- + -- This setting is specific to the [@http2@ + -- package's](https://hackage.haskell.org/package/http2) implementation of + -- the HTTP\/2 specification. In particular, the library imposes a rate + -- limit for empty DATA frames as a security measure against + -- [CVE-2019-9518](https://www.cve.org/CVERecord?id=CVE-2019-9518). By + -- default, it sets this limit at 4 frames/second. If you find yourself + -- being disconnected from a gRPC peer because that peer is sending too + -- many empty DATA frames (you will see an + -- [EnhanceYourCalm](https://hackage.haskell.org/package/http2-5.1.2/docs/Network-HTTP2-Client.html#t:ErrorCode) + -- exception, corresponding to the + -- [ENHANCE_YOUR_CALM](https://www.rfc-editor.org/rfc/rfc9113#ErrorCodes) + -- HTTP\/2 error code), you may wish to increase this limit. If you are + -- connecting to a peer that you trust, you can set this limit to + -- 'maxBound' (effectively turning off protection against empty DATA frame + -- flooding). + , http2OverrideEmptyFrameRateLimit :: Maybe Int + + -- | SETTINGS frame rate limit + -- + -- This setting is specific to the [@http2@ + -- package's](https://hackage.haskell.org/package/http2) implementation of + -- the HTTP\/2 specification. In particular, the library imposes a rate + -- limit for SETTINGS frames as a security measure against + -- [CVE-2019-9515](https://www.cve.org/CVERecord?id=CVE-2019-9515). By + -- default, it sets this limit at 4 frames/second. If you find yourself + -- being disconnected from a gRPC peer because that peer is sending too + -- many SETTINGS frames (you will see an + -- [EnhanceYourCalm](https://hackage.haskell.org/package/http2-5.1.2/docs/Network-HTTP2-Client.html#t:ErrorCode) + -- exception, corresponding to the + -- [ENHANCE_YOUR_CALM](https://www.rfc-editor.org/rfc/rfc9113#ErrorCodes) + -- HTTP\/2 error code), you may wish to increase this limit. If you are + -- connecting to a peer that you trust, you can set this limit to + -- 'maxBound' (effectively turning off protection against SETTINGS frame + -- flooding). + , http2OverrideSettingsRateLimit :: Maybe Int + + -- | Reset (RST) frame rate limit + -- + -- This setting is specific to the [@http2@ + -- package's](https://hackage.haskell.org/package/http2) implementation of + -- the HTTP\/2 specification. In particular, the library imposes a rate + -- limit for RST frames as a security measure against + -- [CVE-2023-44487](https://www.cve.org/CVERecord?id=CVE-2023-44487). By + -- default, it sets this limit at 4 frames/second. If you find yourself + -- being disconnected from a gRPC peer because that peer is sending too + -- many empty RST frames (you will see an + -- [EnhanceYourCalm](https://hackage.haskell.org/package/http2-5.1.2/docs/Network-HTTP2-Client.html#t:ErrorCode) + -- exception, corresponding to the + -- [ENHANCE_YOUR_CALM](https://www.rfc-editor.org/rfc/rfc9113#ErrorCodes) + -- HTTP\/2 error code), you may wish to increase this limit. If you are + -- connecting to a peer that you trust, you can set this limit to + -- 'maxBound' (effectively turning off protection against RST frame + -- flooding). + , http2OverrideRstRateLimit :: Maybe Int } deriving (Show) @@ -109,11 +166,14 @@ data HTTP2Settings = HTTP2Settings { -- PINGs/sec. defaultHTTP2Settings :: HTTP2Settings defaultHTTP2Settings = HTTP2Settings { - http2MaxConcurrentStreams = defMaxConcurrentStreams - , http2StreamWindowSize = defInitialStreamWindowSize - , http2ConnectionWindowSize = defMaxConcurrentStreams * defInitialStreamWindowSize - , http2OverridePingRateLimit = Just 100 - , http2TcpNoDelay = True + http2MaxConcurrentStreams = defMaxConcurrentStreams + , http2StreamWindowSize = defInitialStreamWindowSize + , http2ConnectionWindowSize = defMaxConcurrentStreams * defInitialStreamWindowSize + , http2TcpNoDelay = True + , http2OverridePingRateLimit = Just 100 + , http2OverrideEmptyFrameRateLimit = Nothing + , http2OverrideSettingsRateLimit = Nothing + , http2OverrideRstRateLimit = Nothing } where defMaxConcurrentStreams = 128 diff --git a/util/Network/GRPC/Util/HTTP2.hs b/util/Network/GRPC/Util/HTTP2.hs index e3356d35..3e0e2492 100644 --- a/util/Network/GRPC/Util/HTTP2.hs +++ b/util/Network/GRPC/Util/HTTP2.hs @@ -147,16 +147,31 @@ mkServerConfig http2Settings numberOfWorkers = (fromIntegral <$> numberOfWorkers) , Server.connectionWindowSize = fromIntegral $ http2ConnectionWindowSize http2Settings - , Server.settings = Server.defaultSettings { - Server.initialWindowSize = fromIntegral $ - http2StreamWindowSize http2Settings - , Server.maxConcurrentStreams = Just . fromIntegral $ - http2MaxConcurrentStreams http2Settings --- , Server.pingRateLimit = --- fromMaybe --- (Server.pingRateLimit Server.defaultSettings) --- (http2OverridePingRateLimit http2Settings) - } + , Server.settings = + Server.defaultSettings { + Server.initialWindowSize = + fromIntegral $ + http2StreamWindowSize http2Settings + , Server.maxConcurrentStreams = + Just . fromIntegral $ + http2MaxConcurrentStreams http2Settings + , Server.pingRateLimit = + case http2OverridePingRateLimit http2Settings of + Nothing -> Server.pingRateLimit Server.defaultSettings + Just limit -> limit + , Server.emptyFrameRateLimit = + case http2OverrideEmptyFrameRateLimit http2Settings of + Nothing -> Server.emptyFrameRateLimit Server.defaultSettings + Just limit -> limit + , Server.settingsRateLimit = + case http2OverrideSettingsRateLimit http2Settings of + Nothing -> Server.settingsRateLimit Server.defaultSettings + Just limit -> limit + , Server.rstRateLimit = + case http2OverrideRstRateLimit http2Settings of + Nothing -> Server.rstRateLimit Server.defaultSettings + Just limit -> limit + } } -- | Settings for secure server (with TLS) @@ -180,12 +195,28 @@ mkTlsSettings http2Settings numberOfWorkers keyLogger = fromMaybe (Server.TLS.settingsNumberOfWorkers Server.TLS.defaultSettings) (fromIntegral <$> numberOfWorkers) - , Server.TLS.settingsConnectionWindowSize = fromIntegral $ - http2ConnectionWindowSize http2Settings - , Server.TLS.settingsStreamWindowSize = fromIntegral $ - http2StreamWindowSize http2Settings - , Server.TLS.settingsConcurrentStreams = fromIntegral $ - http2MaxConcurrentStreams http2Settings + , Server.TLS.settingsConnectionWindowSize = + fromIntegral $ http2ConnectionWindowSize http2Settings + , Server.TLS.settingsStreamWindowSize = + fromIntegral $ http2StreamWindowSize http2Settings + , Server.TLS.settingsConcurrentStreams = + fromIntegral $ http2MaxConcurrentStreams http2Settings + , Server.TLS.settingsPingRateLimit = + case http2OverridePingRateLimit http2Settings of + Nothing -> Server.pingRateLimit Server.defaultSettings + Just limit -> limit + , Server.TLS.settingsEmptyFrameRateLimit = + case http2OverrideEmptyFrameRateLimit http2Settings of + Nothing -> Server.emptyFrameRateLimit Server.defaultSettings + Just limit -> limit + , Server.TLS.settingsSettingsRateLimit = + case http2OverrideSettingsRateLimit http2Settings of + Nothing -> Server.settingsRateLimit Server.defaultSettings + Just limit -> limit + , Server.TLS.settingsRstRateLimit = + case http2OverrideRstRateLimit http2Settings of + Nothing -> Server.rstRateLimit Server.defaultSettings + Just limit -> limit } {-------------------------------------------------------------------------------