github.com/core-coin/go-core/v2@v2.1.9/node/rpcstack.go (about) 1 // Copyright 2020 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package node 18 19 import ( 20 "compress/gzip" 21 "context" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "net" 26 "net/http" 27 "sort" 28 "strings" 29 "sync" 30 "sync/atomic" 31 32 "github.com/rs/cors" 33 34 "github.com/core-coin/go-core/v2/log" 35 "github.com/core-coin/go-core/v2/rpc" 36 ) 37 38 // httpConfig is the JSON-RPC/HTTP configuration. 39 type httpConfig struct { 40 Modules []string 41 CorsAllowedOrigins []string 42 Vhosts []string 43 prefix string // path prefix on which to mount http handler 44 jwtSecret []byte // optional JWT secret 45 } 46 47 // wsConfig is the JSON-RPC/Websocket configuration 48 type wsConfig struct { 49 Origins []string 50 Modules []string 51 prefix string // path prefix on which to mount ws handler 52 jwtSecret []byte // optional JWT secret 53 } 54 55 type rpcHandler struct { 56 http.Handler 57 server *rpc.Server 58 } 59 60 type httpServer struct { 61 log log.Logger 62 timeouts rpc.HTTPTimeouts 63 mux http.ServeMux // registered handlers go here 64 65 mu sync.Mutex 66 server *http.Server 67 listener net.Listener // non-nil when server is running 68 69 // HTTP RPC handler things. 70 httpConfig httpConfig 71 httpHandler atomic.Value // *rpcHandler 72 73 // WebSocket handler things. 74 wsConfig wsConfig 75 wsHandler atomic.Value // *rpcHandler 76 77 // These are set by setListenAddr. 78 endpoint string 79 host string 80 port int 81 82 handlerNames map[string]string 83 } 84 85 func newHTTPServer(log log.Logger, timeouts rpc.HTTPTimeouts) *httpServer { 86 h := &httpServer{log: log, timeouts: timeouts, handlerNames: make(map[string]string)} 87 h.httpHandler.Store((*rpcHandler)(nil)) 88 h.wsHandler.Store((*rpcHandler)(nil)) 89 return h 90 } 91 92 // setListenAddr configures the listening address of the server. 93 // The address can only be set while the server isn't running. 94 func (h *httpServer) setListenAddr(host string, port int) error { 95 h.mu.Lock() 96 defer h.mu.Unlock() 97 98 if h.listener != nil && (host != h.host || port != h.port) { 99 return fmt.Errorf("HTTP server already running on %s", h.endpoint) 100 } 101 102 h.host, h.port = host, port 103 h.endpoint = fmt.Sprintf("%s:%d", host, port) 104 return nil 105 } 106 107 // listenAddr returns the listening address of the server. 108 func (h *httpServer) listenAddr() string { 109 h.mu.Lock() 110 defer h.mu.Unlock() 111 112 if h.listener != nil { 113 return h.listener.Addr().String() 114 } 115 return h.endpoint 116 } 117 118 // start starts the HTTP server if it is enabled and not already running. 119 func (h *httpServer) start() error { 120 h.mu.Lock() 121 defer h.mu.Unlock() 122 123 if h.endpoint == "" || h.listener != nil { 124 return nil // already running or not configured 125 } 126 127 // Initialize the server. 128 h.server = &http.Server{Handler: h} 129 if h.timeouts != (rpc.HTTPTimeouts{}) { 130 CheckTimeouts(&h.timeouts) 131 h.server.ReadTimeout = h.timeouts.ReadTimeout 132 h.server.WriteTimeout = h.timeouts.WriteTimeout 133 h.server.IdleTimeout = h.timeouts.IdleTimeout 134 } 135 136 // Start the server. 137 listener, err := net.Listen("tcp", h.endpoint) 138 if err != nil { 139 // If the server fails to start, we need to clear out the RPC and WS 140 // configuration so they can be configured another time. 141 h.disableRPC() 142 h.disableWS() 143 return err 144 } 145 h.listener = listener 146 go h.server.Serve(listener) 147 148 // if server is websocket only, return after logging 149 if h.wsAllowed() && !h.rpcAllowed() { 150 url := fmt.Sprintf("ws://%v", listener.Addr()) 151 if h.wsConfig.prefix != "" { 152 url += h.wsConfig.prefix 153 } 154 h.log.Info("WebSocket enabled", "url", url) 155 return nil 156 } 157 // Log http endpoint. 158 h.log.Info("HTTP server started", 159 "endpoint", listener.Addr(), "auth", (h.httpConfig.jwtSecret != nil), 160 "cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","), 161 "prefix", h.httpConfig.prefix, 162 "vhosts", strings.Join(h.httpConfig.Vhosts, ","), 163 ) 164 165 // Log all handlers mounted on server. 166 var paths []string 167 for path := range h.handlerNames { 168 paths = append(paths, path) 169 } 170 sort.Strings(paths) 171 logged := make(map[string]bool, len(paths)) 172 for _, path := range paths { 173 name := h.handlerNames[path] 174 if !logged[name] { 175 log.Info(name+" enabled", "url", "http://"+listener.Addr().String()+path) 176 logged[name] = true 177 } 178 } 179 return nil 180 } 181 182 func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 183 // check if ws request and serve if ws enabled 184 ws := h.wsHandler.Load().(*rpcHandler) 185 if ws != nil && isWebsocket(r) { 186 if checkPath(r, h.wsConfig.prefix) { 187 ws.ServeHTTP(w, r) 188 } 189 return 190 } 191 // if http-rpc is enabled, try to serve request 192 rpc := h.httpHandler.Load().(*rpcHandler) 193 if rpc != nil { 194 // First try to route in the mux. 195 // Requests to a path below root are handled by the mux, 196 // which has all the handlers registered via Node.RegisterHandler. 197 // These are made available when RPC is enabled. 198 muxHandler, pattern := h.mux.Handler(r) 199 if pattern != "" { 200 muxHandler.ServeHTTP(w, r) 201 return 202 } 203 204 if checkPath(r, h.httpConfig.prefix) { 205 rpc.ServeHTTP(w, r) 206 return 207 } 208 } 209 w.WriteHeader(http.StatusNotFound) 210 } 211 212 // checkPath checks whether a given request URL matches a given path prefix. 213 func checkPath(r *http.Request, path string) bool { 214 // if no prefix has been specified, request URL must be on root 215 if path == "" { 216 return r.URL.Path == "/" 217 } 218 // otherwise, check to make sure prefix matches 219 return len(r.URL.Path) >= len(path) && r.URL.Path[:len(path)] == path 220 } 221 222 // validatePrefix checks if 'path' is a valid configuration value for the RPC prefix option. 223 func validatePrefix(what, path string) error { 224 if path == "" { 225 return nil 226 } 227 if path[0] != '/' { 228 return fmt.Errorf(`%s RPC path prefix %q does not contain leading "/"`, what, path) 229 } 230 if strings.ContainsAny(path, "?#") { 231 // This is just to avoid confusion. While these would match correctly (i.e. they'd 232 // match if URL-escaped into path), it's not easy to understand for users when 233 // setting that on the command line. 234 return fmt.Errorf("%s RPC path prefix %q contains URL meta-characters", what, path) 235 } 236 return nil 237 } 238 239 // stop shuts down the HTTP server. 240 func (h *httpServer) stop() { 241 h.mu.Lock() 242 defer h.mu.Unlock() 243 h.doStop() 244 } 245 246 func (h *httpServer) doStop() { 247 if h.listener == nil { 248 return // not running 249 } 250 251 // Shut down the server. 252 httpHandler := h.httpHandler.Load().(*rpcHandler) 253 wsHandler := h.httpHandler.Load().(*rpcHandler) 254 if httpHandler != nil { 255 h.httpHandler.Store((*rpcHandler)(nil)) 256 httpHandler.server.Stop() 257 } 258 if wsHandler != nil { 259 h.wsHandler.Store((*rpcHandler)(nil)) 260 wsHandler.server.Stop() 261 } 262 h.server.Shutdown(context.Background()) 263 h.listener.Close() 264 h.log.Info("HTTP server stopped", "endpoint", h.listener.Addr()) 265 266 // Clear out everything to allow re-configuring it later. 267 h.host, h.port, h.endpoint = "", 0, "" 268 h.server, h.listener = nil, nil 269 } 270 271 // enableRPC turns on JSON-RPC over HTTP on the server. 272 func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { 273 h.mu.Lock() 274 defer h.mu.Unlock() 275 276 if h.rpcAllowed() { 277 return fmt.Errorf("JSON-RPC over HTTP is already enabled") 278 } 279 280 // Create RPC server and handler. 281 srv := rpc.NewServer() 282 if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil { 283 return err 284 } 285 h.httpConfig = config 286 h.httpHandler.Store(&rpcHandler{ 287 Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts, config.jwtSecret), 288 server: srv, 289 }) 290 return nil 291 } 292 293 // disableRPC stops the HTTP RPC handler. This is internal, the caller must hold h.mu. 294 func (h *httpServer) disableRPC() bool { 295 handler := h.httpHandler.Load().(*rpcHandler) 296 if handler != nil { 297 h.httpHandler.Store((*rpcHandler)(nil)) 298 handler.server.Stop() 299 } 300 return handler != nil 301 } 302 303 // enableWS turns on JSON-RPC over WebSocket on the server. 304 func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { 305 h.mu.Lock() 306 defer h.mu.Unlock() 307 308 if h.wsAllowed() { 309 return fmt.Errorf("JSON-RPC over WebSocket is already enabled") 310 } 311 312 // Create RPC server and handler. 313 srv := rpc.NewServer() 314 if err := RegisterApisFromWhitelist(apis, config.Modules, srv, false); err != nil { 315 return err 316 } 317 h.wsConfig = config 318 h.wsHandler.Store(&rpcHandler{ 319 Handler: NewWSHandlerStack(srv.WebsocketHandler(config.Origins), config.jwtSecret), 320 server: srv, 321 }) 322 return nil 323 } 324 325 // stopWS disables JSON-RPC over WebSocket and also stops the server if it only serves WebSocket. 326 func (h *httpServer) stopWS() { 327 h.mu.Lock() 328 defer h.mu.Unlock() 329 330 if h.disableWS() { 331 if !h.rpcAllowed() { 332 h.doStop() 333 } 334 } 335 } 336 337 // disableWS disables the WebSocket handler. This is internal, the caller must hold h.mu. 338 func (h *httpServer) disableWS() bool { 339 ws := h.wsHandler.Load().(*rpcHandler) 340 if ws != nil { 341 h.wsHandler.Store((*rpcHandler)(nil)) 342 ws.server.Stop() 343 } 344 return ws != nil 345 } 346 347 // rpcAllowed returns true when JSON-RPC over HTTP is enabled. 348 func (h *httpServer) rpcAllowed() bool { 349 return h.httpHandler.Load().(*rpcHandler) != nil 350 } 351 352 // wsAllowed returns true when JSON-RPC over WebSocket is enabled. 353 func (h *httpServer) wsAllowed() bool { 354 return h.wsHandler.Load().(*rpcHandler) != nil 355 } 356 357 // isWebsocket checks the header of an http request for a websocket upgrade request. 358 func isWebsocket(r *http.Request) bool { 359 return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && 360 strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade") 361 } 362 363 // NewHTTPHandlerStack returns wrapped http-related handlers 364 func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string, jwtSecret []byte) http.Handler { 365 // Wrap the CORS-handler within a host-handler 366 handler := newCorsHandler(srv, cors) 367 handler = newVHostHandler(vhosts, handler) 368 if len(jwtSecret) != 0 { 369 handler = newJWTHandler(jwtSecret, handler) 370 } 371 return newGzipHandler(handler) 372 } 373 374 // NewWSHandlerStack returns a wrapped ws-related handler. 375 func NewWSHandlerStack(srv http.Handler, jwtSecret []byte) http.Handler { 376 if len(jwtSecret) != 0 { 377 return newJWTHandler(jwtSecret, srv) 378 } 379 return srv 380 } 381 382 func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { 383 // disable CORS support if user has not specified a custom CORS configuration 384 if len(allowedOrigins) == 0 { 385 return srv 386 } 387 c := cors.New(cors.Options{ 388 AllowedOrigins: allowedOrigins, 389 AllowedMethods: []string{http.MethodPost, http.MethodGet}, 390 AllowedHeaders: []string{"*"}, 391 MaxAge: 600, 392 }) 393 return c.Handler(srv) 394 } 395 396 // virtualHostHandler is a handler which validates the Host-header of incoming requests. 397 // Using virtual hosts can help prevent DNS rebinding attacks, where a 'random' domain name points to 398 // the service ip address (but without CORS headers). By verifying the targeted virtual host, we can 399 // ensure that it's a destination that the node operator has defined. 400 type virtualHostHandler struct { 401 vhosts map[string]struct{} 402 next http.Handler 403 } 404 405 func newVHostHandler(vhosts []string, next http.Handler) http.Handler { 406 vhostMap := make(map[string]struct{}) 407 for _, allowedHost := range vhosts { 408 vhostMap[strings.ToLower(allowedHost)] = struct{}{} 409 } 410 return &virtualHostHandler{vhostMap, next} 411 } 412 413 // ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler 414 func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 415 // if r.Host is not set, we can continue serving since a browser would set the Host header 416 if r.Host == "" { 417 h.next.ServeHTTP(w, r) 418 return 419 } 420 host, _, err := net.SplitHostPort(r.Host) 421 if err != nil { 422 // Either invalid (too many colons) or no port specified 423 host = r.Host 424 } 425 if ipAddr := net.ParseIP(host); ipAddr != nil { 426 // It's an IP address, we can serve that 427 h.next.ServeHTTP(w, r) 428 return 429 430 } 431 // Not an IP address, but a hostname. Need to validate 432 if _, exist := h.vhosts["*"]; exist { 433 h.next.ServeHTTP(w, r) 434 return 435 } 436 if _, exist := h.vhosts[host]; exist { 437 h.next.ServeHTTP(w, r) 438 return 439 } 440 http.Error(w, "invalid host specified", http.StatusForbidden) 441 } 442 443 var gzPool = sync.Pool{ 444 New: func() interface{} { 445 w := gzip.NewWriter(ioutil.Discard) 446 return w 447 }, 448 } 449 450 type gzipResponseWriter struct { 451 io.Writer 452 http.ResponseWriter 453 } 454 455 func (w *gzipResponseWriter) WriteHeader(status int) { 456 w.Header().Del("Content-Length") 457 w.ResponseWriter.WriteHeader(status) 458 } 459 460 func (w *gzipResponseWriter) Write(b []byte) (int, error) { 461 return w.Writer.Write(b) 462 } 463 464 func newGzipHandler(next http.Handler) http.Handler { 465 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 466 if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { 467 next.ServeHTTP(w, r) 468 return 469 } 470 471 w.Header().Set("Content-Encoding", "gzip") 472 473 gz := gzPool.Get().(*gzip.Writer) 474 defer gzPool.Put(gz) 475 476 gz.Reset(w) 477 defer gz.Close() 478 479 next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) 480 }) 481 } 482 483 type ipcServer struct { 484 log log.Logger 485 endpoint string 486 487 mu sync.Mutex 488 listener net.Listener 489 srv *rpc.Server 490 } 491 492 func newIPCServer(log log.Logger, endpoint string) *ipcServer { 493 return &ipcServer{log: log, endpoint: endpoint} 494 } 495 496 // Start starts the httpServer's http.Server 497 func (is *ipcServer) start(apis []rpc.API) error { 498 is.mu.Lock() 499 defer is.mu.Unlock() 500 501 if is.listener != nil { 502 return nil // already running 503 } 504 listener, srv, err := rpc.StartIPCEndpoint(is.endpoint, apis) 505 if err != nil { 506 is.log.Warn("IPC opening failed", "url", is.endpoint, "error", err) 507 return err 508 } 509 is.log.Info("IPC endpoint opened", "url", is.endpoint) 510 is.listener, is.srv = listener, srv 511 return nil 512 } 513 514 func (is *ipcServer) stop() error { 515 is.mu.Lock() 516 defer is.mu.Unlock() 517 518 if is.listener == nil { 519 return nil // not running 520 } 521 err := is.listener.Close() 522 is.srv.Stop() 523 is.listener, is.srv = nil, nil 524 is.log.Info("IPC endpoint closed", "url", is.endpoint) 525 return err 526 } 527 528 // RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, 529 // and then registers all of the APIs exposed by the services. 530 func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { 531 if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { 532 log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) 533 } 534 // Generate the whitelist based on the allowed modules 535 whitelist := make(map[string]bool) 536 for _, module := range modules { 537 whitelist[module] = true 538 } 539 // Register all the APIs exposed by the services 540 for _, api := range apis { 541 if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { 542 if err := srv.RegisterName(api.Namespace, api.Service); err != nil { 543 return err 544 } 545 } 546 } 547 return nil 548 }