diff --git a/go.mod b/go.mod index 5cb33051..e3ab56f8 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23.6 require ( github.com/AdguardTeam/dnsproxy v0.75.0 - github.com/AdguardTeam/golibs v0.32.1 + github.com/AdguardTeam/golibs v0.32.5 github.com/AdguardTeam/urlfilter v0.20.0 github.com/NYTimes/gziphandler v1.1.1 github.com/ameshkov/dnscrypt/v2 v2.3.0 @@ -33,9 +33,9 @@ require ( github.com/stretchr/testify v1.10.0 github.com/ti-mo/netfilter v0.5.2 go.etcd.io/bbolt v1.4.0 - golang.org/x/crypto v0.32.0 - golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 - golang.org/x/net v0.34.0 + golang.org/x/crypto v0.33.0 + golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa + golang.org/x/net v0.35.0 golang.org/x/sys v0.30.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 @@ -63,6 +63,6 @@ require ( golang.org/x/mod v0.23.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/tools v0.30.0 // indirect gonum.org/v1/gonum v0.15.1 // indirect ) diff --git a/go.sum b/go.sum index 49faf8eb..d84cc596 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/AdguardTeam/dnsproxy v0.75.0 h1:v8/Oq/xPYzNoALR7SEUZEIbKmjnPcXLVhJLFVbrozEc= github.com/AdguardTeam/dnsproxy v0.75.0/go.mod h1:O2qoXwF4BUBFui7OMUiWSYwapEDcYxKWeur4+jfy9nM= -github.com/AdguardTeam/golibs v0.32.1 h1:Ajf6Q0k+A9zjFbj8HOzNAbHImrV4JtbT0vwy02D6VeI= -github.com/AdguardTeam/golibs v0.32.1/go.mod h1:dXRLSsnJQDxOfQVl0ochy1bfk4NgnJQGQdR1YPJdwcw= +github.com/AdguardTeam/golibs v0.32.5 h1:4Rkv2xBnyJe6l/EM2MFgoY1S4pweYwDgLTYg2MDArEA= +github.com/AdguardTeam/golibs v0.32.5/go.mod h1:agsvz8Iyv0uV9NU56hpCoFLAtSPkiBf9nPVhDvdUIb0= github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs= github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= @@ -128,10 +128,10 @@ go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34= -golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= @@ -142,8 +142,8 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= @@ -169,14 +169,14 @@ golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/home/clients.go b/internal/home/clients.go index 0f9b5266..23958cc9 100644 --- a/internal/home/clients.go +++ b/internal/home/clients.go @@ -75,6 +75,7 @@ func (clients *clientsContainer) Init( etcHosts *aghnet.HostsContainer, arpDB arpdb.Interface, filteringConf *filtering.Config, + sigHdlr *signalHandler, ) (err error) { // TODO(s.chzhen): Refactor it. if clients.storage != nil { @@ -120,6 +121,8 @@ func (clients *clientsContainer) Init( return fmt.Errorf("init client storage: %w", err) } + sigHdlr.addClientStorage(clients.storage) + return nil } diff --git a/internal/home/clients_internal_test.go b/internal/home/clients_internal_test.go index ad44e2e3..0f80604b 100644 --- a/internal/home/clients_internal_test.go +++ b/internal/home/clients_internal_test.go @@ -31,6 +31,7 @@ func newClientsContainer(t *testing.T) (c *clientsContainer) { nil, nil, &filtering.Config{}, + newSignalHandler(nil, nil), ) require.NoError(t, err) diff --git a/internal/home/home.go b/internal/home/home.go index 3cd3b27d..0d42dd26 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -113,31 +113,23 @@ func Main(clientBuildFS fs.FS) { signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) - go func() { - ctx := context.Background() - for { - sig := <-signals - log.Info("Received signal %q", sig) - switch sig { - case syscall.SIGHUP: - globalContext.clients.storage.ReloadARP(ctx) - globalContext.tls.reload() - default: - cleanup(ctx) - cleanupAlways() - close(done) - } - } - }() + ctx := context.Background() + sigHdlr := newSignalHandler(signals, func(ctx context.Context) { + cleanup(ctx) + cleanupAlways() + close(done) + }) + + go sigHdlr.handle(ctx) if opts.serviceControlAction != "" { - handleServiceControlAction(opts, clientBuildFS, signals, done) + handleServiceControlAction(opts, clientBuildFS, signals, done, sigHdlr) return } // run the protection - run(opts, clientBuildFS, done) + run(opts, clientBuildFS, done, sigHdlr) } // setupContext initializes [globalContext] fields. It also reads and upgrades @@ -278,7 +270,11 @@ func setupOpts(opts options) (err error) { } // initContextClients initializes Context clients and related fields. -func initContextClients(ctx context.Context, logger *slog.Logger) (err error) { +func initContextClients( + ctx context.Context, + logger *slog.Logger, + sigHdlr *signalHandler, +) (err error) { err = setupDNSFilteringConf(ctx, logger, config.Filtering) if err != nil { // Don't wrap the error, because it's informative enough as is. @@ -313,6 +309,7 @@ func initContextClients(ctx context.Context, logger *slog.Logger) (err error) { globalContext.etcHosts, arpDB, config.Filtering, + sigHdlr, ) } @@ -583,7 +580,7 @@ func fatalOnError(err error) { // run configures and starts AdGuard Home. // // TODO(e.burkov): Make opts a pointer. -func run(opts options, clientBuildFS fs.FS, done chan struct{}) { +func run(opts options, clientBuildFS fs.FS, done chan struct{}, sigHdlr *signalHandler) { // Configure working dir. err := initWorkingDir(opts) fatalOnError(err) @@ -599,6 +596,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) { // TODO(a.garipov): Use slog everywhere. slogLogger := newSlogLogger(ls) + sigHdlr.swapLogger(slogLogger) // Print the first message after logger is configured. log.Info(version.Full()) @@ -621,7 +619,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) { // TODO(s.chzhen): Use it for the entire initialization process. ctx := context.Background() - err = initContextClients(ctx, slogLogger) + err = initContextClients(ctx, slogLogger, sigHdlr) fatalOnError(err) err = setupOpts(opts) @@ -664,6 +662,8 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) { onConfigModified() } + sigHdlr.addTLSManager(globalContext.tls) + globalContext.web, err = initWeb(ctx, opts, clientBuildFS, upd, slogLogger, customURL) fatalOnError(err) diff --git a/internal/home/service.go b/internal/home/service.go index 95ceafc6..4883e94c 100644 --- a/internal/home/service.go +++ b/internal/home/service.go @@ -36,6 +36,7 @@ type program struct { signals chan os.Signal done chan struct{} opts options + sigHdlr *signalHandler } // type check @@ -47,7 +48,7 @@ func (p *program) Start(_ service.Service) (err error) { args := p.opts args.runningAsService = true - go run(args, p.clientBuildFS, p.done) + go run(args, p.clientBuildFS, p.done, p.sigHdlr) return nil } @@ -204,6 +205,7 @@ func handleServiceControlAction( clientBuildFS fs.FS, signals chan os.Signal, done chan struct{}, + sigHdlr *signalHandler, ) { // Call chooseSystem explicitly to introduce OpenBSD support for service // package. It's a noop for other GOOS values. @@ -244,6 +246,7 @@ func handleServiceControlAction( signals: signals, done: done, opts: runOpts, + sigHdlr: sigHdlr, }, svcConfig) if err != nil { log.Fatalf("service: initializing service: %s", err) diff --git a/internal/home/signal.go b/internal/home/signal.go new file mode 100644 index 00000000..824e62dd --- /dev/null +++ b/internal/home/signal.go @@ -0,0 +1,121 @@ +package home + +import ( + "context" + "log/slog" + "os" + "sync" + "sync/atomic" + "syscall" + + "github.com/AdguardTeam/AdGuardHome/internal/client" + "github.com/AdguardTeam/golibs/logutil/slogutil" + "github.com/AdguardTeam/golibs/osutil" +) + +// signalHandler processes incoming signals. It reloads configurations of +// stored entities on SIGHUP and performs cleanup on all other signals. +type signalHandler struct { + // logger is used to log the operation of the signal handler. Initially, + // [slog.Default] is used, but it should be swapped later using + // [signalHandler.swapLogger]. + logger *atomic.Pointer[slog.Logger] + + // mu protects clientStorage and tlsManager. + mu *sync.Mutex + + // clientStorage is used to reload information about runtime clients with an + // ARP source. + clientStorage *client.Storage + + // tlsManager is used to reload the TLS configuration. + tlsManager *tlsManager + + // signals receives incoming signals. + signals <-chan os.Signal + + // cleanup is called to perform cleanup on all incoming signals, except + // SIGHUP. + cleanup func(ctx context.Context) +} + +// newSignalHandler returns a new properly initialized *signalHandler. +func newSignalHandler( + signals <-chan os.Signal, + cleanup func(ctx context.Context), +) (h *signalHandler) { + h = &signalHandler{ + logger: &atomic.Pointer[slog.Logger]{}, + mu: &sync.Mutex{}, + signals: signals, + cleanup: cleanup, + } + + h.logger.Store(slog.Default()) + + return h +} + +// swapLogger replaces the stored logger with the given logger. +func (h *signalHandler) swapLogger(logger *slog.Logger) { + h.logger.Swap(logger) +} + +// addClientStorage stores the client storage. +func (h *signalHandler) addClientStorage(s *client.Storage) { + h.mu.Lock() + defer h.mu.Unlock() + + h.clientStorage = s +} + +// addTLSManager stores the TLS manager. +func (h *signalHandler) addTLSManager(m *tlsManager) { + h.mu.Lock() + defer h.mu.Unlock() + + h.tlsManager = m +} + +// handle processes incoming signals. It blocks until a signal is received. It +// reloads configurations of stored entities on SIGHUP, or performs cleanup on +// all other signals. It is intended to be used as a goroutine. +func (h *signalHandler) handle(ctx context.Context) { + // NOTE: Avoid using [slogutil.RecoverAndExit] to prevent immediate + // evaluation of the logger. + defer func() { + v := recover() + if v == nil { + return + } + + slogutil.PrintRecovered(ctx, h.logger.Load(), v) + + os.Exit(osutil.ExitCodeFailure) + }() + + for { + sig := <-h.signals + h.logger.Load().InfoContext(ctx, "received signal", "signal", sig) + switch sig { + case syscall.SIGHUP: + h.reloadConfig(ctx) + default: + h.cleanup(ctx) + } + } +} + +// reloadConfig refreshes configurations of stored entities. +func (h *signalHandler) reloadConfig(ctx context.Context) { + h.mu.Lock() + defer h.mu.Unlock() + + if h.clientStorage != nil { + h.clientStorage.ReloadARP(ctx) + } + + if h.tlsManager != nil { + h.tlsManager.reload() + } +}