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
defaultMaxPeers = 125
defaultBanDuration = time.Hour * 24
defaultMaxRPCClients = 10
defaultVerifyEnabled = false
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)"`
RPCCert string `long:"rpccert" description:"File containing the certificate file"`
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"`
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"`
@ -278,15 +280,16 @@ func newConfigParser(cfg *config, so *serviceOptions, options flags.Options) *fl
func loadConfig() (*config, []string, error) {
// Default config.
cfg := config{
DebugLevel: defaultLogLevel,
MaxPeers: defaultMaxPeers,
BanDuration: defaultBanDuration,
ConfigFile: defaultConfigFile,
DataDir: defaultDataDir,
LogDir: defaultLogDir,
DbType: defaultDbType,
RPCKey: defaultRPCKeyFile,
RPCCert: defaultRPCCertFile,
ConfigFile: defaultConfigFile,
DebugLevel: defaultLogLevel,
MaxPeers: defaultMaxPeers,
BanDuration: defaultBanDuration,
RPCMaxClients: defaultMaxRPCClients,
DataDir: defaultDataDir,
LogDir: defaultLogDir,
DbType: defaultDbType,
RPCKey: defaultRPCKeyFile,
RPCCert: defaultRPCCertFile,
}
// Service options which are only added on Windows.

1
doc.go
View File

@ -41,6 +41,7 @@ Application Options:
(default port: 8334, testnet: 18334)
--rpccert= File containing the certificate file
--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
disabled by default if no rpcuser/rpcpass is specified
--nodnsseed Disable DNS seeding for peers

View File

@ -140,14 +140,16 @@ var rpcUnimplemented = map[string]bool{
// rpcServer holds the items the rpc server may need to access (config,
// shutdown, main server, etc.)
type rpcServer struct {
started int32
shutdown int32
server *server
authsha [sha256.Size]byte
ws *wsContext
wg sync.WaitGroup
listeners []net.Listener
quit chan int
started int32
shutdown int32
server *server
authsha [sha256.Size]byte
ws *wsContext
numClients int
numClientsMutex sync.Mutex
wg sync.WaitGroup
listeners []net.Listener
quit chan int
}
// Start is used by server.go to start the rpc listener.
@ -167,11 +169,22 @@ func (s *rpcServer) Start() {
}
rpcServeMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
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 {
jsonAuthFail(w, r, s)
return
}
jsonRPCRead(w, r, s)
})
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
// 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
@ -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
// and responding to RPC messages.
func jsonRPCRead(w http.ResponseWriter, r *http.Request, s *rpcServer) {
r.Close = true
if atomic.LoadInt32(&s.shutdown) != 0 {
return
}

View File

@ -143,6 +143,9 @@
; rpclisten=0.0.0.0:8337 ; all ipv4 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
; rpcpass are specified above. This allows one to quickly disable the RPC
; server without having to remove credentials from the config file.