diff --git a/internal/home/config.go b/internal/home/config.go index 65da7ed1..098ea0a9 100644 --- a/internal/home/config.go +++ b/internal/home/config.go @@ -640,7 +640,7 @@ func readConfigFile() (fileData []byte, err error) { } // Saves configuration to the YAML file and also saves the user filter contents to a file -func (c *configuration) write() (err error) { +func (c *configuration) write(tlsMgr *tlsManager) (err error) { c.Lock() defer c.Unlock() @@ -648,9 +648,9 @@ func (c *configuration) write() (err error) { config.Users = globalContext.auth.usersList() } - if globalContext.tls != nil { + if tlsMgr != nil { tlsConf := tlsConfigSettings{} - globalContext.tls.WriteDiskConfig(&tlsConf) + tlsMgr.WriteDiskConfig(&tlsConf) config.TLS = tlsConf } diff --git a/internal/home/control.go b/internal/home/control.go index f15ee463..4be955f3 100644 --- a/internal/home/control.go +++ b/internal/home/control.go @@ -68,7 +68,8 @@ func appendDNSAddrsWithIfaces(dst []string, src []netip.Addr) (res []string, err // collectDNSAddresses returns the list of DNS addresses the server is listening // on, including the addresses on all interfaces in cases of unspecified IPs. -func collectDNSAddresses() (addrs []string, err error) { +// tlsMgr must not be nil. +func collectDNSAddresses(tlsMgr *tlsManager) (addrs []string, err error) { if hosts := config.DNS.BindHosts; len(hosts) == 0 { addrs = appendDNSAddrs(addrs, netutil.IPv4Localhost()) } else { @@ -78,7 +79,7 @@ func collectDNSAddresses() (addrs []string, err error) { } } - de := getDNSEncryption() + de := getDNSEncryption(tlsMgr) if de.https != "" { addrs = append(addrs, de.https) } @@ -113,8 +114,8 @@ type statusResponse struct { IsRunning bool `json:"running"` } -func handleStatus(w http.ResponseWriter, r *http.Request) { - dnsAddrs, err := collectDNSAddresses() +func (web *webAPI) handleStatus(w http.ResponseWriter, r *http.Request) { + dnsAddrs, err := collectDNSAddresses(web.tlsManager) if err != nil { // Don't add a lot of formatting, since the error is already // wrapped by collectDNSAddresses. @@ -167,9 +168,8 @@ func handleStatus(w http.ResponseWriter, r *http.Request) { aghhttp.WriteJSONResponseOK(w, r, resp) } -// ------------------------ -// registration of handlers -// ------------------------ +// registerControlHandlers sets up HTTP handlers for various control endpoints. +// web must not be nil. func registerControlHandlers(web *webAPI) { globalContext.mux.HandleFunc( "/control/version.json", @@ -177,7 +177,7 @@ func registerControlHandlers(web *webAPI) { ) httpRegister(http.MethodPost, "/control/update", web.handleUpdate) - httpRegister(http.MethodGet, "/control/status", handleStatus) + httpRegister(http.MethodGet, "/control/status", web.handleStatus) httpRegister(http.MethodPost, "/control/i18n/change_language", handleI18nChangeLanguage) httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage) httpRegister(http.MethodGet, "/control/profile", handleGetProfile) @@ -189,6 +189,7 @@ func registerControlHandlers(web *webAPI) { RegisterAuthHandlers() } +// httpRegister registers an HTTP handler. func httpRegister(method, url string, handler http.HandlerFunc) { if method == "" { // "/dns-query" handler doesn't need auth, gzip and isn't restricted by 1 HTTP method diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go index 52bd8b69..6e52f80a 100644 --- a/internal/home/controlinstall.go +++ b/internal/home/controlinstall.go @@ -452,7 +452,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request // moment we'll allow setting up TLS in the initial configuration or the // configuration itself will use HTTPS protocol, because the underlying // functions potentially restart the HTTPS server. - err = startMods(web.baseLogger) + err = startMods(r.Context(), web.baseLogger, web.tlsManager) if err != nil { globalContext.firstRun = true copyInstallSettings(config, curConfig) @@ -461,7 +461,7 @@ func (web *webAPI) handleInstallConfigure(w http.ResponseWriter, r *http.Request return } - err = config.write() + err = config.write(web.tlsManager) if err != nil { globalContext.firstRun = true copyInstallSettings(config, curConfig) @@ -527,6 +527,31 @@ func decodeApplyConfigReq(r io.Reader) (req *applyConfigReq, restartHTTP bool, e return req, restartHTTP, err } +// startMods initializes and starts the DNS server after installation. +// baseLogger and tlsMgr must not be nil. +func startMods(ctx context.Context, baseLogger *slog.Logger, tlsMgr *tlsManager) (err error) { + statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&globalContext, config) + if err != nil { + return err + } + + err = initDNS(baseLogger, tlsMgr, statsDir, querylogDir) + if err != nil { + return err + } + + tlsMgr.start(ctx) + + err = startDNSServer() + if err != nil { + closeDNSServer() + + return err + } + + return nil +} + func (web *webAPI) registerInstallHandlers() { globalContext.mux.HandleFunc("/control/install/get_addresses", preInstall(ensureGET(web.handleInstallGetAddresses))) globalContext.mux.HandleFunc("/control/install/check_config", preInstall(ensurePOST(web.handleInstallCheckConfig))) diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go index 96891ad2..724f6862 100644 --- a/internal/home/controlupdate.go +++ b/internal/home/controlupdate.go @@ -62,7 +62,7 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) { return } - err = resp.setAllowedToAutoUpdate() + err = resp.setAllowedToAutoUpdate(web.tlsManager) if err != nil { // Don't wrap the error, because it's informative enough as is. aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err) @@ -158,14 +158,14 @@ type versionResponse struct { } // setAllowedToAutoUpdate sets CanAutoUpdate to true if AdGuard Home is actually -// allowed to perform an automatic update by the OS. -func (vr *versionResponse) setAllowedToAutoUpdate() (err error) { +// allowed to perform an automatic update by the OS. tlsMgr must not be nil. +func (vr *versionResponse) setAllowedToAutoUpdate(tlsMgr *tlsManager) (err error) { if vr.CanAutoUpdate != aghalg.NBTrue { return nil } tlsConf := &tlsConfigSettings{} - globalContext.tls.WriteDiskConfig(tlsConf) + tlsMgr.WriteDiskConfig(tlsConf) canUpdate := true if tlsConfUsesPrivilegedPorts(tlsConf) || diff --git a/internal/home/dns.go b/internal/home/dns.go index e4af268f..4a89131c 100644 --- a/internal/home/dns.go +++ b/internal/home/dns.go @@ -39,16 +39,22 @@ const ( // Called by other modules when configuration is changed func onConfigModified() { - err := config.write() + err := config.write(globalContext.tls) if err != nil { log.Error("writing config: %s", err) } } -// initDNS updates all the fields of the [globalContext] needed to initialize the DNS -// server and initializes it at last. It also must not be called unless -// [config] and [globalContext] are initialized. baseLogger must not be nil. -func initDNS(baseLogger *slog.Logger, statsDir, querylogDir string) (err error) { +// initDNS updates all the fields of the [globalContext] needed to initialize +// the DNS server and initializes it at last. It also must not be called unless +// [config] and [globalContext] are initialized. baseLogger and tlsMgr must not +// be nil. +func initDNS( + baseLogger *slog.Logger, + tlsMgr *tlsManager, + statsDir string, + querylogDir string, +) (err error) { anonymizer := config.anonymizer() statsConf := stats.Config{ @@ -104,7 +110,7 @@ func initDNS(baseLogger *slog.Logger, statsDir, querylogDir string) (err error) } tlsConf := &tlsConfigSettings{} - globalContext.tls.WriteDiskConfig(tlsConf) + tlsMgr.WriteDiskConfig(tlsConf) return initDNSServer( globalContext.filters, @@ -357,16 +363,18 @@ func newDNSCryptConfig( }, nil } +// dnsEncryption contains different types of TLS encryption addresses. type dnsEncryption struct { https string tls string quic string } -func getDNSEncryption() (de dnsEncryption) { +// getDNSEncryption returns the TLS encryption addresses that AdGuard Home +// listens on. tlsMgr must not be nil. +func getDNSEncryption(tlsMgr *tlsManager) (de dnsEncryption) { tlsConf := tlsConfigSettings{} - - globalContext.tls.WriteDiskConfig(&tlsConf) + tlsMgr.WriteDiskConfig(&tlsConf) if !tlsConf.Enabled || len(tlsConf.ServerName) == 0 { return dnsEncryption{} @@ -487,9 +495,11 @@ func startDNSServer() error { return nil } -func reconfigureDNSServer() (err error) { +// reconfigureDNSServer updates the DNS server configuration using the provided +// TLS settings. tlsMgr must not be nil. +func reconfigureDNSServer(tlsMgr *tlsManager) (err error) { tlsConf := &tlsConfigSettings{} - globalContext.tls.WriteDiskConfig(tlsConf) + tlsMgr.WriteDiskConfig(tlsConf) newConf, err := newServerConfig( &config.DNS, diff --git a/internal/home/home.go b/internal/home/home.go index f7092bdb..7777e6dd 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -57,7 +57,12 @@ type homeContext struct { auth *Auth // HTTP authentication module filters *filtering.DNSFilter // DNS filtering module web *webAPI // Web (HTTP, HTTPS) module - tls *tlsManager // TLS module + + // tls contains the current configuration and state of TLS encryption. + // + // TODO(s.chzhen): Remove once it is no longer called from different + // modules. See [onConfigModified]. + tls *tlsManager // etcHosts contains IP-hostname mappings taken from the OS-specific hosts // configuration files, for example /etc/hosts. @@ -519,13 +524,15 @@ func isUpdateEnabled(ctx context.Context, l *slog.Logger, opts *options, customU } } -// initWeb initializes the web module. upd and baseLogger must not be nil. +// initWeb initializes the web module. upd, baseLogger, and tlsMgr must not be +// nil. func initWeb( ctx context.Context, opts options, clientBuildFS fs.FS, upd *updater.Updater, baseLogger *slog.Logger, + tlsMgr *tlsManager, customURL bool, ) (web *webAPI, err error) { logger := baseLogger.With(slogutil.KeyPrefix, "webapi") @@ -548,6 +555,7 @@ func initWeb( updater: upd, logger: logger, baseLogger: baseLogger, + tlsManager: tlsMgr, clientFS: clientFS, @@ -638,7 +646,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH if !globalContext.firstRun { // Save the updated config. - err = config.write() + err = config.write(nil) fatalOnError(err) if config.HTTPConfig.Pprof.Enabled { @@ -656,25 +664,26 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalH globalContext.auth, err = initUsers() fatalOnError(err) - globalContext.tls, err = newTLSManager(config.TLS, config.DNS.ServePlainDNS) + tlsMgr, err := newTLSManager(config.TLS, config.DNS.ServePlainDNS) if err != nil { log.Error("initializing tls: %s", err) onConfigModified() } - sigHdlr.addTLSManager(globalContext.tls) + globalContext.tls = tlsMgr + sigHdlr.addTLSManager(tlsMgr) - globalContext.web, err = initWeb(ctx, opts, clientBuildFS, upd, slogLogger, customURL) + globalContext.web, err = initWeb(ctx, opts, clientBuildFS, upd, slogLogger, tlsMgr, customURL) fatalOnError(err) statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&globalContext, config) fatalOnError(err) if !globalContext.firstRun { - err = initDNS(slogLogger, statsDir, querylogDir) + err = initDNS(slogLogger, tlsMgr, statsDir, querylogDir) fatalOnError(err) - globalContext.tls.start() + tlsMgr.start(ctx) go func() { startErr := startDNSServer() @@ -807,31 +816,6 @@ func (c *configuration) anonymizer() (ipmut *aghnet.IPMut) { return aghnet.NewIPMut(anonFunc) } -// startMods initializes and starts the DNS server after installation. -// baseLogger must not be nil. -func startMods(baseLogger *slog.Logger) (err error) { - statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&globalContext, config) - if err != nil { - return err - } - - err = initDNS(baseLogger, statsDir, querylogDir) - if err != nil { - return err - } - - globalContext.tls.start() - - err = startDNSServer() - if err != nil { - closeDNSServer() - - return err - } - - return nil -} - // checkNetworkPermissions checks if the current user permissions are enough to // use the required networking functionality. func checkNetworkPermissions() { @@ -950,10 +934,6 @@ func cleanup(ctx context.Context) { log.Error("closing hosts container: %s", err) } } - - if globalContext.tls != nil { - globalContext.tls = nil - } } // This function is called before application exits @@ -1005,10 +985,12 @@ func printWebAddrs(proto, addr string, port uint16) { // printHTTPAddresses prints the IP addresses which user can use to access the // admin interface. proto is either schemeHTTP or schemeHTTPS. -func printHTTPAddresses(proto string) { +// +// TODO(s.chzhen): Implement separate functions for HTTP and HTTPS. +func printHTTPAddresses(proto string, tlsMgr *tlsManager) { tlsConf := tlsConfigSettings{} - if globalContext.tls != nil { - globalContext.tls.WriteDiskConfig(&tlsConf) + if tlsMgr != nil { + tlsMgr.WriteDiskConfig(&tlsConf) } port := config.HTTPConfig.Address.Port() @@ -1016,7 +998,6 @@ func printHTTPAddresses(proto string) { port = tlsConf.PortHTTPS } - // TODO(e.burkov): Inspect and perhaps merge with the previous condition. if proto == urlutil.SchemeHTTPS && tlsConf.ServerName != "" { printWebAddrs(proto, tlsConf.ServerName, tlsConf.PortHTTPS) diff --git a/internal/home/mobileconfig_internal_test.go b/internal/home/mobileconfig_internal_test.go index cc4564e9..86710003 100644 --- a/internal/home/mobileconfig_internal_test.go +++ b/internal/home/mobileconfig_internal_test.go @@ -19,10 +19,8 @@ func setupDNSIPs(t testing.TB) { t.Helper() prevConfig := config - prevTLS := globalContext.tls t.Cleanup(func() { config = prevConfig - globalContext.tls = prevTLS }) config = &configuration{ @@ -31,8 +29,6 @@ func setupDNSIPs(t testing.TB) { Port: defaultPortDNS, }, } - - globalContext.tls = &tlsManager{} } func TestHandleMobileConfigDoH(t *testing.T) { @@ -62,11 +58,6 @@ func TestHandleMobileConfigDoH(t *testing.T) { }) t.Run("error_no_host", func(t *testing.T) { - oldTLSConf := globalContext.tls - t.Cleanup(func() { globalContext.tls = oldTLSConf }) - - globalContext.tls = &tlsManager{conf: tlsConfigSettings{}} - r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/doh.mobileconfig", nil) require.NoError(t, err) @@ -134,11 +125,6 @@ func TestHandleMobileConfigDoT(t *testing.T) { }) t.Run("error_no_host", func(t *testing.T) { - oldTLSConf := globalContext.tls - t.Cleanup(func() { globalContext.tls = oldTLSConf }) - - globalContext.tls = &tlsManager{conf: tlsConfigSettings{}} - r, err := http.NewRequest(http.MethodGet, "https://example.com:12345/apple/dot.mobileconfig", nil) require.NoError(t, err) diff --git a/internal/home/service.go b/internal/home/service.go index 55e7823c..7075e5d1 100644 --- a/internal/home/service.go +++ b/internal/home/service.go @@ -339,7 +339,7 @@ AdGuard Home is successfully installed and will automatically start on boot. There are a few more things that must be configured before you can use it. Click on the link below and follow the Installation Wizard steps to finish setup. AdGuard Home is now available at the following addresses:`) - printHTTPAddresses(urlutil.SchemeHTTP) + printHTTPAddresses(urlutil.SchemeHTTP, nil) } } diff --git a/internal/home/tls.go b/internal/home/tls.go index f434a204..0e8d5c62 100644 --- a/internal/home/tls.go +++ b/internal/home/tls.go @@ -102,7 +102,9 @@ func (m *tlsManager) setCertFileTime() { } // start updates the configuration of t and starts it. -func (m *tlsManager) start() { +// +// TODO(s.chzhen): Use context. +func (m *tlsManager) start(_ context.Context) { m.registerWebHandlers() m.confLock.Lock() @@ -151,7 +153,7 @@ func (m *tlsManager) reload() { m.certLastMod = fi.ModTime().UTC() - _ = reconfigureDNSServer() + _ = reconfigureDNSServer(m) m.confLock.Lock() tlsConf = m.conf @@ -440,7 +442,7 @@ func (m *tlsManager) handleTLSConfigure(w http.ResponseWriter, r *http.Request) onConfigModified() - err = reconfigureDNSServer() + err = reconfigureDNSServer(m) if err != nil { aghhttp.Error(r, w, http.StatusInternalServerError, "%s", err) diff --git a/internal/home/web.go b/internal/home/web.go index 0e67d7e1..e9fe6dab 100644 --- a/internal/home/web.go +++ b/internal/home/web.go @@ -49,6 +49,10 @@ type webConfig struct { // nil. baseLogger *slog.Logger + // tlsManager contains the current configuration and state of TLS + // encryption. It must not be nil. + tlsManager *tlsManager + clientFS fs.FS // BindAddr is the binding address with port for plain HTTP web interface. @@ -108,6 +112,10 @@ type webAPI struct { // nil. baseLogger *slog.Logger + // tlsManager contains the current configuration and state of TLS + // encryption. + tlsManager *tlsManager + // httpsServer is the server that handles HTTPS traffic. If it is not nil, // [Web.http3Server] must also not be nil. httpsServer httpsServer @@ -124,6 +132,7 @@ func newWebAPI(ctx context.Context, conf *webConfig) (w *webAPI) { conf: conf, logger: conf.logger, baseLogger: conf.baseLogger, + tlsManager: conf.tlsManager, } clientFS := http.FileServer(http.FS(conf.clientFS)) @@ -220,7 +229,7 @@ func (web *webAPI) start(ctx context.Context) { // this loop is used as an ability to change listening host and/or port for !web.httpsServer.inShutdown { - printHTTPAddresses(urlutil.SchemeHTTP) + printHTTPAddresses(urlutil.SchemeHTTP, web.tlsManager) errs := make(chan error, 2) // Use an h2c handler to support unencrypted HTTP/2, e.g. for proxies. @@ -330,7 +339,7 @@ func (web *webAPI) tlsServerLoop(ctx context.Context) { ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), } - printHTTPAddresses(urlutil.SchemeHTTPS) + printHTTPAddresses(urlutil.SchemeHTTPS, web.tlsManager) if web.conf.serveHTTP3 { go web.mustStartHTTP3(ctx, addr)