Pull request 2354: AGDNS-2690-global-context-tls

Merge in DNS/adguard-home from AGDNS-2690-global-context-tls to master

Squashed commit of the following:

commit ae1d9e6f3f3b8abefbc5e776eb256577f7fbbb0f
Merge: 6f30f488a bf9be98c7
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 18:15:24 2025 +0300

    Merge branch 'master' into AGDNS-2690-global-context-tls

commit 6f30f488aa2305e518000dc6c1028ede83bf1cc6
Merge: baa187ab0 66fba942c
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 15:08:47 2025 +0300

    Merge branch 'master' into AGDNS-2690-global-context-tls

commit baa187ab0b6db7f41e49dece7b4d0430409e7cae
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 10 15:08:39 2025 +0300

    home: imp docs

commit 96a09389c5049a84bb30ed285cc5e1df9aaa438f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 6 20:15:05 2025 +0300

    home: imp docs

commit 1cd007707af4a7a5160c8fe21b20b84543d59e5a
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 6 18:54:07 2025 +0300

    home: imp docs

commit ad3d2b6616c2c3aba566a2158ffc597e5802929f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Tue Mar 4 19:38:45 2025 +0300

    home: global context tls
This commit is contained in:
Stanislav Chzhen
2025-03-10 18:24:41 +03:00
parent bf9be98c71
commit 3255efcaf3
10 changed files with 103 additions and 89 deletions

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)))

View File

@@ -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) ||

View File

@@ -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,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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)