Add --rpcmaxclients option with default of 10.

This commit adds a new configuration option, --rpcmaxclients, to limit the
number of max standard RPC clients that are served concurrently.  Note
that this value does not apply to websocket connections.  A future commit
will add support for limiting those separately.

Closes #68.
This commit is contained in:
Dave Collins 2014-02-18 20:44:37 -06:00
parent 66e93f5163
commit a293212581
4 changed files with 80 additions and 18 deletions

View file

@ -33,6 +33,7 @@ const (
defaultBtcnet = btcwire.MainNet defaultBtcnet = btcwire.MainNet
defaultMaxPeers = 125 defaultMaxPeers = 125
defaultBanDuration = time.Hour * 24 defaultBanDuration = time.Hour * 24
defaultMaxRPCClients = 10
defaultVerifyEnabled = false defaultVerifyEnabled = false
defaultDbType = "leveldb" defaultDbType = "leveldb"
) )
@ -71,6 +72,7 @@ type config struct {
RPCListeners []string `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 8334, testnet: 18334)"` RPCListeners []string `long:"rpclisten" description:"Add an interface/port to listen for RPC connections (default port: 8334, testnet: 18334)"`
RPCCert string `long:"rpccert" description:"File containing the certificate file"` RPCCert string `long:"rpccert" description:"File containing the certificate file"`
RPCKey string `long:"rpckey" description:"File containing the certificate key"` RPCKey string `long:"rpckey" description:"File containing the certificate key"`
RPCMaxClients int `long:"rpcmaxclients" description:"Max number of RPC clients for standard connections"`
DisableRPC bool `long:"norpc" description:"Disable built-in RPC server -- NOTE: The RPC server is disabled by default if no rpcuser/rpcpass is specified"` DisableRPC bool `long:"norpc" description:"Disable built-in RPC server -- NOTE: The RPC server is disabled by default if no rpcuser/rpcpass is specified"`
DisableDNSSeed bool `long:"nodnsseed" description:"Disable DNS seeding for peers"` DisableDNSSeed bool `long:"nodnsseed" description:"Disable DNS seeding for peers"`
ExternalIPs []string `long:"externalip" description:"Add an ip to the list of local addresses we claim to listen on to peers"` ExternalIPs []string `long:"externalip" description:"Add an ip to the list of local addresses we claim to listen on to peers"`
@ -278,10 +280,11 @@ func newConfigParser(cfg *config, so *serviceOptions, options flags.Options) *fl
func loadConfig() (*config, []string, error) { func loadConfig() (*config, []string, error) {
// Default config. // Default config.
cfg := config{ cfg := config{
ConfigFile: defaultConfigFile,
DebugLevel: defaultLogLevel, DebugLevel: defaultLogLevel,
MaxPeers: defaultMaxPeers, MaxPeers: defaultMaxPeers,
BanDuration: defaultBanDuration, BanDuration: defaultBanDuration,
ConfigFile: defaultConfigFile, RPCMaxClients: defaultMaxRPCClients,
DataDir: defaultDataDir, DataDir: defaultDataDir,
LogDir: defaultLogDir, LogDir: defaultLogDir,
DbType: defaultDbType, DbType: defaultDbType,

1
doc.go
View file

@ -41,6 +41,7 @@ Application Options:
(default port: 8334, testnet: 18334) (default port: 8334, testnet: 18334)
--rpccert= File containing the certificate file --rpccert= File containing the certificate file
--rpckey= File containing the certificate key --rpckey= File containing the certificate key
--rpcmaxclients= Max number of RPC clients for standard connections (10)
--norpc Disable built-in RPC server -- NOTE: The RPC server is --norpc Disable built-in RPC server -- NOTE: The RPC server is
disabled by default if no rpcuser/rpcpass is specified disabled by default if no rpcuser/rpcpass is specified
--nodnsseed Disable DNS seeding for peers --nodnsseed Disable DNS seeding for peers

View file

@ -145,6 +145,8 @@ type rpcServer struct {
server *server server *server
authsha [sha256.Size]byte authsha [sha256.Size]byte
ws *wsContext ws *wsContext
numClients int
numClientsMutex sync.Mutex
wg sync.WaitGroup wg sync.WaitGroup
listeners []net.Listener listeners []net.Listener
quit chan int quit chan int
@ -167,11 +169,22 @@ func (s *rpcServer) Start() {
} }
rpcServeMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { rpcServeMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Connection", "close") w.Header().Set("Connection", "close")
r.Close = true
// Limit the number of connections to max allowed.
if s.limitConnections(w, r.RemoteAddr) {
return
}
// Keep track of the number of connected clients.
s.incrementClients()
defer s.decrementClients()
if _, err := s.checkAuth(r, true); err != nil { if _, err := s.checkAuth(r, true); err != nil {
jsonAuthFail(w, r, s) jsonAuthFail(w, r, s)
return return
} }
jsonRPCRead(w, r, s) jsonRPCRead(w, r, s)
}) })
rpcServeMux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { rpcServeMux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
@ -199,6 +212,49 @@ func (s *rpcServer) Start() {
} }
} }
// limitConnections responds with a 503 service unavailable and returns true if
// adding another client would exceed the maximum allow RPC clients.
//
// This function is safe for concurrent access.
func (s *rpcServer) limitConnections(w http.ResponseWriter, remoteAddr string) bool {
s.numClientsMutex.Lock()
defer s.numClientsMutex.Unlock()
if s.numClients+1 > cfg.RPCMaxClients {
rpcsLog.Infof("Max RPC clients exceeded [%d] - "+
"disconnecting client %s", cfg.RPCMaxClients,
remoteAddr)
http.Error(w, "503 Too busy. Try again later.",
http.StatusServiceUnavailable)
return true
}
return false
}
// incrementClients adds one to the number of connected RPC clients. Note
// this only applies to standard clients. Websocket clients have their own
// limits and are tracked separately.
//
// This function is safe for concurrent access.
func (s *rpcServer) incrementClients() {
s.numClientsMutex.Lock()
defer s.numClientsMutex.Unlock()
s.numClients++
}
// decrementClients subtracts one from the number of connected RPC clients.
// Note this only applies to standard clients. Websocket clients have their own
// limits and are tracked separately.
//
// This function is safe for concurrent access.
func (s *rpcServer) decrementClients() {
s.numClientsMutex.Lock()
defer s.numClientsMutex.Unlock()
s.numClients--
}
// checkAuth checks the HTTP Basic authentication supplied by a wallet // checkAuth checks the HTTP Basic authentication supplied by a wallet
// or RPC client in the HTTP request r. If the supplied authentication // or RPC client in the HTTP request r. If the supplied authentication
// does not match the username and password expected, a non-nil error is // does not match the username and password expected, a non-nil error is
@ -344,7 +400,6 @@ func jsonAuthFail(w http.ResponseWriter, r *http.Request, s *rpcServer) {
// jsonRPCRead is the RPC wrapper around the jsonRead function to handle reading // jsonRPCRead is the RPC wrapper around the jsonRead function to handle reading
// and responding to RPC messages. // and responding to RPC messages.
func jsonRPCRead(w http.ResponseWriter, r *http.Request, s *rpcServer) { func jsonRPCRead(w http.ResponseWriter, r *http.Request, s *rpcServer) {
r.Close = true
if atomic.LoadInt32(&s.shutdown) != 0 { if atomic.LoadInt32(&s.shutdown) != 0 {
return return
} }

View file

@ -143,6 +143,9 @@
; rpclisten=0.0.0.0:8337 ; all ipv4 interfaces on non-standard port 8337 ; rpclisten=0.0.0.0:8337 ; all ipv4 interfaces on non-standard port 8337
; rpclisten=[::]:8337 ; all ipv6 interfaces on non-standard port 8337 ; rpclisten=[::]:8337 ; all ipv6 interfaces on non-standard port 8337
; Specify the maximum number of concurrent RPC clients for standard connections.
; rpcmaxclients=10
; Use the following setting to disable the RPC server even if the rpcuser and ; Use the following setting to disable the RPC server even if the rpcuser and
; rpcpass are specified above. This allows one to quickly disable the RPC ; rpcpass are specified above. This allows one to quickly disable the RPC
; server without having to remove credentials from the config file. ; server without having to remove credentials from the config file.