all: sync with master

This commit is contained in:
Eugene Burkov
2024-12-05 16:00:18 +03:00
parent 54f3a5f990
commit 3f95db98d3
143 changed files with 3476 additions and 2959 deletions

View File

@@ -91,10 +91,7 @@ func InitAuth(
}
var err error
opts := *bbolt.DefaultOptions
opts.OpenFile = aghos.OpenFile
a.db, err = bbolt.Open(dbFilename, aghos.DefaultPermFile, &opts)
a.db, err = bbolt.Open(dbFilename, aghos.DefaultPermFile, nil)
if err != nil {
log.Error("auth: open DB: %s: %s", dbFilename, err)
if err.Error() == "invalid argument" {

View File

@@ -369,7 +369,7 @@ func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.
return
}
if !clients.storage.RemoveByName(cj.Name) {
if !clients.storage.RemoveByName(r.Context(), cj.Name) {
aghhttp.Error(r, w, http.StatusBadRequest, "Client not found")
return

View File

@@ -162,6 +162,12 @@ type configuration struct {
// SchemaVersion is the version of the configuration schema. See
// [configmigrate.LastSchemaVersion].
SchemaVersion uint `yaml:"schema_version"`
// UnsafeUseCustomUpdateIndexURL is the URL to the custom update index.
//
// NOTE: It's only exists for testing purposes and should not be used in
// release.
UnsafeUseCustomUpdateIndexURL bool `yaml:"unsafe_use_custom_update_index_url,omitempty"`
}
// httpConfig is a block with HTTP configuration params.
@@ -708,7 +714,7 @@ func (c *configuration) write() (err error) {
return fmt.Errorf("generating config file: %w", err)
}
err = aghos.WriteFile(confPath, buf.Bytes(), aghos.DefaultPermFile)
err = maybe.WriteFile(confPath, buf.Bytes(), aghos.DefaultPermFile)
if err != nil {
return fmt.Errorf("writing config file: %w", err)
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/NYTimes/gziphandler"
)
@@ -376,7 +377,7 @@ func handleHTTPSRedirect(w http.ResponseWriter, r *http.Request) (proceed bool)
//
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin.
originURL := &url.URL{
Scheme: aghhttp.SchemeHTTP,
Scheme: urlutil.SchemeHTTP,
Host: r.Host,
}
@@ -395,7 +396,7 @@ func httpsURL(u *url.URL, host string, portHTTPS uint16) (redirectURL *url.URL)
}
return &url.URL{
Scheme: aghhttp.SchemeHTTPS,
Scheme: urlutil.SchemeHTTPS,
Host: hostPort,
Path: u.Path,
RawQuery: u.RawQuery,

View File

@@ -75,30 +75,31 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) {
// update server.
func (web *webAPI) requestVersionInfo(resp *versionResponse, recheck bool) (err error) {
updater := web.conf.updater
for i := 0; i != 3; i++ {
for range 3 {
resp.VersionInfo, err = updater.VersionInfo(recheck)
if err != nil {
var terr temporaryError
if errors.As(err, &terr) && terr.Temporary() {
// Temporary network error. This case may happen while we're
// restarting our DNS server. Log and sleep for some time.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/934.
d := time.Duration(i) * time.Second
log.Info("update: temp net error: %q; sleeping for %s and retrying", err, d)
time.Sleep(d)
if err == nil {
return nil
}
continue
}
var terr temporaryError
if errors.As(err, &terr) && terr.Temporary() {
// Temporary network error. This case may happen while we're
// restarting our DNS server. Log and sleep for some time.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/934.
const sleepTime = 2 * time.Second
log.Info("update: temp net error: %v; sleeping for %s and retrying", err, sleepTime)
time.Sleep(sleepTime)
continue
}
break
}
if err != nil {
vcu := updater.VersionCheckURL()
return fmt.Errorf("getting version info from %s: %w", vcu, err)
return fmt.Errorf("getting version info: %w", err)
}
return nil

View File

@@ -23,6 +23,7 @@ import (
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/ameshkov/dnscrypt/v2"
yaml "gopkg.in/yaml.v3"
)
@@ -47,11 +48,11 @@ func onConfigModified() {
// initDNS updates all the fields of the [Context] needed to initialize the DNS
// server and initializes it at last. It also must not be called unless
// [config] and [Context] are initialized. l must not be nil.
func initDNS(l *slog.Logger, statsDir, querylogDir string) (err error) {
func initDNS(baseLogger *slog.Logger, statsDir, querylogDir string) (err error) {
anonymizer := config.anonymizer()
statsConf := stats.Config{
Logger: l.With(slogutil.KeyPrefix, "stats"),
Logger: baseLogger.With(slogutil.KeyPrefix, "stats"),
Filename: filepath.Join(statsDir, "stats.db"),
Limit: config.Stats.Interval.Duration,
ConfigModified: onConfigModified,
@@ -72,6 +73,7 @@ func initDNS(l *slog.Logger, statsDir, querylogDir string) (err error) {
}
conf := querylog.Config{
Logger: baseLogger.With(slogutil.KeyPrefix, "querylog"),
Anonymizer: anonymizer,
ConfigModified: onConfigModified,
HTTPRegister: httpRegister,
@@ -112,7 +114,7 @@ func initDNS(l *slog.Logger, statsDir, querylogDir string) (err error) {
anonymizer,
httpRegister,
tlsConf,
l,
baseLogger,
)
}
@@ -371,7 +373,7 @@ func getDNSEncryption() (de dnsEncryption) {
}
de.https = (&url.URL{
Scheme: "https",
Scheme: urlutil.SchemeHTTPS,
Host: addr,
Path: "/dns-query",
}).String()
@@ -456,7 +458,8 @@ func startDNSServer() error {
Context.filters.EnableFilters(false)
// TODO(s.chzhen): Pass context.
err := Context.clients.Start(context.TODO())
ctx := context.TODO()
err := Context.clients.Start(ctx)
if err != nil {
return fmt.Errorf("starting clients container: %w", err)
}
@@ -468,7 +471,11 @@ func startDNSServer() error {
Context.filters.Start()
Context.stats.Start()
Context.queryLog.Start()
err = Context.queryLog.Start(ctx)
if err != nil {
return fmt.Errorf("starting query log: %w", err)
}
return nil
}
@@ -524,12 +531,16 @@ func closeDNSServer() {
if Context.stats != nil {
err := Context.stats.Close()
if err != nil {
log.Debug("closing stats: %s", err)
log.Error("closing stats: %s", err)
}
}
if Context.queryLog != nil {
Context.queryLog.Close()
// TODO(s.chzhen): Pass context.
err := Context.queryLog.Shutdown(context.TODO())
if err != nil {
log.Error("closing query log: %s", err)
}
}
log.Debug("all dns modules are closed")

View File

@@ -12,7 +12,6 @@ import (
"net/url"
"os"
"os/signal"
"path"
"path/filepath"
"runtime"
"slices"
@@ -21,7 +20,6 @@ import (
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/aghtls"
@@ -42,6 +40,7 @@ import (
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/osutil"
)
@@ -159,7 +158,7 @@ func setupContext(opts options) (err error) {
if Context.firstRun {
log.Info("This is the first time AdGuard Home is launched")
checkPermissions()
checkNetworkPermissions()
return nil
}
@@ -495,11 +494,42 @@ func checkPorts() (err error) {
return nil
}
// isUpdateEnabled returns true if the update is enabled for current
// configuration. It also logs the decision. customURL should be true if the
// updater is using a custom URL.
func isUpdateEnabled(ctx context.Context, l *slog.Logger, opts *options, customURL bool) (ok bool) {
if opts.disableUpdate {
l.DebugContext(ctx, "updates are disabled by command-line option")
return false
}
switch version.Channel() {
case
version.ChannelDevelopment,
version.ChannelCandidate:
if customURL {
l.DebugContext(ctx, "updates are enabled because custom url is used")
} else {
l.DebugContext(ctx, "updates are disabled for development and candidate builds")
}
return customURL
default:
l.DebugContext(ctx, "updates are enabled")
return true
}
}
// initWeb initializes the web module.
func initWeb(
ctx context.Context,
opts options,
clientBuildFS fs.FS,
upd *updater.Updater,
l *slog.Logger,
customURL bool,
) (web *webAPI, err error) {
var clientFS fs.FS
if opts.localFrontend {
@@ -513,17 +543,7 @@ func initWeb(
}
}
disableUpdate := opts.disableUpdate
switch version.Channel() {
case
version.ChannelDevelopment,
version.ChannelCandidate:
disableUpdate = true
}
if disableUpdate {
log.Info("AdGuard Home updates are disabled")
}
disableUpdate := !isUpdateEnabled(ctx, l, &opts, customURL)
webConf := &webConfig{
updater: upd,
@@ -544,7 +564,7 @@ func initWeb(
web = newWebAPI(webConf, l)
if web == nil {
return nil, fmt.Errorf("initializing web: %w", err)
return nil, errors.Error("can not initialize web")
}
return web, nil
@@ -557,6 +577,8 @@ 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{}) {
// Configure working dir.
err := initWorkingDir(opts)
@@ -604,33 +626,13 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
execPath, err := os.Executable()
fatalOnError(errors.Annotate(err, "getting executable path: %w"))
u := &url.URL{
Scheme: "https",
// TODO(a.garipov): Make configurable.
Host: "static.adtidy.org",
Path: path.Join("adguardhome", version.Channel(), "version.json"),
}
confPath := configFilePath()
log.Debug("using config path %q for updater", confPath)
upd := updater.NewUpdater(&updater.Config{
Client: config.Filtering.HTTPClient,
Version: version.Version(),
Channel: version.Channel(),
GOARCH: runtime.GOARCH,
GOOS: runtime.GOOS,
GOARM: version.GOARM(),
GOMIPS: version.GOMIPS(),
WorkDir: Context.workDir,
ConfName: confPath,
ExecPath: execPath,
VersionCheckURL: u.String(),
})
upd, customURL := newUpdater(ctx, slogLogger, Context.workDir, confPath, execPath, config)
// TODO(e.burkov): This could be made earlier, probably as the option's
// effect.
cmdlineUpdate(opts, upd, slogLogger)
cmdlineUpdate(ctx, slogLogger, opts, upd)
if !Context.firstRun {
// Save the updated config.
@@ -643,7 +645,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
}
dataDir := Context.getDataDir()
err = aghos.MkdirAll(dataDir, aghos.DefaultPermDir)
err = os.MkdirAll(dataDir, aghos.DefaultPermDir)
fatalOnError(errors.Annotate(err, "creating DNS data dir at %s: %w", dataDir))
GLMode = opts.glinetMode
@@ -658,7 +660,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
onConfigModified()
}
Context.web, err = initWeb(opts, clientBuildFS, upd, slogLogger)
Context.web, err = initWeb(ctx, opts, clientBuildFS, upd, slogLogger, customURL)
fatalOnError(err)
statsDir, querylogDir, err := checkStatsAndQuerylogDirs(&Context, config)
@@ -686,18 +688,87 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) {
}
}
if permcheck.NeedsMigration(confPath) {
permcheck.Migrate(Context.workDir, dataDir, statsDir, querylogDir, confPath)
if !opts.noPermCheck {
checkPermissions(ctx, slogLogger, Context.workDir, confPath, dataDir, statsDir, querylogDir)
}
permcheck.Check(Context.workDir, dataDir, statsDir, querylogDir, confPath)
Context.web.start()
// Wait for other goroutines to complete their job.
<-done
}
// newUpdater creates a new AdGuard Home updater. customURL is true if the user
// has specified a custom version announcement URL.
func newUpdater(
ctx context.Context,
l *slog.Logger,
workDir string,
confPath string,
execPath string,
config *configuration,
) (upd *updater.Updater, customURL bool) {
// envName is the name of the environment variable that can be used to
// override the default version check URL.
const envName = "ADGUARD_HOME_TEST_UPDATE_VERSION_URL"
customURLStr := os.Getenv(envName)
var versionURL *url.URL
switch {
case version.Channel() == version.ChannelRelease:
// Only enable custom version URL for development builds.
l.DebugContext(ctx, "custom version url is disabled for release builds")
case !config.UnsafeUseCustomUpdateIndexURL:
l.DebugContext(ctx, "custom version url is disabled in config")
default:
versionURL, _ = url.Parse(customURLStr)
}
err := urlutil.ValidateHTTPURL(versionURL)
if customURL = err == nil; !customURL {
l.DebugContext(ctx, "parsing custom version url", slogutil.KeyError, err)
versionURL = updater.DefaultVersionURL()
}
l.DebugContext(ctx, "creating updater", "config_path", confPath)
return updater.NewUpdater(&updater.Config{
Client: config.Filtering.HTTPClient,
Version: version.Version(),
Channel: version.Channel(),
GOARCH: runtime.GOARCH,
GOOS: runtime.GOOS,
GOARM: version.GOARM(),
GOMIPS: version.GOMIPS(),
WorkDir: workDir,
ConfName: confPath,
ExecPath: execPath,
VersionCheckURL: versionURL,
}), customURL
}
// checkPermissions checks and migrates permissions of the files and directories
// used by AdGuard Home, if needed.
func checkPermissions(
ctx context.Context,
baseLogger *slog.Logger,
workDir string,
confPath string,
dataDir string,
statsDir string,
querylogDir string,
) {
l := baseLogger.With(slogutil.KeyPrefix, "permcheck")
if permcheck.NeedsMigration(ctx, l, workDir, confPath) {
permcheck.Migrate(ctx, l, workDir, dataDir, statsDir, querylogDir, confPath)
}
permcheck.Check(ctx, l, workDir, dataDir, statsDir, querylogDir, confPath)
}
// initUsers initializes context auth module. Clears config users field.
func initUsers() (auth *Auth, err error) {
sessFilename := filepath.Join(Context.getDataDir(), "sessions.db")
@@ -757,8 +828,9 @@ func startMods(l *slog.Logger) (err error) {
return nil
}
// Check if the current user permissions are enough to run AdGuard Home
func checkPermissions() {
// checkNetworkPermissions checks if the current user permissions are enough to
// use the required networking functionality.
func checkNetworkPermissions() {
log.Info("Checking if AdGuard Home has necessary permissions")
if ok, err := aghnet.CanBindPrivilegedPorts(); !ok || err != nil {
@@ -936,12 +1008,12 @@ func printHTTPAddresses(proto string) {
}
port := config.HTTPConfig.Address.Port()
if proto == aghhttp.SchemeHTTPS {
if proto == urlutil.SchemeHTTPS {
port = tlsConf.PortHTTPS
}
// TODO(e.burkov): Inspect and perhaps merge with the previous condition.
if proto == aghhttp.SchemeHTTPS && tlsConf.ServerName != "" {
if proto == urlutil.SchemeHTTPS && tlsConf.ServerName != "" {
printWebAddrs(proto, tlsConf.ServerName, tlsConf.PortHTTPS)
return
@@ -1001,7 +1073,7 @@ type jsonError struct {
}
// cmdlineUpdate updates current application and exits. l must not be nil.
func cmdlineUpdate(opts options, upd *updater.Updater, l *slog.Logger) {
func cmdlineUpdate(ctx context.Context, l *slog.Logger, opts options, upd *updater.Updater) {
if !opts.performUpdate {
return
}
@@ -1014,20 +1086,19 @@ func cmdlineUpdate(opts options, upd *updater.Updater, l *slog.Logger) {
err := initDNSServer(nil, nil, nil, nil, nil, nil, &tlsConfigSettings{}, l)
fatalOnError(err)
log.Info("cmdline update: performing update")
l.InfoContext(ctx, "performing update via cli")
info, err := upd.VersionInfo(true)
if err != nil {
vcu := upd.VersionCheckURL()
log.Error("getting version info from %s: %s", vcu, err)
l.ErrorContext(ctx, "getting version info", slogutil.KeyError, err)
os.Exit(1)
os.Exit(osutil.ExitCodeFailure)
}
if info.NewVersion == version.Version() {
log.Info("no updates available")
l.InfoContext(ctx, "no updates available")
os.Exit(0)
os.Exit(osutil.ExitCodeSuccess)
}
err = upd.Update(Context.firstRun)
@@ -1035,10 +1106,10 @@ func cmdlineUpdate(opts options, upd *updater.Updater, l *slog.Logger) {
err = restartService()
if err != nil {
log.Debug("restarting service: %s", err)
log.Info("AdGuard Home was not installed as a service. " +
l.DebugContext(ctx, "restarting service", slogutil.KeyError, err)
l.InfoContext(ctx, "AdGuard Home was not installed as a service. "+
"Please restart running instances of AdGuardHome manually.")
}
os.Exit(0)
os.Exit(osutil.ExitCodeSuccess)
}

View File

@@ -8,11 +8,11 @@ import (
"net/url"
"path"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/google/uuid"
"howett.net/plist"
)
@@ -84,7 +84,7 @@ func encodeMobileConfig(d *dnsSettings, clientID string) ([]byte, error) {
case dnsProtoHTTPS:
dspName = fmt.Sprintf("%s DoH", d.ServerName)
u := &url.URL{
Scheme: aghhttp.SchemeHTTPS,
Scheme: urlutil.SchemeHTTPS,
Host: d.ServerName,
Path: path.Join("/dns-query", clientID),
}

View File

@@ -78,6 +78,10 @@ type options struct {
// localFrontend forces AdGuard Home to use the frontend files from disk
// rather than the ones that have been compiled into the binary.
localFrontend bool
// noPermCheck disables checking and migration of permissions for the
// security-sensitive files.
noPermCheck bool
}
// initCmdLineOpts completes initialization of the global command-line option
@@ -305,6 +309,15 @@ var cmdLineOpts = []cmdLineOpt{{
description: "Run in GL-Inet compatibility mode.",
longName: "glinet",
shortName: "",
}, {
updateWithValue: nil,
updateNoValue: func(o options) (options, error) { o.noPermCheck = true; return o, nil },
effect: nil,
serialize: func(o options) (val string, ok bool) { return "", o.noPermCheck },
description: "Skip checking and migration of permissions " +
"of security-sensitive files.",
longName: "no-permcheck",
shortName: "",
}, {
updateWithValue: nil,
updateNoValue: nil,

View File

@@ -10,11 +10,11 @@ import (
"syscall"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/kardianos/service"
)
@@ -336,7 +336,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(aghhttp.SchemeHTTP)
printHTTPAddresses(urlutil.SchemeHTTP)
}
}

View File

@@ -11,13 +11,13 @@ import (
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/updater"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/httputil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/NYTimes/gziphandler"
"github.com/quic-go/quic-go/http3"
"golang.org/x/net/http2"
@@ -101,6 +101,8 @@ type webAPI struct {
// newWebAPI creates a new instance of the web UI and API server. l must not be
// nil.
//
// TODO(a.garipov): Return a proper error.
func newWebAPI(conf *webConfig, l *slog.Logger) (w *webAPI) {
log.Info("web: initializing")
@@ -192,7 +194,7 @@ func (web *webAPI) start() {
// this loop is used as an ability to change listening host and/or port
for !web.httpsServer.inShutdown {
printHTTPAddresses(aghhttp.SchemeHTTP)
printHTTPAddresses(urlutil.SchemeHTTP)
errs := make(chan error, 2)
// Use an h2c handler to support unencrypted HTTP/2, e.g. for proxies.
@@ -286,7 +288,7 @@ func (web *webAPI) tlsServerLoop() {
WriteTimeout: web.conf.WriteTimeout,
}
printHTTPAddresses(aghhttp.SchemeHTTPS)
printHTTPAddresses(urlutil.SchemeHTTPS)
if web.conf.serveHTTP3 {
go web.mustStartHTTP3(addr)