From e56bd0227d70185b052b20b2338017541a545124 Mon Sep 17 00:00:00 2001 From: Dave Collins Date: Sat, 16 Sep 2023 03:30:11 -0500 Subject: [PATCH] gui: Create listener in GUI constructor. This creates the listener in the GUI constructor so any issues with listening will cause the entire pool to fail to start thereby allowing the admin to resolve any issues immediately. --- dcrpool.go | 2 +- internal/gui/gui.go | 125 ++++++++++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 53 deletions(-) diff --git a/dcrpool.go b/dcrpool.go index c5980c63..64123dd5 100644 --- a/dcrpool.go +++ b/dcrpool.go @@ -109,7 +109,7 @@ func newGUI(cfg *config, hub *pool.Hub) (*gui.GUI, error) { gcfg.HTTPBackupDB = hub.HTTPBackupDB } - return gui.NewGUI(gcfg) + return gui.New(gcfg) } // realMain is the real main function for dcrpool. It is necessary to work diff --git a/internal/gui/gui.go b/internal/gui/gui.go index f422f36e..5c3578ff 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -91,6 +91,7 @@ type Config struct { // GUI represents the mining pool user interface. type GUI struct { cfg *Config + listener net.Listener limiter *pool.RateLimiter templates *template.Template cookieStore *sessions.CookieStore @@ -206,8 +207,71 @@ func loadTemplates(cfg *Config) (*template.Template, error) { return httpTemplates.ParseFiles(templates...) } -// NewGUI creates an instance of the user interface. -func NewGUI(cfg *Config) (*GUI, error) { +// New creates an instance of the user interface. +func New(cfg *Config) (*GUI, error) { + // Create TCP listener based on configuration options. + listenFunc := net.Listen + listenAddr := cfg.GUIListen + switch { + case cfg.UseLEHTTPS: + certMgr := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + Cache: autocert.DirCache("certs"), + HostPolicy: autocert.HostWhitelist(cfg.Domain), + } + + tlsConfig := tls.Config{ + GetCertificate: certMgr.GetCertificate, + MinVersion: tls.VersionTLS12, + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, + } + + // Change the standard net.Listen function to the tls one and the + // address to use the https port. + listenFunc = func(net string, laddr string) (net.Listener, error) { + return tls.Listen(net, laddr, &tlsConfig) + } + listenAddr = ":https" + + case cfg.NoGUITLS: + // Nothing special. + + default: + serverCert, err := tls.LoadX509KeyPair(cfg.TLSCertFile, cfg.TLSKeyFile) + if err != nil { + return nil, err + } + tlsConfig := tls.Config{ + Certificates: []tls.Certificate{serverCert}, + MinVersion: tls.VersionTLS12, + CipherSuites: []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, + } + + // Change the standard net.Listen function to the tls one. + listenFunc = func(net string, laddr string) (net.Listener, error) { + return tls.Listen(net, laddr, &tlsConfig) + } + } + + listener, err := listenFunc("tcp", listenAddr) + if err != nil { + return nil, err + } + templates, err := loadTemplates(cfg) if err != nil { return nil, err @@ -220,6 +284,7 @@ func NewGUI(cfg *Config) (*GUI, error) { ui := GUI{ cfg: cfg, + listener: listener, limiter: pool.NewRateLimiter(), templates: templates, cookieStore: sessions.NewCookieStore(cfg.CSRFSecret), @@ -236,7 +301,6 @@ func NewGUI(cfg *Config) (*GUI, error) { // // It must be run as a routine. func (ui *GUI) runWebServer(ctx context.Context) { - // Create base HTTP/S server configuration. server := http.Server{ // Use the provided context as the parent context for all requests to // ensure handlers are able to react to both client disconnects as well @@ -248,58 +312,15 @@ func (ui *GUI) runWebServer(ctx context.Context) { WriteTimeout: time.Second * 30, ReadTimeout: time.Second * 30, IdleTimeout: time.Second * 30, - Addr: ui.cfg.GUIListen, Handler: ui.router, } - - switch { - case ui.cfg.UseLEHTTPS: - certMgr := &autocert.Manager{ - Prompt: autocert.AcceptTOS, - Cache: autocert.DirCache("certs"), - HostPolicy: autocert.HostWhitelist(ui.cfg.Domain), - } - - server.Addr = ":https" - server.TLSConfig = &tls.Config{ - GetCertificate: certMgr.GetCertificate, - MinVersion: tls.VersionTLS12, - CipherSuites: []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - }, + go func() { + log.Infof("Starting GUI server on %s", ui.listener.Addr()) + err := server.Serve(ui.listener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Error(err) } - - go func() { - log.Info("Starting GUI server on port 443 (https)") - if err := server.ListenAndServeTLS("", ""); err != nil { - log.Error(err) - } - }() - - case ui.cfg.NoGUITLS: - go func() { - log.Infof("Starting GUI server on %s (http)", ui.cfg.GUIListen) - if err := server.ListenAndServe(); err != nil && - !errors.Is(err, http.ErrServerClosed) { - log.Error(err) - } - }() - - default: - go func() { - log.Infof("Starting GUI server on %s (https)", ui.cfg.GUIListen) - if err := server.ListenAndServeTLS(ui.cfg.TLSCertFile, - ui.cfg.TLSKeyFile); err != nil && - !errors.Is(err, http.ErrServerClosed) { - log.Error(err) - } - }() - } + }() // Wait until the context is canceled and gracefully shutdown the server. <-ctx.Done()