From 5d7e22a53cdb8c3dd5b6c574ed73d3af8ece820a Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Sat, 20 Jul 2019 16:44:42 +0900 Subject: [PATCH] rpc: make max_body_bytes and max_header_bytes configurable (#3818) * rpc: make max_body_bytes and max_header_bytes configurable * update changelog pending --- CHANGELOG_PENDING.md | 1 + config/config.go | 15 +++++++++++++++ config/toml.go | 6 ++++++ docs/tendermint-core/configuration.md | 6 ++++++ node/node.go | 25 ++++++++++++++----------- rpc/lib/server/handlers.go | 13 ++++++++++++- rpc/lib/server/http_server.go | 24 +++++++++++------------- 7 files changed, 65 insertions(+), 25 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index e62bf1079..066e702f2 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,5 +21,6 @@ program](https://hackerone.com/tendermint). ### IMPROVEMENTS: - [abci] \#3809 Recover from application panics in `server/socket_server.go` to allow socket cleanup (@ruseinov) +- [rpc] \#3818 Make `max_body_bytes` and `max_header_bytes` configurable ### BUG FIXES: diff --git a/config/config.go b/config/config.go index 32e37f3e1..6a3cb8ce8 100644 --- a/config/config.go +++ b/config/config.go @@ -351,6 +351,12 @@ type RPCConfig struct { // See https://github.com/tendermint/tendermint/issues/3435 TimeoutBroadcastTxCommit time.Duration `mapstructure:"timeout_broadcast_tx_commit"` + // Maximum size of request body, in bytes + MaxBodyBytes int64 `mapstructure:"max_body_bytes"` + + // Maximum size of request header, in bytes + MaxHeaderBytes int `mapstructure:"max_header_bytes"` + // The path to a file containing certificate that is used to create the HTTPS server. // Migth be either absolute path or path related to tendermint's config directory. // @@ -385,6 +391,9 @@ func DefaultRPCConfig() *RPCConfig { MaxSubscriptionsPerClient: 5, TimeoutBroadcastTxCommit: 10 * time.Second, + MaxBodyBytes: int64(1000000), // 1MB + MaxHeaderBytes: 1 << 20, // same as the net/http default + TLSCertFile: "", TLSKeyFile: "", } @@ -417,6 +426,12 @@ func (cfg *RPCConfig) ValidateBasic() error { if cfg.TimeoutBroadcastTxCommit < 0 { return errors.New("timeout_broadcast_tx_commit can't be negative") } + if cfg.MaxBodyBytes < 0 { + return errors.New("max_body_bytes can't be negative") + } + if cfg.MaxHeaderBytes < 0 { + return errors.New("max_header_bytes can't be negative") + } return nil } diff --git a/config/toml.go b/config/toml.go index 09117a0fb..1cafc9c2d 100644 --- a/config/toml.go +++ b/config/toml.go @@ -192,6 +192,12 @@ max_subscriptions_per_client = {{ .RPC.MaxSubscriptionsPerClient }} # See https://github.com/tendermint/tendermint/issues/3435 timeout_broadcast_tx_commit = "{{ .RPC.TimeoutBroadcastTxCommit }}" +# Maximum size of request body, in bytes +max_body_bytes = {{ .RPC.MaxBodyBytes }} + +# Maximum size of request header, in bytes +max_header_bytes = {{ .RPC.MaxHeaderBytes }} + # The path to a file containing certificate that is used to create the HTTPS server. # Migth be either absolute path or path related to tendermint's config directory. # If the certificate is signed by a certificate authority, diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index b9f784596..026a75374 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -138,6 +138,12 @@ max_subscriptions_per_client = 5 # See https://github.com/tendermint/tendermint/issues/3435 timeout_broadcast_tx_commit = "10s" +# Maximum size of request body, in bytes +max_body_bytes = {{ .RPC.MaxBodyBytes }} + +# Maximum size of request header, in bytes +max_header_bytes = {{ .RPC.MaxHeaderBytes }} + # The path to a file containing certificate that is used to create the HTTPS server. # Migth be either absolute path or path related to tendermint's config directory. # If the certificate is signed by a certificate authority, diff --git a/node/node.go b/node/node.go index 73c7ed008..9a481e601 100644 --- a/node/node.go +++ b/node/node.go @@ -820,6 +820,17 @@ func (n *Node) startRPC() ([]net.Listener, error) { rpccore.AddUnsafeRoutes() } + config := rpcserver.DefaultConfig() + config.MaxBodyBytes = n.config.RPC.MaxBodyBytes + config.MaxHeaderBytes = n.config.RPC.MaxHeaderBytes + config.MaxOpenConnections = n.config.RPC.MaxOpenConnections + // If necessary adjust global WriteTimeout to ensure it's greater than + // TimeoutBroadcastTxCommit. + // See https://github.com/tendermint/tendermint/issues/3435 + if config.WriteTimeout <= n.config.RPC.TimeoutBroadcastTxCommit { + config.WriteTimeout = n.config.RPC.TimeoutBroadcastTxCommit + 1*time.Second + } + // we may expose the rpc over both a unix and tcp socket listeners := make([]net.Listener, len(listenAddrs)) for i, listenAddr := range listenAddrs { @@ -832,20 +843,12 @@ func (n *Node) startRPC() ([]net.Listener, error) { if err != nil && err != tmpubsub.ErrSubscriptionNotFound { wmLogger.Error("Failed to unsubscribe addr from events", "addr", remoteAddr, "err", err) } - })) + }), + rpcserver.ReadLimit(config.MaxBodyBytes), + ) wm.SetLogger(wmLogger) mux.HandleFunc("/websocket", wm.WebsocketHandler) rpcserver.RegisterRPCFuncs(mux, rpccore.Routes, coreCodec, rpcLogger) - - config := rpcserver.DefaultConfig() - config.MaxOpenConnections = n.config.RPC.MaxOpenConnections - // If necessary adjust global WriteTimeout to ensure it's greater than - // TimeoutBroadcastTxCommit. - // See https://github.com/tendermint/tendermint/issues/3435 - if config.WriteTimeout <= n.config.RPC.TimeoutBroadcastTxCommit { - config.WriteTimeout = n.config.RPC.TimeoutBroadcastTxCommit + 1*time.Second - } - listener, err := rpcserver.Listen( listenAddr, config, diff --git a/rpc/lib/server/handlers.go b/rpc/lib/server/handlers.go index c1c1ebf1a..434ee8916 100644 --- a/rpc/lib/server/handlers.go +++ b/rpc/lib/server/handlers.go @@ -448,6 +448,9 @@ type wsConnection struct { // Send pings to server with this period. Must be less than readWait, but greater than zero. pingPeriod time.Duration + // Maximum message size. + readLimit int64 + // callback which is called upon disconnect onDisconnect func(remoteAddr string) @@ -467,7 +470,6 @@ func NewWSConnection( cdc *amino.Codec, options ...func(*wsConnection), ) *wsConnection { - baseConn.SetReadLimit(maxBodyBytes) wsc := &wsConnection{ remoteAddr: baseConn.RemoteAddr().String(), baseConn: baseConn, @@ -481,6 +483,7 @@ func NewWSConnection( for _, option := range options { option(wsc) } + wsc.baseConn.SetReadLimit(wsc.readLimit) wsc.BaseService = *cmn.NewBaseService(nil, "wsConnection", wsc) return wsc } @@ -525,6 +528,14 @@ func PingPeriod(pingPeriod time.Duration) func(*wsConnection) { } } +// ReadLimit sets the maximum size for reading message. +// It should only be used in the constructor - not Goroutine-safe. +func ReadLimit(readLimit int64) func(*wsConnection) { + return func(wsc *wsConnection) { + wsc.readLimit = readLimit + } +} + // OnStart implements cmn.Service by starting the read and write routines. It // blocks until the connection closes. func (wsc *wsConnection) OnStart() error { diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index 7825605eb..c97739bd2 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -26,6 +26,11 @@ type Config struct { ReadTimeout time.Duration // mirrors http.Server#WriteTimeout WriteTimeout time.Duration + // MaxBodyBytes controls the maximum number of bytes the + // server will read parsing the request body. + MaxBodyBytes int64 + // mirrors http.Server#MaxHeaderBytes + MaxHeaderBytes int } // DefaultConfig returns a default configuration. @@ -34,28 +39,21 @@ func DefaultConfig() *Config { MaxOpenConnections: 0, // unlimited ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, + MaxBodyBytes: int64(1000000), // 1MB + MaxHeaderBytes: 1 << 20, // same as the net/http default } } -const ( - // maxBodyBytes controls the maximum number of bytes the - // server will read parsing the request body. - maxBodyBytes = int64(1000000) // 1MB - - // same as the net/http default - maxHeaderBytes = 1 << 20 -) - // StartHTTPServer takes a listener and starts an HTTP server with the given handler. // It wraps handler with RecoverAndLogHandler. // NOTE: This function blocks - you may want to call it in a go-routine. func StartHTTPServer(listener net.Listener, handler http.Handler, logger log.Logger, config *Config) error { logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s", listener.Addr())) s := &http.Server{ - Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), + Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger), ReadTimeout: config.ReadTimeout, WriteTimeout: config.WriteTimeout, - MaxHeaderBytes: maxHeaderBytes, + MaxHeaderBytes: config.MaxHeaderBytes, } err := s.Serve(listener) logger.Info("RPC HTTP server stopped", "err", err) @@ -75,10 +73,10 @@ func StartHTTPAndTLSServer( logger.Info(fmt.Sprintf("Starting RPC HTTPS server on %s (cert: %q, key: %q)", listener.Addr(), certFile, keyFile)) s := &http.Server{ - Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), + Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: config.MaxBodyBytes}, logger), ReadTimeout: config.ReadTimeout, WriteTimeout: config.WriteTimeout, - MaxHeaderBytes: maxHeaderBytes, + MaxHeaderBytes: config.MaxHeaderBytes, } err := s.ServeTLS(listener, certFile, keyFile)