decred.org/dcrwallet/v3@v3.1.0/internal/rpc/jsonrpc/server.go (about) 1 // Copyright (c) 2013-2015 The btcsuite developers 2 // Copyright (c) 2017-2019 The Decred developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 package jsonrpc 7 8 import ( 9 "context" 10 "crypto/sha256" 11 "crypto/subtle" 12 "encoding/base64" 13 "encoding/json" 14 "io" 15 "net" 16 "net/http" 17 "runtime/trace" 18 "sync" 19 "sync/atomic" 20 "time" 21 22 "decred.org/dcrwallet/v3/errors" 23 "decred.org/dcrwallet/v3/internal/loader" 24 "decred.org/dcrwallet/v3/rpc/jsonrpc/types" 25 "github.com/decred/dcrd/chaincfg/v3" 26 "github.com/decred/dcrd/dcrjson/v4" 27 dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v4" 28 "github.com/gorilla/websocket" 29 ) 30 31 type websocketClient struct { 32 conn *websocket.Conn 33 authenticated bool 34 allRequests chan []byte 35 responses chan []byte 36 cancel func() 37 quit chan struct{} // closed on disconnect 38 wg sync.WaitGroup 39 } 40 41 func newWebsocketClient(c *websocket.Conn, cancel func(), authenticated bool) *websocketClient { 42 return &websocketClient{ 43 conn: c, 44 authenticated: authenticated, 45 allRequests: make(chan []byte), 46 responses: make(chan []byte), 47 cancel: cancel, 48 quit: make(chan struct{}), 49 } 50 } 51 52 func (c *websocketClient) send(b []byte) error { 53 select { 54 case c.responses <- b: 55 return nil 56 case <-c.quit: 57 return errors.New("websocket client disconnected") 58 } 59 } 60 61 // Server holds the items the RPC server may need to access (auth, 62 // config, shutdown, etc.) 63 type Server struct { 64 httpServer http.Server 65 walletLoader *loader.Loader 66 listeners []net.Listener 67 authsha *[sha256.Size]byte // nil when basic auth is disabled 68 upgrader websocket.Upgrader 69 70 cfg Options 71 72 wg sync.WaitGroup 73 quit chan struct{} 74 quitMtx sync.Mutex 75 76 requestShutdownChan chan struct{} 77 78 activeNet *chaincfg.Params 79 } 80 81 type handler struct { 82 fn func(*Server, context.Context, interface{}) (interface{}, error) 83 noHelp bool 84 } 85 86 // jsonAuthFail sends a message back to the client if the http auth is rejected. 87 func jsonAuthFail(w http.ResponseWriter) { 88 w.Header().Add("WWW-Authenticate", `Basic realm="dcrwallet RPC"`) 89 http.Error(w, "401 Unauthorized.", http.StatusUnauthorized) 90 } 91 92 // NewServer creates a new server for serving JSON-RPC client connections, 93 // both HTTP POST and websocket. 94 func NewServer(opts *Options, activeNet *chaincfg.Params, walletLoader *loader.Loader, listeners []net.Listener) *Server { 95 serveMux := http.NewServeMux() 96 const rpcAuthTimeoutSeconds = 10 97 server := &Server{ 98 httpServer: http.Server{ 99 Handler: serveMux, 100 101 // Timeout connections which don't complete the initial 102 // handshake within the allowed timeframe. 103 ReadTimeout: time.Second * rpcAuthTimeoutSeconds, 104 }, 105 walletLoader: walletLoader, 106 cfg: *opts, 107 listeners: listeners, 108 // A hash of the HTTP basic auth string is used for a constant 109 // time comparison. 110 upgrader: websocket.Upgrader{ 111 // Allow all origins. 112 CheckOrigin: func(r *http.Request) bool { return true }, 113 }, 114 quit: make(chan struct{}), 115 requestShutdownChan: make(chan struct{}, 1), 116 activeNet: activeNet, 117 } 118 if opts.Username != "" && opts.Password != "" { 119 h := sha256.Sum256(httpBasicAuth(opts.Username, opts.Password)) 120 server.authsha = &h 121 } 122 123 serveMux.Handle("/", throttledFn(opts.MaxPOSTClients, 124 func(w http.ResponseWriter, r *http.Request) { 125 w.Header().Set("Connection", "close") 126 w.Header().Set("Content-Type", "application/json") 127 r.Close = true 128 129 if err := server.checkAuthHeader(r); err != nil { 130 log.Warnf("Failed authentication attempt from client %s", 131 r.RemoteAddr) 132 jsonAuthFail(w) 133 return 134 } 135 server.wg.Add(1) 136 defer server.wg.Done() 137 server.postClientRPC(w, r) 138 })) 139 140 serveMux.Handle("/ws", throttledFn(opts.MaxWebsocketClients, 141 func(w http.ResponseWriter, r *http.Request) { 142 authenticated := false 143 switch server.checkAuthHeader(r) { 144 case nil: 145 authenticated = true 146 case errNoAuth: 147 // nothing 148 default: 149 // If auth was supplied but incorrect, rather than simply 150 // being missing, immediately terminate the connection. 151 log.Warnf("Failed authentication attempt from client %s", 152 r.RemoteAddr) 153 jsonAuthFail(w) 154 return 155 } 156 157 conn, err := server.upgrader.Upgrade(w, r, nil) 158 if err != nil { 159 log.Warnf("Cannot websocket upgrade client %s: %v", 160 r.RemoteAddr, err) 161 return 162 } 163 ctx := withRemoteAddr(r.Context(), r.RemoteAddr) 164 ctx, cancel := context.WithCancel(ctx) 165 wsc := newWebsocketClient(conn, cancel, authenticated) 166 server.websocketClientRPC(ctx, wsc) 167 })) 168 169 for _, lis := range listeners { 170 server.serve(lis) 171 } 172 173 return server 174 } 175 176 // httpBasicAuth returns the UTF-8 bytes of the HTTP Basic authentication 177 // string: 178 // 179 // "Basic " + base64(username + ":" + password) 180 func httpBasicAuth(username, password string) []byte { 181 const header = "Basic " 182 base64 := base64.StdEncoding 183 184 b64InputLen := len(username) + len(":") + len(password) 185 b64Input := make([]byte, 0, b64InputLen) 186 b64Input = append(b64Input, username...) 187 b64Input = append(b64Input, ':') 188 b64Input = append(b64Input, password...) 189 190 output := make([]byte, len(header)+base64.EncodedLen(b64InputLen)) 191 copy(output, header) 192 base64.Encode(output[len(header):], b64Input) 193 return output 194 } 195 196 // serve serves HTTP POST and websocket RPC for the JSON-RPC RPC server. 197 // This function does not block on lis.Accept. 198 func (s *Server) serve(lis net.Listener) { 199 s.wg.Add(1) 200 go func() { 201 defer s.wg.Done() 202 log.Infof("Listening on %s", lis.Addr()) 203 err := s.httpServer.Serve(lis) 204 log.Tracef("Finished serving RPC: %v", err) 205 }() 206 } 207 208 // Stop gracefully shuts down the rpc server by stopping and disconnecting all 209 // clients. This blocks until shutdown completes. 210 func (s *Server) Stop() { 211 s.quitMtx.Lock() 212 select { 213 case <-s.quit: 214 s.quitMtx.Unlock() 215 return 216 default: 217 } 218 219 // Stop all the listeners. 220 for _, listener := range s.listeners { 221 err := listener.Close() 222 if err != nil { 223 log.Errorf("Cannot close listener `%s`: %v", 224 listener.Addr(), err) 225 } 226 } 227 228 // Signal the remaining goroutines to stop. 229 close(s.quit) 230 s.quitMtx.Unlock() 231 232 // Wait for all remaining goroutines to exit. 233 s.wg.Wait() 234 } 235 236 // handlerClosure creates a closure function for handling requests of the given 237 // method. This may be a request that is handled directly by dcrwallet, or 238 // a chain server request that is handled by passing the request down to dcrd. 239 // 240 // NOTE: These handlers do not handle special cases, such as the authenticate 241 // method. Each of these must be checked beforehand (the method is already 242 // known) and handled accordingly. 243 func (s *Server) handlerClosure(ctx context.Context, request *dcrjson.Request) lazyHandler { 244 log.Debugf("RPC method %q invoked by %v", request.Method, remoteAddr(ctx)) 245 return lazyApplyHandler(s, ctx, request) 246 } 247 248 // errNoAuth represents an error where authentication could not succeed 249 // due to a missing Authorization HTTP header. 250 var errNoAuth = errors.E("missing Authorization header") 251 252 // checkAuthHeader checks any HTTP Basic authentication supplied by a client 253 // in the HTTP request r. 254 // 255 // The authentication comparison is time constant. 256 func (s *Server) checkAuthHeader(r *http.Request) error { 257 if s.authsha == nil { 258 return nil 259 } 260 authhdr := r.Header["Authorization"] 261 if len(authhdr) == 0 { 262 return errNoAuth 263 } 264 265 authsha := sha256.Sum256([]byte(authhdr[0])) 266 cmp := subtle.ConstantTimeCompare(authsha[:], s.authsha[:]) 267 if cmp != 1 { 268 return errors.New("invalid Authorization header") 269 } 270 return nil 271 } 272 273 // throttledFn wraps an http.HandlerFunc with throttling of concurrent active 274 // clients by responding with an HTTP 429 when the threshold is crossed. 275 func throttledFn(threshold int64, f http.HandlerFunc) http.Handler { 276 return throttled(threshold, f) 277 } 278 279 // throttled wraps an http.Handler with throttling of concurrent active 280 // clients by responding with an HTTP 429 when the threshold is crossed. 281 func throttled(threshold int64, h http.Handler) http.Handler { 282 var active int64 283 284 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 285 current := atomic.AddInt64(&active, 1) 286 defer atomic.AddInt64(&active, -1) 287 288 if current-1 >= threshold { 289 log.Warnf("Reached threshold of %d concurrent active clients", threshold) 290 http.Error(w, "429 Too Many Requests", http.StatusTooManyRequests) 291 return 292 } 293 294 h.ServeHTTP(w, r) 295 }) 296 } 297 298 // idPointer returns a pointer to the passed ID, or nil if the interface is nil. 299 // Interface pointers are usually a red flag of doing something incorrectly, 300 // but this is only implemented here to work around an oddity with dcrjson, 301 // which uses empty interface pointers for response IDs. 302 func idPointer(id interface{}) (p *interface{}) { 303 if id != nil { 304 p = &id 305 } 306 return 307 } 308 309 // invalidAuth checks whether a websocket request is a valid (parsable) 310 // authenticate request and checks the supplied username and passphrase 311 // against the server auth. 312 func (s *Server) invalidAuth(req *dcrjson.Request) bool { 313 cmd, err := dcrjson.ParseParams(types.Method(req.Method), req.Params) 314 if err != nil { 315 return false 316 } 317 authCmd, ok := cmd.(*dcrdtypes.AuthenticateCmd) 318 if !ok { 319 return false 320 } 321 // Authenticate commands are invalid when no basic auth is used 322 if s.authsha == nil { 323 return true 324 } 325 // Check credentials. 326 login := authCmd.Username + ":" + authCmd.Passphrase 327 auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login)) 328 authSha := sha256.Sum256([]byte(auth)) 329 return subtle.ConstantTimeCompare(authSha[:], s.authsha[:]) != 1 330 } 331 332 func (s *Server) websocketClientRead(ctx context.Context, wsc *websocketClient) { 333 for { 334 _, request, err := wsc.conn.ReadMessage() 335 if err != nil { 336 if !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) { 337 log.Warnf("Websocket receive failed from client %s: %v", 338 remoteAddr(ctx), err) 339 } 340 close(wsc.allRequests) 341 wsc.cancel() 342 break 343 } 344 wsc.allRequests <- request 345 } 346 } 347 348 func (s *Server) websocketClientRespond(ctx context.Context, wsc *websocketClient) { 349 // A for-select with a read of the quit channel is used instead of a 350 // for-range to provide clean shutdown. This is necessary due to 351 // WebsocketClientRead (which sends to the allRequests chan) not closing 352 // allRequests during shutdown if the remote websocket client is still 353 // connected. 354 out: 355 for { 356 select { 357 case reqBytes, ok := <-wsc.allRequests: 358 if !ok { 359 // client disconnected 360 break out 361 } 362 363 var req dcrjson.Request 364 err := json.Unmarshal(reqBytes, &req) 365 if err != nil { 366 log.Warnf("Failed unmarshal of JSON-RPC request object "+ 367 "from client %s", remoteAddr(ctx)) 368 if !wsc.authenticated { 369 // Disconnect immediately. 370 break out 371 } 372 resp := makeResponse(req.ID, nil, 373 dcrjson.ErrRPCInvalidRequest) 374 mresp, err := json.Marshal(resp) 375 // We expect the marshal to succeed. If it 376 // doesn't, it indicates some non-marshalable 377 // type in the response. 378 if err != nil { 379 panic(err) 380 } 381 err = wsc.send(mresp) 382 if err != nil { 383 break out 384 } 385 continue 386 } 387 388 if req.Method == "authenticate" { 389 log.Debugf("RPC method authenticate invoked by %s", 390 remoteAddr(ctx)) 391 switch { 392 case wsc.authenticated: 393 log.Warnf("Multiple authentication attempts from %s", 394 remoteAddr(ctx)) 395 break out 396 case s.invalidAuth(&req): 397 log.Warnf("Failed authentication attempt from %s", 398 remoteAddr(ctx)) 399 break out 400 } 401 wsc.authenticated = true 402 resp := makeResponse(req.ID, nil, nil) 403 // Expected to never fail. 404 mresp, err := json.Marshal(resp) 405 if err != nil { 406 panic(err) 407 } 408 err = wsc.send(mresp) 409 if err != nil { 410 break out 411 } 412 continue 413 } 414 415 if !wsc.authenticated { 416 // Disconnect immediately. 417 break out 418 } 419 420 switch req.Method { 421 case "stop": 422 log.Debugf("RPC method stop invoked by %s", remoteAddr(ctx)) 423 resp := makeResponse(req.ID, 424 "dcrwallet stopping.", nil) 425 mresp, err := json.Marshal(resp) 426 // Expected to never fail. 427 if err != nil { 428 panic(err) 429 } 430 err = wsc.send(mresp) 431 if err != nil { 432 break out 433 } 434 s.requestProcessShutdown() 435 break out 436 437 default: 438 req := req // Copy for the closure 439 ctx, task := trace.NewTask(ctx, req.Method) 440 f := s.handlerClosure(ctx, &req) 441 wsc.wg.Add(1) 442 go func() { 443 defer task.End() 444 defer wsc.wg.Done() 445 resp, jsonErr := f() 446 mresp, err := dcrjson.MarshalResponse(req.Jsonrpc, req.ID, resp, jsonErr) 447 if err != nil { 448 log.Errorf("Unable to marshal response to client %s: %v", 449 remoteAddr(ctx), err) 450 } else { 451 _ = wsc.send(mresp) 452 } 453 }() 454 } 455 456 case <-s.quit: 457 break out 458 } 459 } 460 461 // allow client to disconnect after all handler goroutines are done 462 wsc.wg.Wait() 463 close(wsc.responses) 464 s.wg.Done() 465 } 466 467 func (s *Server) websocketClientSend(ctx context.Context, wsc *websocketClient) { 468 defer s.wg.Done() 469 const deadline time.Duration = 2 * time.Second 470 out: 471 for { 472 select { 473 case response, ok := <-wsc.responses: 474 if !ok { 475 // client disconnected 476 break out 477 } 478 err := wsc.conn.SetWriteDeadline(time.Now().Add(deadline)) 479 if err != nil { 480 log.Warnf("Cannot set write deadline on "+ 481 "client %s: %v", remoteAddr(ctx), err) 482 } 483 err = wsc.conn.WriteMessage(websocket.TextMessage, 484 response) 485 if err != nil { 486 log.Warnf("Failed websocket send to client "+ 487 "%s: %v", remoteAddr(ctx), err) 488 break out 489 } 490 491 case <-s.quit: 492 break out 493 } 494 } 495 close(wsc.quit) 496 log.Infof("Disconnected websocket client %s", remoteAddr(ctx)) 497 } 498 499 // websocketClientRPC starts the goroutines to serve JSON-RPC requests over a 500 // websocket connection for a single client. 501 func (s *Server) websocketClientRPC(ctx context.Context, wsc *websocketClient) { 502 log.Infof("New websocket client %s", remoteAddr(ctx)) 503 504 // Clear the read deadline set before the websocket hijacked 505 // the connection. 506 if err := wsc.conn.SetReadDeadline(time.Time{}); err != nil { 507 log.Warnf("Cannot remove read deadline: %v", err) 508 } 509 510 // WebsocketClientRead is intentionally not run with the waitgroup 511 // so it is ignored during shutdown. This is to prevent a hang during 512 // shutdown where the goroutine is blocked on a read of the 513 // websocket connection if the client is still connected. 514 go s.websocketClientRead(ctx, wsc) 515 516 s.wg.Add(2) 517 go s.websocketClientRespond(ctx, wsc) 518 go s.websocketClientSend(ctx, wsc) 519 520 <-wsc.quit 521 } 522 523 // maxRequestSize specifies the maximum number of bytes in the request body 524 // that may be read from a client. This is currently limited to 4MB. 525 const maxRequestSize = 1024 * 1024 * 4 526 527 // postClientRPC processes and replies to a JSON-RPC client request. 528 func (s *Server) postClientRPC(w http.ResponseWriter, r *http.Request) { 529 ctx := withRemoteAddr(r.Context(), r.RemoteAddr) 530 531 body := http.MaxBytesReader(w, r.Body, maxRequestSize) 532 rpcRequest, err := io.ReadAll(body) 533 if err != nil { 534 // TODO: what if the underlying reader errored? 535 log.Warnf("Request from client %v exceeds maximum size", r.RemoteAddr) 536 http.Error(w, "413 Request Too Large.", 537 http.StatusRequestEntityTooLarge) 538 return 539 } 540 541 // First check whether wallet has a handler for this request's method. 542 // If unfound, the request is sent to the chain server for further 543 // processing. While checking the methods, disallow authenticate 544 // requests, as they are invalid for HTTP POST clients. 545 var req dcrjson.Request 546 err = json.Unmarshal(rpcRequest, &req) 547 if err != nil { 548 resp, err := dcrjson.MarshalResponse(req.Jsonrpc, req.ID, nil, dcrjson.ErrRPCInvalidRequest) 549 if err != nil { 550 log.Errorf("Unable to marshal response to client %s: %v", 551 r.RemoteAddr, err) 552 http.Error(w, "500 Internal Server Error", 553 http.StatusInternalServerError) 554 return 555 } 556 _, err = w.Write(resp) 557 if err != nil { 558 log.Warnf("Cannot write invalid request request to "+ 559 "client %s: %v", r.RemoteAddr, err) 560 } 561 return 562 } 563 564 ctx, task := trace.NewTask(ctx, req.Method) 565 defer task.End() 566 567 // Create the response and error from the request. Two special cases 568 // are handled for the authenticate and stop request methods. 569 var res interface{} 570 var jsonErr *dcrjson.RPCError 571 var stop bool 572 switch req.Method { 573 case "authenticate": 574 log.Warnf("Invalid RPC method authenticate invoked by HTTP POST client %s", 575 r.RemoteAddr) 576 // Drop it. 577 return 578 case "stop": 579 log.Debugf("RPC method stop invoked by %s", r.RemoteAddr) 580 stop = true 581 res = "dcrwallet stopping" 582 default: 583 res, jsonErr = s.handlerClosure(ctx, &req)() 584 } 585 586 // Marshal and send. 587 mresp, err := dcrjson.MarshalResponse(req.Jsonrpc, req.ID, res, jsonErr) 588 if err != nil { 589 log.Errorf("Unable to marshal response to client %s: %v", 590 r.RemoteAddr, err) 591 http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) 592 return 593 } 594 _, err = w.Write(mresp) 595 if err != nil { 596 log.Warnf("Failed to write response to client %s: %v", 597 r.RemoteAddr, err) 598 } 599 600 if stop { 601 s.requestProcessShutdown() 602 } 603 } 604 605 func (s *Server) requestProcessShutdown() { 606 s.requestShutdownChan <- struct{}{} 607 } 608 609 // RequestProcessShutdown returns a channel that is sent to when an authorized 610 // client requests remote shutdown. 611 func (s *Server) RequestProcessShutdown() <-chan struct{} { 612 return s.requestShutdownChan 613 }