Sync v2.16.0

This commit is contained in:
Andrey Meshkov
2025-09-29 10:32:55 +03:00
parent 97dbe76804
commit 5da2a9fd26
87 changed files with 2603 additions and 1744 deletions

View File

@@ -21,5 +21,6 @@
},
"line-length": false,
"no-bare-urls": false,
"no-emphasis-as-heading": false,
"link-fragments": false
}

View File

@@ -7,6 +7,16 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
[kec]: https://keepachangelog.com/en/1.0.0/
[sem]: https://semver.org/spec/v2.0.0.html
## AGDNS-3241 / Build 1067
- The environment variables `QUERYLOG_SEMAPHORE_ENABLED` and `QUERYLOG_SEMAPHORE_LIMIT` have been added.
- The environment variable `MAX_THREADS` has been added.
## AGDNS-3228 / Build 1063
- The environment variables `CRASH_OUTPUT_DIR`, `CRASH_OUTPUT_ENABLED`, and `CRASH_OUTPUT_PREFIX` have been added.
## AGDNS-2998 / Build 1042
- Profiles file cache version has been incremented. The new field `StandardEnabled` has been added to access object.

View File

@@ -24,7 +24,7 @@ BRANCH = $${BRANCH:-$$(git rev-parse --abbrev-ref HEAD)}
GOAMD64 = v1
GOPROXY = https://proxy.golang.org|direct
GOTELEMETRY = off
GOTOOLCHAIN = go1.24.6
GOTOOLCHAIN = go1.25.1
RACE = 0
REVISION = $${REVISION:-$$(git rev-parse --short HEAD)}
VERSION = 0
@@ -36,6 +36,7 @@ ENV = env \
GOPROXY='$(GOPROXY)' \
GOTELEMETRY='$(GOTELEMETRY)' \
GOTOOLCHAIN='$(GOTOOLCHAIN)' \
ONLY="$(ONLY)" \
PATH="$${PWD}/bin:$$("$(GO.MACRO)" env GOPATH)/bin:$${PATH}" \
RACE='$(RACE)' \
REVISION="$(REVISION)" \
@@ -52,12 +53,16 @@ ENV_MISC = env \
# Keep this target first, so that a naked make invocation triggers a
# full build.
.PHONY: build
build: go-deps go-build
.PHONY: init
init: ; git config core.hooksPath ./scripts/hooks
.PHONY: test
test: go-test
.PHONY: go-bench go-build go-deps go-env go-fuzz go-gen go-lint go-test go-tools go-upd-tools
go-bench: ; $(ENV) "$(SHELL)" ./scripts/make/go-bench.sh
go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh
go-deps: ; $(ENV) "$(SHELL)" ./scripts/make/go-deps.sh
@@ -69,23 +74,28 @@ go-test: ; $(ENV) RACE='1' "$(SHELL)" ./scripts/make/go-test.sh
go-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-tools.sh
go-upd-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-upd-tools.sh
.PHONY: go-check
go-check: go-tools go-lint go-test
# A quick check to make sure that all operating systems relevant to the
# development of the project can be typechecked and built successfully.
.PHONY: go-os-check
go-os-check:
$(ENV) GOOS='darwin' "$(GO.MACRO)" vet ./internal/... ./internal/dnsserver/...
$(ENV) GOOS='linux' "$(GO.MACRO)" vet ./internal/... ./internal/dnsserver/...
$(ENV) GOOS='darwin' "$(GO.MACRO)" vet work
$(ENV) GOOS='linux' "$(GO.MACRO)" vet work
# Additionally, check the AdGuard Home OSs in the dnsserver module.
$(ENV) GOOS='freebsd' "$(GO.MACRO)" vet ./internal/dnsserver/...
$(ENV) GOOS='openbsd' "$(GO.MACRO)" vet ./internal/dnsserver/...
$(ENV) GOOS='windows' "$(GO.MACRO)" vet ./internal/dnsserver/...
.PHONY: txt-lint
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
.PHONY: md-lint
md-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/md-lint.sh
sh-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/sh-lint.sh
# Targets related to AdGuard DNS start here.
.PHONY: sync-github
sync-github: ; $(ENV) "$(SHELL)" ./scripts/make/github-sync.sh

View File

@@ -640,7 +640,7 @@ The items of the `filtering_groups` array have the following properties:
**Example:** `true`.
- <a href="#fg-*-block_chrome_prefetch" id="fg-*-block_chrome_prefetch" name="fg-*-block_chrome_prefetch">`block_chrome_prefetch`</a>: If true, Chrome prefetch domain queries are blocked for requests using this filtering group, forcing the preferch proxy into preflight mode.
- <a href="#fg-*-block_chrome_prefetch" id="fg-*-block_chrome_prefetch" name="fg-*-block_chrome_prefetch">`block_chrome_prefetch`</a>: If true, Chrome prefetch domain queries are blocked for requests using this filtering group, forcing the prefetch proxy into preflight mode.
**Example:** `true`.

View File

@@ -11,7 +11,7 @@
Development is supported on Linux and macOS (aka Darwin) systems.
1. Install Go 1.24 or later.
1. Install Go 1.25 or later.
2. Call `make init` to set up the Git pre-commit hook.
@@ -43,13 +43,27 @@ This is not an extensive list. See `../Makefile`.
- `../internal/backendpb/dns.pb.go`
- `../internal/backendpb/dns_grpc.pb.go`
- `../internal/ecscache/ecsblockilist_generate.go`
- `../internal/ecscache/ecsblocklist_generate.go`
- `../internal/geoip/asntops_generate.go`
- `../internal/geoip/country_generate.go`
- `../internal/profiledb/internal/filecachepb/filecache.pb.go`
You'll need to [install `protoc`][protoc] for the last one.
Use the `ONLY` environment variable to generate selected parts. This generates only the backend and file-cache protobuf files:
```sh
make ONLY='backendpb filecachepb' go-gen
```
Available values:
- `backendpb`
- `ecscache`
- `filecachepb`
- `geoip_asntops`
- `geoip_country`
- `make go-lint`: Run Go checkers and static analysis.
- `make go-test`: Run Go tests.

View File

@@ -16,6 +16,9 @@ AdGuard DNS uses [environment variables][wiki-env] to store some of the more sen
- [`CONSUL_ALLOWLIST_URL`](#CONSUL_ALLOWLIST_URL)
- [`CONSUL_DNSCHECK_KV_URL`](#CONSUL_DNSCHECK_KV_URL)
- [`CONSUL_DNSCHECK_SESSION_URL`](#CONSUL_DNSCHECK_SESSION_URL)
- [`CRASH_OUTPUT_DIR`](#CRASH_OUTPUT_DIR)
- [`CRASH_OUTPUT_ENABLED`](#CRASH_OUTPUT_ENABLED)
- [`CRASH_OUTPUT_PREFIX`](#CRASH_OUTPUT_PREFIX)
- [`CUSTOM_DOMAINS_API_KEY`](#CUSTOM_DOMAINS_API_KEY)
- [`CUSTOM_DOMAINS_CACHE_PATH`](#CUSTOM_DOMAINS_CACHE_PATH)
- [`CUSTOM_DOMAINS_ENABLED`](#CUSTOM_DOMAINS_ENABLED)
@@ -36,6 +39,7 @@ AdGuard DNS uses [environment variables][wiki-env] to store some of the more sen
- [`LISTEN_PORT`](#LISTEN_PORT)
- [`LOG_FORMAT`](#LOG_FORMAT)
- [`LOG_TIMESTAMP`](#LOG_TIMESTAMP)
- [`MAX_THREADS`](#MAX_THREADS)
- [`METRICS_NAMESPACE`](#METRICS_NAMESPACE)
- [`NEW_REG_DOMAINS_ENABLED`](#NEW_REG_DOMAINS_ENABLED)
- [`NEW_REG_DOMAINS_URL`](#NEW_REG_DOMAINS_URL)
@@ -54,6 +58,8 @@ AdGuard DNS uses [environment variables][wiki-env] to store some of the more sen
- [`REDIS_PORT`](#REDIS_PORT)
- [`REDIS_WAIT`](#REDIS_WAIT)
- [`QUERYLOG_PATH`](#QUERYLOG_PATH)
- [`QUERYLOG_SEMAPHORE_ENABLED`](#QUERYLOG_SEMAPHORE_ENABLED)
- [`QUERYLOG_SEMAPHORE_LIMIT`](#QUERYLOG_SEMAPHORE_LIMIT)
- [`RATELIMIT_ALLOWLIST_TYPE`](#RATELIMIT_ALLOWLIST_TYPE)
- [`RULESTAT_URL`](#RULESTAT_URL)
- [`SAFE_BROWSING_ENABLED`](#SAFE_BROWSING_ENABLED)
@@ -166,6 +172,24 @@ The HTTP(S) URL of the session API of the Consul instance used as a key-value da
**Default:** **Unset.**
## <a href="#CRASH_OUTPUT_DIR" id="CRASH_OUTPUT_DIR" name="CRASH_OUTPUT_DIR">`CRASH_OUTPUT_DIR`</a>
The path to the directory used to create crash reports. The directory must exist.
**Default:** No default value, the variable is required if `CRASH_OUTPUT_ENABLED` is set to `1`.
## <a href="#CRASH_OUTPUT_ENABLED" id="CRASH_OUTPUT_ENABLED" name="CRASH_OUTPUT_ENABLED">`CRASH_OUTPUT_ENABLED`</a>
When set to `1`, put a crash report to `CRASH_OUTPUT_DIR`.
**Default:** `0`.
## <a href="#CRASH_OUTPUT_PREFIX" id="CRASH_OUTPUT_PREFIX" name="CRASH_OUTPUT_PREFIX">`CRASH_OUTPUT_PREFIX`</a>
The prefix to use for the crash report files. The variable is required if `CRASH_OUTPUT_ENABLED` is set to `1`.
**Default:** `agdns`.
## <a href="#CUSTOM_DOMAINS_API_KEY" id="CUSTOM_DOMAINS_API_KEY" name="CUSTOM_DOMAINS_API_KEY">`CUSTOM_DOMAINS_API_KEY`</a>
The API key to use when authenticating queries to the backend custom-domain API, if any. The API key should be valid as defined by [RFC 6750].
@@ -311,6 +335,12 @@ If `1`, show timestamps in the plain text logs. If `0`, don't show the timestamp
**Default:** `1`.
## <a href="#MAX_THREADS" id="MAX_THREADS" name="MAX_THREADS">`MAX_THREADS`</a>
If greater than zero, sets the maximum number of threads for the Go runtime. If zero, the number remains the default one, which is 10 000. It must not be negative.
**Default:** `0`.
## <a href="#METRICS_NAMESPACE" id="METRICS_NAMESPACE" name="METRICS_NAMESPACE">`METRICS_NAMESPACE`</a>
The namespace to be used for Prometheus metrics. It must be a valid Prometheus metric label.
@@ -454,6 +484,18 @@ The path to the file into which the query log is going to be written.
**Default:** `./querylog.jsonl`.
## <a href="#QUERYLOG_SEMAPHORE_ENABLED" id="QUERYLOG_SEMAPHORE_ENABLED" name="QUERYLOG_SEMAPHORE_ENABLED">`QUERYLOG_SEMAPHORE_ENABLED`</a>
If `1`, enabled the querylog semaphore used to limit the parallelism of writing to the querylog and thus reducing the amount of OS threads that are created.
**Default:** `0`.
## <a href="#QUERYLOG_SEMAPHORE_LIMIT" id="QUERYLOG_SEMAPHORE_LIMIT" name="QUERYLOG_SEMAPHORE_LIMIT">`QUERYLOG_SEMAPHORE_LIMIT`</a>
The amount of writes to the querylog that can run in parallel.
**Default:** No default value, the variable is required if `QUERYLOG_SEMAPHORE_ENABLED` is set to `1`.
## <a href="#RATELIMIT_ALLOWLIST_TYPE" id="RATELIMIT_ALLOWLIST_TYPE" name="RATELIMIT_ALLOWLIST_TYPE">`RATELIMIT_ALLOWLIST_TYPE`</a>
Defines where the rate limit settings are received from. Allowed values are `backend` and `consul`.

76
go.mod
View File

@@ -1,12 +1,12 @@
module github.com/AdguardTeam/AdGuardDNS
go 1.24.6
go 1.25.1
require (
// NOTE: Do not change the pseudoversion.
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.0.0-00010101000000-000000000000
github.com/AdguardTeam/golibs v0.34.0
github.com/AdguardTeam/urlfilter v0.21.0
github.com/AdguardTeam/golibs v0.34.1
github.com/AdguardTeam/urlfilter v0.22.0
github.com/ameshkov/dnscrypt/v2 v2.4.0
github.com/axiomhq/hyperloglog v0.2.5
github.com/bluele/gcache v0.0.2
@@ -19,23 +19,23 @@ require (
github.com/miekg/dns v1.1.68
github.com/oschwald/maxminddb-golang v1.13.1
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
github.com/prometheus/client_golang v1.23.0
github.com/prometheus/client_golang v1.23.1
github.com/prometheus/client_model v0.6.2
github.com/prometheus/common v0.65.0
github.com/prometheus/common v0.66.0
github.com/quic-go/quic-go v0.54.0
github.com/stretchr/testify v1.11.1
github.com/viktordanov/golang-lru v0.5.6
golang.org/x/crypto v0.41.0
golang.org/x/net v0.43.0
golang.org/x/sys v0.35.0
golang.org/x/time v0.12.0
google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.8
golang.org/x/crypto v0.42.0
golang.org/x/net v0.44.0
golang.org/x/sys v0.36.0
golang.org/x/time v0.13.0
google.golang.org/grpc v1.75.1
google.golang.org/protobuf v1.36.9
gopkg.in/yaml.v2 v2.4.0
)
require (
cloud.google.com/go v0.121.6 // indirect
cloud.google.com/go v0.122.0 // indirect
cloud.google.com/go/ai v0.12.1 // indirect
cloud.google.com/go/auth v0.16.5 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
@@ -61,8 +61,9 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/gookit/color v1.6.0 // indirect
github.com/gordonklaus/ineffassign v0.2.0 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/jstemmer/go-junit-report/v2 v2.1.0 // indirect
github.com/kamstrup/intmap v0.5.1 // indirect
github.com/kisielk/errcheck v1.9.0 // indirect
@@ -77,35 +78,46 @@ require (
github.com/securego/gosec/v2 v2.22.8 // indirect
github.com/uudashr/gocognit v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.uber.org/mock v0.6.0 // indirect
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
golang.org/x/exp/typeparams v0.0.0-20250819193227-8b4c13bb791b // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/telemetry v0.0.0-20250822161441-f407b8c191ff // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/tools v0.36.0 // indirect
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/oauth2 v0.31.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.org/x/vuln v1.1.4 // indirect
google.golang.org/api v0.248.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
google.golang.org/api v0.249.0 // indirect
google.golang.org/genproto v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.6.1 // indirect
mvdan.cc/editorconfig v0.3.0 // indirect
mvdan.cc/gofumpt v0.8.0 // indirect
mvdan.cc/gofumpt v0.9.1 // indirect
mvdan.cc/sh/v3 v3.12.0 // indirect
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
)
// NOTE: Keep in sync with .gitignore.
ignore (
./bin/
./filters/
./github-mirror/
./test-reports/
./test/
./tmp/
)
tool (
github.com/fzipp/gocyclo/cmd/gocyclo
github.com/golangci/misspell/cmd/misspell

156
go.sum
View File

@@ -1,5 +1,5 @@
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
cloud.google.com/go v0.122.0 h1:0JTLGrcSIs3HIGsgVPvTx3cfyFSP/k9CI8vLPHTd6Wc=
cloud.google.com/go v0.122.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/ai v0.12.1 h1:m1n/VjUuHS+pEO/2R4/VbuuEIkgk0w67fDQvFaMngM0=
cloud.google.com/go/ai v0.12.1/go.mod h1:5vIPNe1ZQsVZqCliXIPL4QnhObQQY4d9hAGHdVc4iw4=
cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=
@@ -10,10 +10,10 @@ cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcao
cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=
cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
github.com/AdguardTeam/golibs v0.34.0 h1:JQK024DkTYxE7vsPVsYsoyDHW/53Nun7OYb9qscniK8=
github.com/AdguardTeam/golibs v0.34.0/go.mod h1:K4C2EbfSEM1zY5YXoti9SfbTAHN/kIX97LpDtCwORrM=
github.com/AdguardTeam/urlfilter v0.21.0 h1:ThIxiP7yoaXt8JTEroGQeU5ftQSoFpUq+t1L+TIx2pA=
github.com/AdguardTeam/urlfilter v0.21.0/go.mod h1:xoZ3AF5qpE9ngbbeSShY9hgVeyHtm9MdH5xH1u714Wg=
github.com/AdguardTeam/golibs v0.34.1 h1:RyBpZiXnJqlO3T+xjWldlxsEZDelmaFfKvXiJHDZZFQ=
github.com/AdguardTeam/golibs v0.34.1/go.mod h1:K4C2EbfSEM1zY5YXoti9SfbTAHN/kIX97LpDtCwORrM=
github.com/AdguardTeam/urlfilter v0.22.0 h1:ybOz3FywbpGDGC+8gFFkM1LMUOSosY7CWSBXIYXnG1U=
github.com/AdguardTeam/urlfilter v0.22.0/go.mod h1:q0lWKapXlYTA4TUWUM1YDwU6Q0PKvQEokztcvRV2OW0=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
@@ -57,8 +57,6 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
@@ -90,10 +88,14 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA=
github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs=
github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk=
github.com/jstemmer/go-junit-report/v2 v2.1.0 h1:X3+hPYlSczH9IMIpSC9CQSZA0L+BipYafciZUWHEmsc=
github.com/jstemmer/go-junit-report/v2 v2.1.0/go.mod h1:mgHVr7VUo5Tn8OLVr1cKnLuEy0M92wdRntM99h7RkgQ=
github.com/kamstrup/intmap v0.5.1 h1:ENGAowczZA+PJPYYlreoqJvWgQVtAmX1l899WfYFVK0=
@@ -108,8 +110,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -130,14 +130,12 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_golang v1.23.1 h1:w6gXMLQGgd0jXXlote9lRHMe0nG01EbnJT+C0EJru2Y=
github.com/prometheus/client_golang v1.23.1/go.mod h1:br8j//v2eg2K5Vvna5klK8Ku5pcU5r4ll73v6ik5dIQ=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY=
github.com/prometheus/common v0.66.0/go.mod h1:Ux6NtV1B4LatamKE63tJBntoxD++xmtI/lK0VtEplN4=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
@@ -150,74 +148,64 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/securego/gosec/v2 v2.22.8 h1:3NMpmfXO8wAVFZPNsd3EscOTa32Jyo6FLLlW53bexMI=
github.com/securego/gosec/v2 v2.22.8/go.mod h1:ZAw8K2ikuH9qDlfdV87JmNghnVfKB1XC7+TVzk6Utto=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
github.com/viktordanov/golang-lru v0.5.6 h1:wEyMgglEo5IZ7Maxeh8E2jCPskpQnt6FJAYl1/TJ6ac=
github.com/viktordanov/golang-lru v0.5.6/go.mod h1:R91CBCcMhp6TYUy8NHP/PJ09sk5BTDwb8KMO21CELes=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/auto/sdk v1.2.0 h1:YpRtUFjvhSymycLS2T81lT6IGhcUP+LUPtv0iv1N8bM=
go.opentelemetry.io/auto/sdk v1.2.0/go.mod h1:1deq2zL7rwjwC8mR7XgY2N+tlIl6pjmEUoLDENMEzwk=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/exp/typeparams v0.0.0-20250819193227-8b4c13bb791b h1:GU1ttDuJS89SePnuEsEuLj7dMMFP2JkGsDV1Z51iDXo=
golang.org/x/exp/typeparams v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20250822161441-f407b8c191ff h1:ey0rzo8V0Po6WkHMbhGVL6zNkuYDBe9iP5toIRchj9Q=
golang.org/x/telemetry v0.0.0-20250822161441-f407b8c191ff/go.mod h1:JIJwPkb04vX0KeIBbQ7epGtgIjA8ihHbsAtW4A/lIQ4=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621 h1:Yl4H5w2RV7L/dvSHp2GerziT5K2CORgFINPaMFxWGWw=
golang.org/x/exp/typeparams v0.0.0-20250911091902-df9299821621/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 h1:dHQOQddU4YHS5gY33/6klKjq7Gp3WwMyOXGNp5nzRj8=
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
@@ -226,20 +214,20 @@ golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.248.0 h1:hUotakSkcwGdYUqzCRc5yGYsg4wXxpkKlW5ryVqvC1Y=
google.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1 h1:APHvLLYBhtZvsbnpkfknDZ7NyH4z5+ub/I0u8L3Oz6g=
google.golang.org/genproto/googleapis/api v0.0.0-20250826171959-ef028d996bc1/go.mod h1:xUjFWUnWDpZ/C0Gu0qloASKFb6f8/QXiiXhSPFsD668=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/api v0.249.0 h1:0VrsWAKzIZi058aeq+I86uIXbNhm9GxSHpbmZ92a38w=
google.golang.org/api v0.249.0/go.mod h1:dGk9qyI0UYPwO/cjt2q06LG/EhUpwZGdAbYF14wHHrQ=
google.golang.org/genproto v0.0.0-20250908214217-97024824d090 h1:ywCL7vA2n3vVHyf+bx1ZV/knaTPRI8GIeKY0MEhEeOc=
google.golang.org/genproto v0.0.0-20250908214217-97024824d090/go.mod h1:zwJI9HzbJJlw2KXy0wX+lmT2JuZoaKK9JC4ppqmxxjk=
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:/OQuEa4YWtDt7uQWHd3q3sUMb+QOLQUg1xa8CEsRv5w=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -251,8 +239,8 @@ honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI=
honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
mvdan.cc/editorconfig v0.3.0 h1:D1D2wLYEYGpawWT5SpM5pRivgEgXjtEXwC9MWhEY0gQ=
mvdan.cc/editorconfig v0.3.0/go.mod h1:NcJHuDtNOTEJ6251indKiWuzK6+VcrMuLzGMLKBFupQ=
mvdan.cc/gofumpt v0.8.0 h1:nZUCeC2ViFaerTcYKstMmfysj6uhQrA2vJe+2vwGU6k=
mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg=
mvdan.cc/gofumpt v0.9.1 h1:p5YT2NfFWsYyTieYgwcQ8aKV3xRvFH4uuN/zB2gBbMQ=
mvdan.cc/gofumpt v0.9.1/go.mod h1:3xYtNemnKiXaTh6R4VtlqDATFwBbdXI8lJvH/4qk7mw=
mvdan.cc/sh/v3 v3.12.0 h1:ejKUR7ONP5bb+UGHGEG/k9V5+pRVIyD+LsZz7o8KHrI=
mvdan.cc/sh/v3 v3.12.0/go.mod h1:Se6Cj17eYSn+sNooLZiEUnNNmNxg0imoYlTu4CyaGyg=
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8=

View File

@@ -1,4 +1,4 @@
go 1.24.6
go 1.25.1
use (
.

View File

@@ -11,6 +11,7 @@ cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=
cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
@@ -31,21 +32,37 @@ cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eA
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
cloud.google.com/go/accessapproval v1.7.5/go.mod h1:g88i1ok5dvQ9XJsxpUInWWvUBrIZhyPDPbk4T01OoJ0=
cloud.google.com/go/accessapproval v1.8.7/go.mod h1:BFvZOW4GJjJnl6aA/YDEg0TGViFHyusa/bMdcVFmh8A=
cloud.google.com/go/accesscontextmanager v1.8.5/go.mod h1:TInEhcZ7V9jptGNqN3EzZ5XMhT6ijWxTGjzyETwmL0Q=
cloud.google.com/go/accesscontextmanager v1.9.6/go.mod h1:884XHwy1AQpCX5Cj2VqYse77gfLaq9f8emE2bYriilk=
cloud.google.com/go/aiplatform v1.60.0/go.mod h1:eTlGuHOahHprZw3Hio5VKmtThIOak5/qy6pzdsqcQnM=
cloud.google.com/go/aiplatform v1.100.0/go.mod h1:oZUOTz6+cMt9eVNe62CXPfIQQQ+QjR4rW3GBGD9r6Fg=
cloud.google.com/go/analytics v0.23.0/go.mod h1:YPd7Bvik3WS95KBok2gPXDqQPHy08TsCQG6CdUCb+u0=
cloud.google.com/go/analytics v0.30.0/go.mod h1:dneJtsGmmK6EkEPg59vRlncKFWt3xzmKNOc9aKXCTrI=
cloud.google.com/go/apigateway v1.6.5/go.mod h1:6wCwvYRckRQogyDDltpANi3zsCDl6kWi0b4Je+w2UiI=
cloud.google.com/go/apigateway v1.7.7/go.mod h1:j1bCmrUK1BzVHpiIyTApxB7cRyhivKzltqLmp6j6i7U=
cloud.google.com/go/apigeeconnect v1.6.5/go.mod h1:MEKm3AiT7s11PqTfKE3KZluZA9O91FNysvd3E6SJ6Ow=
cloud.google.com/go/apigeeconnect v1.7.7/go.mod h1:ftGK3nca0JePiVLl0A6alaMjKdOc5C+sAkFMyH2RH8U=
cloud.google.com/go/apigeeregistry v0.8.3/go.mod h1:aInOWnqF4yMQx8kTjDqHNXjZGh/mxeNlAf52YqtASUs=
cloud.google.com/go/apigeeregistry v0.9.6/go.mod h1:AFEepJBKPtGDfgabG2HWaLH453VVWWFFs3P4W00jbPs=
cloud.google.com/go/appengine v1.8.5/go.mod h1:uHBgNoGLTS5di7BvU25NFDuKa82v0qQLjyMJLuPQrVo=
cloud.google.com/go/appengine v1.9.7/go.mod h1:y1XpGVeAhbsNzHida79cHbr3pFRsym0ob8xnC8yphbo=
cloud.google.com/go/area120 v0.8.5/go.mod h1:BcoFCbDLZjsfe4EkCnEq1LKvHSK0Ew/zk5UFu6GMyA0=
cloud.google.com/go/area120 v0.9.7/go.mod h1:5nJ0yksmjOMfc4Zpk+okWfJ3A1004FvB82rfia+ZLaY=
cloud.google.com/go/artifactregistry v1.14.7/go.mod h1:0AUKhzWQzfmeTvT4SjfI4zjot72EMfrkvL9g9aRjnnM=
cloud.google.com/go/artifactregistry v1.17.1/go.mod h1:06gLv5QwQPWtaudI2fWO37gfwwRUHwxm3gA8Fe568Hc=
cloud.google.com/go/asset v1.17.2/go.mod h1:SVbzde67ehddSoKf5uebOD1sYw8Ab/jD/9EIeWg99q4=
cloud.google.com/go/asset v1.21.1/go.mod h1:7AzY1GCC+s1O73yzLM1IpHFLHz3ws2OigmCpOQHwebk=
cloud.google.com/go/assuredworkloads v1.11.5/go.mod h1:FKJ3g3ZvkL2D7qtqIGnDufFkHxwIpNM9vtmhvt+6wqk=
cloud.google.com/go/assuredworkloads v1.12.6/go.mod h1:QyZHd7nH08fmZ+G4ElihV1zoZ7H0FQCpgS0YWtwjCKo=
cloud.google.com/go/automl v1.13.5/go.mod h1:MDw3vLem3yh+SvmSgeYUmUKqyls6NzSumDm9OJ3xJ1Y=
cloud.google.com/go/automl v1.14.7/go.mod h1:8a4XbIH5pdvrReOU72oB+H3pOw2JBxo9XTk39oljObE=
cloud.google.com/go/baremetalsolution v1.2.4/go.mod h1:BHCmxgpevw9IEryE99HbYEfxXkAEA3hkMJbYYsHtIuY=
cloud.google.com/go/baremetalsolution v1.3.6/go.mod h1:7/CS0LzpLccRGO0HL3q2Rofxas2JwjREKut414sE9iM=
cloud.google.com/go/batch v1.8.0/go.mod h1:k8V7f6VE2Suc0zUM4WtoibNrA6D3dqBpB+++e3vSGYc=
cloud.google.com/go/batch v1.12.2/go.mod h1:tbnuTN/Iw59/n1yjAYKV2aZUjvMM2VJqAgvUgft6UEU=
cloud.google.com/go/beyondcorp v1.0.4/go.mod h1:Gx8/Rk2MxrvWfn4WIhHIG1NV7IBfg14pTKv1+EArVcc=
cloud.google.com/go/beyondcorp v1.1.6/go.mod h1:V1PigSWPGh5L/vRRmyutfnjAbkxLI2aWqJDdxKbwvsQ=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -54,13 +71,22 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/bigquery v1.59.1/go.mod h1:VP1UJYgevyTwsV7desjzNzDND5p6hZB+Z8gZJN1GQUc=
cloud.google.com/go/bigquery v1.70.0/go.mod h1:6lEAkgTJN+H2JcaX1eKiuEHTKyqBaJq5U3SpLGbSvwI=
cloud.google.com/go/bigtable v1.39.0/go.mod h1:zgL2Vxux9Bx+TcARDJDUxVyE+BCUfP2u4Zm9qeHF+g0=
cloud.google.com/go/billing v1.18.2/go.mod h1:PPIwVsOOQ7xzbADCwNe8nvK776QpfrOAUkvKjCUcpSE=
cloud.google.com/go/billing v1.20.4/go.mod h1:hBm7iUmGKGCnBm6Wp439YgEdt+OnefEq/Ib9SlJYxIU=
cloud.google.com/go/binaryauthorization v1.8.1/go.mod h1:1HVRyBerREA/nhI7yLang4Zn7vfNVA3okoAR9qYQJAQ=
cloud.google.com/go/binaryauthorization v1.9.5/go.mod h1:CV5GkS2eiY461Bzv+OH3r5/AsuB6zny+MruRju3ccB8=
cloud.google.com/go/certificatemanager v1.7.5/go.mod h1:uX+v7kWqy0Y3NG/ZhNvffh0kuqkKZIXdvlZRO7z0VtM=
cloud.google.com/go/certificatemanager v1.9.5/go.mod h1:kn7gxT/80oVGhjL8rurMUYD36AOimgtzSBPadtAeffs=
cloud.google.com/go/channel v1.17.5/go.mod h1:FlpaOSINDAXgEext0KMaBq/vwpLMkkPAw9b2mApQeHc=
cloud.google.com/go/channel v1.20.0/go.mod h1:nBR1Lz+/1TjSA16HTllvW9Y+QULODj3o3jEKrNNeOp4=
cloud.google.com/go/cloudbuild v1.15.1/go.mod h1:gIofXZSu+XD2Uy+qkOrGKEx45zd7s28u/k8f99qKals=
cloud.google.com/go/cloudbuild v1.23.0/go.mod h1:BkxnZUIHUHkl+oNpEbwc7n9id4pZRDQRVKIa6sDCuJI=
cloud.google.com/go/clouddms v1.7.4/go.mod h1:RdrVqoFG9RWI5AvZ81SxJ/xvxPdtcRhFotwdE79DieY=
cloud.google.com/go/clouddms v1.8.7/go.mod h1:DhWLd3nzHP8GoHkA6hOhso0R9Iou+IGggNqlVaq/KZ4=
cloud.google.com/go/cloudtasks v1.12.6/go.mod h1:b7c7fe4+TJsFZfDyzO51F7cjq7HLUlRi/KZQLQjDsaY=
cloud.google.com/go/cloudtasks v1.13.6/go.mod h1:/IDaQqGKMixD+ayM43CfsvWF2k36GeomEuy9gL4gLmU=
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk=
cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
@@ -70,6 +96,8 @@ cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1Yl
cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls=
cloud.google.com/go/compute v1.45.0 h1:bcq5kVYiC6O62afoM/rh40jnLpLUw6GP1O+8a8NiI+Y=
cloud.google.com/go/compute v1.45.0/go.mod h1:wQjjP1m9aYkZAPbYxilUyJ0RSAAb+/PFNGHBVLzDiRM=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
@@ -80,38 +108,68 @@ cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixA
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/contactcenterinsights v1.13.0/go.mod h1:ieq5d5EtHsu8vhe2y3amtZ+BE+AQwX5qAy7cpo0POsI=
cloud.google.com/go/contactcenterinsights v1.17.3/go.mod h1:7Uu2CpxS3f6XxhRdlEzYAkrChpR5P5QfcdGAFEdHOG8=
cloud.google.com/go/container v1.31.0/go.mod h1:7yABn5s3Iv3lmw7oMmyGbeV6tQj86njcTijkkGuvdZA=
cloud.google.com/go/container v1.44.0/go.mod h1:tVK2o4UZUTkg9WpBcgj4qRzwGA1dSFdWA3mil3YkLIQ=
cloud.google.com/go/containeranalysis v0.11.4/go.mod h1:cVZT7rXYBS9NG1rhQbWL9pWbXCKHWJPYraE8/FTSYPE=
cloud.google.com/go/containeranalysis v0.14.1/go.mod h1:28e+tlZgauWGHmEbnI5UfIsjMmrkoR1tFN0K2i71jBI=
cloud.google.com/go/datacatalog v1.19.3/go.mod h1:ra8V3UAsciBpJKQ+z9Whkxzxv7jmQg1hfODr3N3YPJ4=
cloud.google.com/go/datacatalog v1.26.0/go.mod h1:bLN2HLBAwB3kLTFT5ZKLHVPj/weNz6bR0c7nYp0LE14=
cloud.google.com/go/dataflow v0.9.5/go.mod h1:udl6oi8pfUHnL0z6UN9Lf9chGqzDMVqcYTcZ1aPnCZQ=
cloud.google.com/go/dataflow v0.11.0/go.mod h1:gNHC9fUjlV9miu0hd4oQaXibIuVYTQvZhMdPievKsPk=
cloud.google.com/go/dataform v0.9.2/go.mod h1:S8cQUwPNWXo7m/g3DhWHsLBoufRNn9EgFrMgne2j7cI=
cloud.google.com/go/dataform v0.12.0/go.mod h1:PuDIEY0lSVuPrZqcFji1fmr5RRvz3DGz4YP/cONc8g4=
cloud.google.com/go/datafusion v1.7.5/go.mod h1:bYH53Oa5UiqahfbNK9YuYKteeD4RbQSNMx7JF7peGHc=
cloud.google.com/go/datafusion v1.8.6/go.mod h1:fCyKJF2zUKC+O3hc2F9ja5EUCAbT4zcH692z8HiFZFw=
cloud.google.com/go/datalabeling v0.8.5/go.mod h1:IABB2lxQnkdUbMnQaOl2prCOfms20mcPxDBm36lps+s=
cloud.google.com/go/datalabeling v0.9.6/go.mod h1:n7o4x0vtPensZOoFwFa4UfZgkSZm8Qs0Pg/T3kQjXSM=
cloud.google.com/go/dataplex v1.14.2/go.mod h1:0oGOSFlEKef1cQeAHXy4GZPB/Ife0fz/PxBf+ZymA2U=
cloud.google.com/go/dataplex v1.26.0/go.mod h1:12R9nlLUzxOscbb2HgoYnkGNibmv4sXEVMXxrdw2a90=
cloud.google.com/go/dataproc/v2 v2.4.0/go.mod h1:3B1Ht2aRB8VZIteGxQS/iNSJGzt9+CA0WGnDVMEm7Z4=
cloud.google.com/go/dataproc/v2 v2.14.0/go.mod h1:AqfdObN5w70H7meRXZOEY52WMK4yMrLtiOd9kROahSM=
cloud.google.com/go/dataqna v0.8.5/go.mod h1:vgihg1mz6n7pb5q2YJF7KlXve6tCglInd6XO0JGOlWM=
cloud.google.com/go/dataqna v0.9.7/go.mod h1:4ac3r7zm7Wqm8NAc8sDIDM0v7Dz7d1e/1Ka1yMFanUM=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/datastore v1.15.0/go.mod h1:GAeStMBIt9bPS7jMJA85kgkpsMkvseWWXiaHya9Jes8=
cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew=
cloud.google.com/go/datastream v1.10.4/go.mod h1:7kRxPdxZxhPg3MFeCSulmAJnil8NJGGvSNdn4p1sRZo=
cloud.google.com/go/datastream v1.15.0/go.mod h1:eA4ZWd7e21YtG6Yx5SWSwRV5U9wbAb9rKHTcb0x20cQ=
cloud.google.com/go/deploy v1.17.1/go.mod h1:SXQyfsXrk0fBmgBHRzBjQbZhMfKZ3hMQBw5ym7MN/50=
cloud.google.com/go/deploy v1.27.2/go.mod h1:4NHWE7ENry2A4O1i/4iAPfXHnJCZ01xckAKpZQwhg1M=
cloud.google.com/go/dialogflow v1.49.0/go.mod h1:dhVrXKETtdPlpPhE7+2/k4Z8FRNUp6kMV3EW3oz/fe0=
cloud.google.com/go/dialogflow v1.69.0/go.mod h1:+2drAzrguQ8vltf6qn6foBPHrT/fFa1S3FQ40byV2WU=
cloud.google.com/go/dlp v1.11.2/go.mod h1:9Czi+8Y/FegpWzgSfkRlyz+jwW6Te9Rv26P3UfU/h/w=
cloud.google.com/go/dlp v1.24.0/go.mod h1:y6EsWNgMDye72NtqjGHYZjN/wUDnO9CUygLV8iuFeW0=
cloud.google.com/go/documentai v1.25.0/go.mod h1:ftLnzw5VcXkLItp6pw1mFic91tMRyfv6hHEY5br4KzY=
cloud.google.com/go/documentai v1.38.0/go.mod h1:zNhZmHJ4/VbvhA0h2U5JRbOHm2BTMq4FxJ276mYAohk=
cloud.google.com/go/domains v0.9.5/go.mod h1:dBzlxgepazdFhvG7u23XMhmMKBjrkoUNaw0A8AQB55Y=
cloud.google.com/go/domains v0.10.6/go.mod h1:3xzG+hASKsVBA8dOPc4cIaoV3OdBHl1qgUpAvXK7pGY=
cloud.google.com/go/edgecontainer v1.1.5/go.mod h1:rgcjrba3DEDEQAidT4yuzaKWTbkTI5zAMu3yy6ZWS0M=
cloud.google.com/go/edgecontainer v1.4.3/go.mod h1:q9Ojw2ox0uhAvFisnfPRAXFTB1nfRIOIXVWzdXMZLcE=
cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=
cloud.google.com/go/errorreporting v0.3.2/go.mod h1:s5kjs5r3l6A8UUyIsgvAhGq6tkqyBCUss0FRpsoVTww=
cloud.google.com/go/essentialcontacts v1.6.6/go.mod h1:XbqHJGaiH0v2UvtuucfOzFXN+rpL/aU5BCZLn4DYl1Q=
cloud.google.com/go/essentialcontacts v1.7.6/go.mod h1:/Ycn2egr4+XfmAfxpLYsJeJlVf9MVnq9V7OMQr9R4lA=
cloud.google.com/go/eventarc v1.13.4/go.mod h1:zV5sFVoAa9orc/52Q+OuYUG9xL2IIZTbbuTHC6JSY8s=
cloud.google.com/go/eventarc v1.15.5/go.mod h1:vDCqGqyY7SRiickhEGt1Zhuj81Ya4F/NtwwL3OZNskg=
cloud.google.com/go/filestore v1.8.1/go.mod h1:MbN9KcaM47DRTIuLfQhJEsjaocVebNtNQhSLhKCF5GM=
cloud.google.com/go/filestore v1.10.2/go.mod h1:w0Pr8uQeSRQfCPRsL0sYKW6NKyooRgixCkV9yyLykR4=
cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ=
cloud.google.com/go/firestore v1.18.0/go.mod h1:5ye0v48PhseZBdcl0qbl3uttu7FIEwEYVaWm0UIEOEU=
cloud.google.com/go/functions v1.16.0/go.mod h1:nbNpfAG7SG7Duw/o1iZ6ohvL7mc6MapWQVpqtM29n8k=
cloud.google.com/go/functions v1.19.6/go.mod h1:0G0RnIlbM4MJEycfbPZlCzSf2lPOjL7toLDwl+r0ZBw=
cloud.google.com/go/gkebackup v1.3.5/go.mod h1:KJ77KkNN7Wm1LdMopOelV6OodM01pMuK2/5Zt1t4Tvc=
cloud.google.com/go/gkebackup v1.8.0/go.mod h1:FjsjNldDilC9MWKEHExnK3kKJyTDaSdO1vF0QeWSOPU=
cloud.google.com/go/gkeconnect v0.8.5/go.mod h1:LC/rS7+CuJ5fgIbXv8tCD/mdfnlAadTaUufgOkmijuk=
cloud.google.com/go/gkeconnect v0.12.4/go.mod h1:bvpU9EbBpZnXGo3nqJ1pzbHWIfA9fYqgBMJ1VjxaZdk=
cloud.google.com/go/gkehub v0.14.5/go.mod h1:6bzqxM+a+vEH/h8W8ec4OJl4r36laxTs3A/fMNHJ0wA=
cloud.google.com/go/gkehub v0.15.6/go.mod h1:sRT0cOPAgI1jUJrS3gzwdYCJ1NEzVVwmnMKEwrS2QaM=
cloud.google.com/go/gkemulticloud v1.1.1/go.mod h1:C+a4vcHlWeEIf45IB5FFR5XGjTeYhF83+AYIpTy4i2Q=
cloud.google.com/go/gkemulticloud v1.5.3/go.mod h1:KPFf+/RcfvmuScqwS9/2MF5exZAmXSuoSLPuaQ98Xlk=
cloud.google.com/go/gsuiteaddons v1.6.5/go.mod h1:Lo4P2IvO8uZ9W+RaC6s1JVxo42vgy+TX5a6hfBZ0ubs=
cloud.google.com/go/gsuiteaddons v1.7.7/go.mod h1:zTGmmKG/GEBCONsvMOY2ckDiEsq3FN+lzWGUiXccF9o=
cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI=
cloud.google.com/go/iam v1.4.0/go.mod h1:gMBgqPaERlriaOV0CUl//XUzDhSfXevn4OEUbg6VRs4=
cloud.google.com/go/iam v1.4.1 h1:cFC25Nv+u5BkTR/BT1tXdoF2daiVbZ1RLx2eqfQ9RMM=
@@ -119,34 +177,58 @@ cloud.google.com/go/iam v1.4.1/go.mod h1:2vUEJpUG3Q9p2UdsyksaKpDzlwOrnMzS30isdRe
cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
cloud.google.com/go/iap v1.9.4/go.mod h1:vO4mSq0xNf/Pu6E5paORLASBwEmphXEjgCFg7aeNu1w=
cloud.google.com/go/iap v1.11.2/go.mod h1:Bh99DMUpP5CitL9lK0BC8MYgjjYO4b3FbyhgW1VHJvg=
cloud.google.com/go/ids v1.4.5/go.mod h1:p0ZnyzjMWxww6d2DvMGnFwCsSxDJM666Iir1bK1UuBo=
cloud.google.com/go/ids v1.5.6/go.mod h1:y3SGLmEf9KiwKsH7OHvYYVNIJAtXybqsD2z8gppsziQ=
cloud.google.com/go/iot v1.7.5/go.mod h1:nq3/sqTz3HGaWJi1xNiX7F41ThOzpud67vwk0YsSsqs=
cloud.google.com/go/iot v1.8.6/go.mod h1:MThnkiihNkMysWNeNje2Hp0GSOpEq2Wkb/DkBCVYa0U=
cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI=
cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8=
cloud.google.com/go/language v1.12.3/go.mod h1:evFX9wECX6mksEva8RbRnr/4wi/vKGYnAJrTRXU8+f8=
cloud.google.com/go/language v1.14.5/go.mod h1:nl2cyAVjcBct1Hk73tzxuKebk0t2eULFCaruhetdZIA=
cloud.google.com/go/lifesciences v0.9.5/go.mod h1:OdBm0n7C0Osh5yZB7j9BXyrMnTRGBJIZonUMxo5CzPw=
cloud.google.com/go/lifesciences v0.10.6/go.mod h1:1nnZwaZcBThDujs9wXzECnd1S5d+UiDkPuJWAmhRi7Q=
cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
cloud.google.com/go/managedidentities v1.6.5/go.mod h1:fkFI2PwwyRQbjLxlm5bQ8SjtObFMW3ChBGNqaMcgZjI=
cloud.google.com/go/managedidentities v1.7.6/go.mod h1:pYCWPaI1AvR8Q027Vtp+SFSM/VOVgbjBF4rxp1/z5p4=
cloud.google.com/go/maps v1.6.4/go.mod h1:rhjqRy8NWmDJ53saCfsXQ0LKwBHfi6OSh5wkq6BaMhI=
cloud.google.com/go/maps v1.23.0/go.mod h1:8tjxLplMV7FEoR9FIwqoY7siDnaOdE7FBWnjaXK/xts=
cloud.google.com/go/mediatranslation v0.8.5/go.mod h1:y7kTHYIPCIfgyLbKncgqouXJtLsU+26hZhHEEy80fSs=
cloud.google.com/go/mediatranslation v0.9.6/go.mod h1:WS3QmObhRtr2Xu5laJBQSsjnWFPPthsyetlOyT9fJvE=
cloud.google.com/go/memcache v1.10.5/go.mod h1:/FcblbNd0FdMsx4natdj+2GWzTq+cjZvMa1I+9QsuMA=
cloud.google.com/go/memcache v1.11.6/go.mod h1:ZM6xr1mw3F8TWO+In7eq9rKlJc3jlX2MDt4+4H+/+cc=
cloud.google.com/go/metastore v1.13.4/go.mod h1:FMv9bvPInEfX9Ac1cVcRXp8EBBQnBcqH6gz3KvJ9BAE=
cloud.google.com/go/metastore v1.14.7/go.mod h1:0dka99KQofeUgdfu+K/Jk1KeT9veWZlxuZdJpZPtuYU=
cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg=
cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM=
cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc=
cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
cloud.google.com/go/networkconnectivity v1.14.4/go.mod h1:PU12q++/IMnDJAB+3r+tJtuCXCfwfN+C6Niyj6ji1Po=
cloud.google.com/go/networkconnectivity v1.18.0/go.mod h1:8MFjpAsCqTKUO+U5y9C6iGAsq2KkrfpQ43/XbqSbICc=
cloud.google.com/go/networkmanagement v1.9.4/go.mod h1:daWJAl0KTFytFL7ar33I6R/oNBH8eEOX/rBNHrC/8TA=
cloud.google.com/go/networkmanagement v1.20.0/go.mod h1:t/GQe1ICzaxeETse/6EPEjmjOr9zGyNImVLlxAX+YB4=
cloud.google.com/go/networksecurity v0.9.5/go.mod h1:KNkjH/RsylSGyyZ8wXpue8xpCEK+bTtvof8SBfIhMG8=
cloud.google.com/go/networksecurity v0.10.6/go.mod h1:FTZvabFPvK2kR/MRIH3l/OoQ/i53eSix2KA1vhBMJec=
cloud.google.com/go/notebooks v1.11.3/go.mod h1:0wQyI2dQC3AZyQqWnRsp+yA+kY4gC7ZIVP4Qg3AQcgo=
cloud.google.com/go/notebooks v1.12.6/go.mod h1:3Z4TMEqAKP3pu6DI/U+aEXrNJw9hGZIVbp+l3zw8EuA=
cloud.google.com/go/optimization v1.6.3/go.mod h1:8ve3svp3W6NFcAEFr4SfJxrldzhUl4VMUJmhrqVKtYA=
cloud.google.com/go/optimization v1.7.6/go.mod h1:4MeQslrSJGv+FY4rg0hnZBR/tBX2awJ1gXYp6jZpsYY=
cloud.google.com/go/orchestration v1.8.5/go.mod h1:C1J7HesE96Ba8/hZ71ISTV2UAat0bwN+pi85ky38Yq8=
cloud.google.com/go/orchestration v1.11.9/go.mod h1:KKXK67ROQaPt7AxUS1V/iK0Gs8yabn3bzJ1cLHw4XBg=
cloud.google.com/go/orgpolicy v1.12.1/go.mod h1:aibX78RDl5pcK3jA8ysDQCFkVxLj3aOQqrbBaUL2V5I=
cloud.google.com/go/orgpolicy v1.15.0/go.mod h1:NTQLwgS8N5cJtdfK55tAnMGtvPSsy95JJhESwYHaJVs=
cloud.google.com/go/osconfig v1.12.5/go.mod h1:D9QFdxzfjgw3h/+ZaAb5NypM8bhOMqBzgmbhzWViiW8=
cloud.google.com/go/osconfig v1.15.0/go.mod h1:0nY8bfGKWJB0Ft5bBKd2zMkjT4Uf0rM3NBFrAGUv1Lk=
cloud.google.com/go/oslogin v1.13.1/go.mod h1:vS8Sr/jR7QvPWpCjNqy6LYZr5Zs1e8ZGW/KPn9gmhws=
cloud.google.com/go/oslogin v1.14.6/go.mod h1:xEvcRZTkMXHfNSKdZ8adxD6wvRzeyAq3cQX3F3kbMRw=
cloud.google.com/go/phishingprotection v0.8.5/go.mod h1:g1smd68F7mF1hgQPuYn3z8HDbNre8L6Z0b7XMYFmX7I=
cloud.google.com/go/phishingprotection v0.9.6/go.mod h1:VmuGg03DCI0wRp/FLSvNyjFj+J8V7+uITgHjCD/x4RQ=
cloud.google.com/go/policytroubleshooter v1.10.3/go.mod h1:+ZqG3agHT7WPb4EBIRqUv4OyIwRTZvsVDHZ8GlZaoxk=
cloud.google.com/go/policytroubleshooter v1.11.6/go.mod h1:jdjYGIveoYolk38Dm2JjS5mPkn8IjVqPsDHccTMu3mY=
cloud.google.com/go/privatecatalog v0.9.5/go.mod h1:fVWeBOVe7uj2n3kWRGlUQqR/pOd450J9yZoOECcQqJk=
cloud.google.com/go/privatecatalog v0.10.7/go.mod h1:Fo/PF/B6m4A9vUYt0nEF1xd0U6Kk19/Je3eZGrQ6l60=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -154,24 +236,43 @@ cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w=
cloud.google.com/go/pubsub v1.36.1/go.mod h1:iYjCa9EzWOoBiTdd4ps7QoMtMln5NwaZQpK1hbRfBDE=
cloud.google.com/go/pubsub v1.50.1/go.mod h1:6YVJv3MzWJUVdvQXG081sFvS0dWQOdnV+oTo++q/xFk=
cloud.google.com/go/pubsub/v2 v2.0.0/go.mod h1:0aztFxNzVQIRSZ8vUr79uH2bS3jwLebwK6q1sgEub+E=
cloud.google.com/go/pubsublite v1.8.1/go.mod h1:fOLdU4f5xldK4RGJrBMm+J7zMWNj/k4PxwEZXy39QS0=
cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI=
cloud.google.com/go/recaptchaenterprise/v2 v2.9.2/go.mod h1:trwwGkfhCmp05Ll5MSJPXY7yvnO0p4v3orGANAFHAuU=
cloud.google.com/go/recaptchaenterprise/v2 v2.20.4/go.mod h1:3H8nb8j8N7Ss2eJ+zr+/H7gyorfzcxiDEtVBDvDjwDQ=
cloud.google.com/go/recommendationengine v0.8.5/go.mod h1:A38rIXHGFvoPvmy6pZLozr0g59NRNREz4cx7F58HAsQ=
cloud.google.com/go/recommendationengine v0.9.6/go.mod h1:nZnjKJu1vvoxbmuRvLB5NwGuh6cDMMQdOLXTnkukUOE=
cloud.google.com/go/recommender v1.12.1/go.mod h1:gf95SInWNND5aPas3yjwl0I572dtudMhMIG4ni8nr+0=
cloud.google.com/go/recommender v1.13.5/go.mod h1:v7x/fzk38oC62TsN5Qkdpn0eoMBh610UgArJtDIgH/E=
cloud.google.com/go/redis v1.14.2/go.mod h1:g0Lu7RRRz46ENdFKQ2EcQZBAJ2PtJHJLuiiRuEXwyQw=
cloud.google.com/go/redis v1.18.2/go.mod h1:q6mPRhLiR2uLf584Lcl4tsiRn0xiFlu6fnJLwCORMtY=
cloud.google.com/go/resourcemanager v1.9.5/go.mod h1:hep6KjelHA+ToEjOfO3garMKi/CLYwTqeAw7YiEI9x8=
cloud.google.com/go/resourcemanager v1.10.6/go.mod h1:VqMoDQ03W4yZmxzLPrB+RuAoVkHDS5tFUUQUhOtnRTg=
cloud.google.com/go/resourcesettings v1.6.5/go.mod h1:WBOIWZraXZOGAgoR4ukNj0o0HiSMO62H9RpFi9WjP9I=
cloud.google.com/go/resourcesettings v1.8.3/go.mod h1:BzgfXFHIWOOmHe6ZV9+r3OWfpHJgnqXy8jqwx4zTMLw=
cloud.google.com/go/retail v1.16.0/go.mod h1:LW7tllVveZo4ReWt68VnldZFWJRzsh9np+01J9dYWzE=
cloud.google.com/go/retail v1.24.0/go.mod h1:pvLFfRzTnqGf3yHNnIq4R+A5nfEy56SYE9optVPOuSk=
cloud.google.com/go/run v1.3.4/go.mod h1:FGieuZvQ3tj1e9GnzXqrMABSuir38AJg5xhiYq+SF3o=
cloud.google.com/go/run v1.12.0/go.mod h1:/APJ89UqgGdIdaD1yaTiSYXozx3fNoqKR/cueDFRueI=
cloud.google.com/go/scheduler v1.10.6/go.mod h1:pe2pNCtJ+R01E06XCDOJs1XvAMbv28ZsQEbqknxGOuE=
cloud.google.com/go/scheduler v1.11.7/go.mod h1:gqYs8ndLx2M5D0oMJh48aGS630YYvC432tHCnVWN13s=
cloud.google.com/go/secretmanager v1.11.5/go.mod h1:eAGv+DaCHkeVyQi0BeXgAHOU0RdrMeZIASKc+S7VqH4=
cloud.google.com/go/secretmanager v1.15.0/go.mod h1:1hQSAhKK7FldiYw//wbR/XPfPc08eQ81oBsnRUHEvUc=
cloud.google.com/go/security v1.15.5/go.mod h1:KS6X2eG3ynWjqcIX976fuToN5juVkF6Ra6c7MPnldtc=
cloud.google.com/go/security v1.19.1/go.mod h1:+T4yyeDXqBYESnCzswqbq/Oip+IYkIrTfRF4UmeT4Bk=
cloud.google.com/go/securitycenter v1.24.4/go.mod h1:PSccin+o1EMYKcFQzz9HMMnZ2r9+7jbc+LvPjXhpwcU=
cloud.google.com/go/securitycenter v1.37.0/go.mod h1:DdQi6OEzw1rmLtPpqtUx6bqnQq8ZdCVuG9eZRYz2QAE=
cloud.google.com/go/servicedirectory v1.11.4/go.mod h1:Bz2T9t+/Ehg6x+Y7Ycq5xiShYLD96NfEsWNHyitj1qM=
cloud.google.com/go/servicedirectory v1.12.6/go.mod h1:OojC1KhOMDYC45oyTn3Mup08FY/S0Kj7I58dxUMMTpg=
cloud.google.com/go/shell v1.7.5/go.mod h1:hL2++7F47/IfpfTO53KYf1EC+F56k3ThfNEXd4zcuiE=
cloud.google.com/go/shell v1.8.6/go.mod h1:GNbTWf1QA/eEtYa+kWSr+ef/XTCDkUzRpV3JPw0LqSk=
cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk=
cloud.google.com/go/spanner v1.56.0/go.mod h1:DndqtUKQAt3VLuV2Le+9Y3WTnq5cNKrnLb/Piqcj+h0=
cloud.google.com/go/spanner v1.85.0/go.mod h1:9zhmtOEoYV06nE4Orbin0dc/ugHzZW9yXuvaM61rpxs=
cloud.google.com/go/speech v1.21.1/go.mod h1:E5GHZXYQlkqWQwY5xRSLHw2ci5NMQNG52FfMU1aZrIA=
cloud.google.com/go/speech v1.28.0/go.mod h1:hJf6oa+1rzCW/CeDE/qCXedV20B2TXEUje5iaGwW+JI=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
@@ -186,22 +287,37 @@ cloud.google.com/go/storage v1.53.0/go.mod h1:7/eO2a/srr9ImZW9k5uufcNahT2+fPb8w5
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU=
cloud.google.com/go/storagetransfer v1.10.4/go.mod h1:vef30rZKu5HSEf/x1tK3WfWrL0XVoUQN/EPDRGPzjZs=
cloud.google.com/go/storagetransfer v1.13.0/go.mod h1:+aov7guRxXBYgR3WCqedkyibbTICdQOiXOdpPcJCKl8=
cloud.google.com/go/talent v1.6.6/go.mod h1:y/WQDKrhVz12WagoarpAIyKKMeKGKHWPoReZ0g8tseQ=
cloud.google.com/go/talent v1.8.3/go.mod h1:oD3/BilJpJX8/ad8ZUAxlXHCslTg2YBbafFH3ciZSLQ=
cloud.google.com/go/texttospeech v1.7.5/go.mod h1:tzpCuNWPwrNJnEa4Pu5taALuZL4QRRLcb+K9pbhXT6M=
cloud.google.com/go/texttospeech v1.14.0/go.mod h1:l25ywjIgXS+mSE2f5LQdXdU7r3MOLwVOGaYZQMiYIWE=
cloud.google.com/go/tpu v1.6.5/go.mod h1:P9DFOEBIBhuEcZhXi+wPoVy/cji+0ICFi4TtTkMHSSs=
cloud.google.com/go/tpu v1.8.3/go.mod h1:Do6Gq+/Jx6Xs3LcY2WhHyGwKDKVw++9jIJp+X+0rxRE=
cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M=
cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
cloud.google.com/go/translate v1.10.1/go.mod h1:adGZcQNom/3ogU65N9UXHOnnSvjPwA/jKQUMnsYXOyk=
cloud.google.com/go/translate v1.10.3 h1:g+B29z4gtRGsiKDoTF+bNeH25bLRokAaElygX2FcZkE=
cloud.google.com/go/translate v1.10.3/go.mod h1:GW0vC1qvPtd3pgtypCv4k4U8B7EdgK9/QEF2aJEUovs=
cloud.google.com/go/translate v1.12.6/go.mod h1:nB3AXuX+iHbV8ZURmElcW85qkEDWZw68sf4kqMT/E5o=
cloud.google.com/go/video v1.20.4/go.mod h1:LyUVjyW+Bwj7dh3UJnUGZfyqjEto9DnrvTe1f/+QrW0=
cloud.google.com/go/video v1.26.0/go.mod h1:iqsrblPUfkxvyH31rnS02Z0dp9p5lySdq7+I0XzozQI=
cloud.google.com/go/videointelligence v1.11.5/go.mod h1:/PkeQjpRponmOerPeJxNPuxvi12HlW7Em0lJO14FC3I=
cloud.google.com/go/videointelligence v1.12.6/go.mod h1:/l34WMndN5/bt04lHodxiYchLVuWPQjCU6SaiTswrIw=
cloud.google.com/go/vision/v2 v2.8.0/go.mod h1:ocqDiA2j97pvgogdyhoxiQp2ZkDCyr0HWpicywGGRhU=
cloud.google.com/go/vision/v2 v2.9.5/go.mod h1:1SiNZPpypqZDbOzU052ZYRiyKjwOcyqgGgqQCI/nlx8=
cloud.google.com/go/vmmigration v1.7.5/go.mod h1:pkvO6huVnVWzkFioxSghZxIGcsstDvYiVCxQ9ZH3eYI=
cloud.google.com/go/vmmigration v1.8.6/go.mod h1:uZ6/KXmekwK3JmC8PzBM/cKQmq404TTfWtThF6bbf0U=
cloud.google.com/go/vmwareengine v1.1.1/go.mod h1:nMpdsIVkUrSaX8UvmnBhzVzG7PPvNYc5BszcvIVudYs=
cloud.google.com/go/vmwareengine v1.3.5/go.mod h1:QuVu2/b/eo8zcIkxBYY5QSwiyEcAy6dInI7N+keI+Jg=
cloud.google.com/go/vpcaccess v1.7.5/go.mod h1:slc5ZRvvjP78c2dnL7m4l4R9GwL3wDLcpIWz6P/ziig=
cloud.google.com/go/vpcaccess v1.8.6/go.mod h1:61yymNplV1hAbo8+kBOFO7Vs+4ZHYI244rSFgmsHC6E=
cloud.google.com/go/webrisk v1.9.5/go.mod h1:aako0Fzep1Q714cPEM5E+mtYX8/jsfegAuS8aivxy3U=
cloud.google.com/go/webrisk v1.11.1/go.mod h1:+9SaepGg2lcp1p0pXuHyz3R2Yi2fHKKb4c1Q9y0qbtA=
cloud.google.com/go/websecurityscanner v1.6.5/go.mod h1:QR+DWaxAz2pWooylsBF854/Ijvuoa3FCyS1zBa1rAVQ=
cloud.google.com/go/websecurityscanner v1.7.6/go.mod h1:ucaaTO5JESFn5f2pjdX01wGbQ8D6h79KHrmO2uGZeiY=
cloud.google.com/go/workflows v1.12.4/go.mod h1:yQ7HUqOkdJK4duVtMeBCAOPiN1ZF1E9pAMX51vpwB/w=
cloud.google.com/go/workflows v1.14.2/go.mod h1:5nqKjMD+MsJs41sJhdVrETgvD5cOK3hUcAs8ygqYvXQ=
contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3 h1:hJiie5Bf3QucGRa4ymsAUOxyhYwGEz1xrsVk0P8erlw=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
@@ -234,8 +350,6 @@ github.com/AdguardTeam/golibs v0.32.11/go.mod h1:LXr0gqqZuVpt+L+bP3Nnr0/CecLmm3r
github.com/AdguardTeam/gomitmproxy v0.2.0 h1:rvCOf17pd1/CnMyMQW891zrEiIQBpQ8cIGjKN9pinUU=
github.com/AdguardTeam/gomitmproxy v0.2.1 h1:p9gr8Er1TYvf+7ic81Ax1sZ62UNCsMTZNbm7tC59S9o=
github.com/AdguardTeam/gomitmproxy v0.2.1/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.21.0 h1:ThIxiP7yoaXt8JTEroGQeU5ftQSoFpUq+t1L+TIx2pA=
github.com/AdguardTeam/urlfilter v0.21.0/go.mod h1:xoZ3AF5qpE9ngbbeSShY9hgVeyHtm9MdH5xH1u714Wg=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
@@ -370,6 +484,7 @@ github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
@@ -461,6 +576,7 @@ github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
@@ -1174,6 +1290,7 @@ go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzK
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=

View File

@@ -139,7 +139,9 @@ func BenchmarkGlobal_IsBlockedHost(b *testing.B) {
for _, tc := range testCases {
b.Run(tc.name, func(b *testing.B) {
var blocked bool
// Warmup to fill the pools and the slices.
blocked := global.IsBlockedHost(tc.host, tc.qt)
tc.want(b, blocked)
b.ReportAllocs()
for b.Loop() {
@@ -151,19 +153,18 @@ func BenchmarkGlobal_IsBlockedHost(b *testing.B) {
}
// Most recent results:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/access
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkGlobal_IsBlockedHost/pass-16 2313513 515.0 ns/op 16 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/blocked_domain_a-16 1604049 683.4 ns/op 24 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/blocked_domain_https-16 1981204 597.7 ns/op 24 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/uppercase_domain-16 2093197 590.5 ns/op 24 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/pass_qt-16 1961065 653.3 ns/op 24 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/block_qt-16 768783 1567 ns/op 24 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/allowlist_block-16 759159 1890 ns/op 32 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/allowlist_test-16 371722 3170 ns/op 32 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/pass-16 3085917 384.7 ns/op 16 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/blocked_domain_a-16 2521102 475.8 ns/op 24 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/blocked_domain_https-16 2520067 476.0 ns/op 24 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/uppercase_domain-16 2445049 490.7 ns/op 24 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/pass_qt-16 2228846 535.7 ns/op 24 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/block_qt-16 822028 1375 ns/op 24 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/allowlist_block-16 765939 1530 ns/op 32 B/op 1 allocs/op
// BenchmarkGlobal_IsBlockedHost/allowlist_test-16 448230 2677 ns/op 32 B/op 1 allocs/op
}
func BenchmarkGlobal_IsBlockedIP(b *testing.B) {
@@ -199,11 +200,10 @@ func BenchmarkGlobal_IsBlockedIP(b *testing.B) {
})
// Most recent results:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/access
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkGlobal_IsBlockedIP/pass-16 100000000 10.18 ns/op 0 B/op 0 allocs/op
// BenchmarkGlobal_IsBlockedIP/block-16 141876058 8.545 ns/op 0 B/op 0 allocs/op
// BenchmarkGlobal_IsBlockedIP/pass-16 152113686 7.893 ns/op 0 B/op 0 allocs/op
// BenchmarkGlobal_IsBlockedIP/block-16 156828942 7.659 ns/op 0 B/op 0 allocs/op
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// testTimeout is the common timeout for tests.
@@ -95,16 +96,12 @@ func TestBlockedHostEngine_IsBlocked_concurrent(t *testing.T) {
wg := &sync.WaitGroup{}
for i := range routinesLimit {
wg.Add(1)
host := fmt.Sprintf("%d.%s", i, "block.test")
go func() {
defer wg.Done()
wg.Go(func() {
req := dnsservertest.NewReq(host, dns.TypeA, dns.ClassINET)
assert.True(t, engine.isBlocked(testutil.ContextWithTimeout(t, testTimeout), req))
}()
})
}
wg.Wait()
@@ -120,35 +117,38 @@ func BenchmarkBlockedHostEngine_IsBlocked(b *testing.B) {
b.Run("pass", func(b *testing.B) {
req := dnsservertest.NewReq("pass.test", dns.TypeA, dns.ClassINET)
var blocked bool
// Warmup to fill the pools and the slices.
blocked := engine.isBlocked(ctx, req)
require.False(b, blocked)
b.ReportAllocs()
for b.Loop() {
blocked = engine.isBlocked(ctx, req)
}
assert.False(b, blocked)
require.False(b, blocked)
})
b.Run("block", func(b *testing.B) {
req := dnsservertest.NewReq("block.test", dns.TypeA, dns.ClassINET)
var blocked bool
// Warmup to fill the pools and the slices.
blocked := engine.isBlocked(ctx, req)
require.True(b, blocked)
b.ReportAllocs()
for b.Loop() {
blocked = engine.isBlocked(ctx, req)
}
assert.True(b, blocked)
require.True(b, blocked)
})
// Most recent results:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/access
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkBlockedHostEngine_IsBlocked/pass-16 3362199 369.1 ns/op 16 B/op 1 allocs/op
// BenchmarkBlockedHostEngine_IsBlocked/block-16 2299890 510.6 ns/op 24 B/op 1 allocs/op
// BenchmarkBlockedHostEngine_IsBlocked/pass-16 3750295 317.8 ns/op 16 B/op 1 allocs/op
// BenchmarkBlockedHostEngine_IsBlocked/block-16 3407104 350.2 ns/op 24 B/op 1 allocs/op
}

View File

@@ -385,8 +385,11 @@ func BenchmarkDefaultProfile_IsBlocked(b *testing.B) {
for _, bc := range benchCases {
b.Run(bc.name, func(b *testing.B) {
// Warmup to fill the pools and the slices.
blocked := a.IsBlocked(ctx, bc.req, passAddrPort, nil)
bc.want(b, blocked)
b.ReportAllocs()
var blocked bool
for b.Loop() {
blocked = a.IsBlocked(ctx, bc.req, passAddrPort, nil)
}
@@ -396,11 +399,10 @@ func BenchmarkDefaultProfile_IsBlocked(b *testing.B) {
}
// Most recent results:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/access
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkDefaultProfile_IsBlocked/pass-16 2679700 468.8 ns/op 16 B/op 1 allocs/op
// BenchmarkDefaultProfile_IsBlocked/block-16 2081113 576.4 ns/op 24 B/op 1 allocs/op
// BenchmarkDefaultProfile_IsBlocked/pass-16 2638284 452.4 ns/op 16 B/op 1 allocs/op
// BenchmarkDefaultProfile_IsBlocked/block-16 2224564 539.1 ns/op 24 B/op 1 allocs/op
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/golibs/testutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func BenchmarkStandardBlocker_IsBlocked(b *testing.B) {
@@ -24,35 +24,38 @@ func BenchmarkStandardBlocker_IsBlocked(b *testing.B) {
b.Run("pass", func(b *testing.B) {
req := dnsservertest.NewReq("pass.test", dns.TypeA, dns.ClassINET)
var blocked bool
// Warmup to fill the pools and the slices.
blocked := blocker.IsBlocked(ctx, req, remoteAddr, nil)
require.False(b, blocked)
b.ReportAllocs()
for b.Loop() {
blocked = blocker.IsBlocked(ctx, req, remoteAddr, nil)
}
assert.False(b, blocked)
require.False(b, blocked)
})
b.Run("block", func(b *testing.B) {
req := dnsservertest.NewReq("block.test", dns.TypeA, dns.ClassINET)
var blocked bool
// Warmup to fill the pools and the slices.
blocked := blocker.IsBlocked(ctx, req, remoteAddr, nil)
require.True(b, blocked)
b.ReportAllocs()
for b.Loop() {
blocked = blocker.IsBlocked(ctx, req, remoteAddr, nil)
}
assert.True(b, blocked)
require.True(b, blocked)
})
// Most recent results:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/access
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkStandardBlocker_IsBlocked/pass-16 3009312 378.2 ns/op 16 B/op 1 allocs/op
// BenchmarkStandardBlocker_IsBlocked/block-16 2518006 421.9 ns/op 24 B/op 1 allocs/op
// BenchmarkStandardBlocker_IsBlocked/pass-16 3568975 335.4 ns/op 16 B/op 1 allocs/op
// BenchmarkStandardBlocker_IsBlocked/block-16 3286392 364.1 ns/op 24 B/op 1 allocs/op
}

View File

@@ -47,9 +47,7 @@ type CustomDomainStateCurrent struct {
// CertName is the unique name for fetching the actual certificate data. If
// [CustomDomainStateCurrent.Enabled] is true, it must not be empty.
//
// TODO(a.garipov): Make a newtype.
CertName string
CertName CertificateName
// Enabled shows if this certificate is enabled.
Enabled bool

53
internal/agd/tls.go Normal file
View File

@@ -0,0 +1,53 @@
package agd
import (
"fmt"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
)
// maxCertificateNameLen is the maximum length of a [CertificateName].
const maxCertificateNameLen = 32
// CertificateName is the unique name identifying the TLS certificate.
type CertificateName string
// NewCertificateName creates a new CertificateName from the given string.
func NewCertificateName(str string) (name CertificateName, err error) {
if str == "" {
return "", errors.ErrEmptyValue
}
err = validate.InRange("length", len(str), 1, maxCertificateNameLen)
if err != nil {
// Don't wrap the error, since it's informative enough as is.
return "", err
}
for i, r := range str {
// Don't use [agdvalidate.FirstNonIDRune] as it allows invalid symbols
// for file names.
if !isValidCertNameRune(r) {
return "", fmt.Errorf("at index %d: bad symbol: %q", i, r)
}
}
return CertificateName(str), nil
}
// isValidCertNameRune returns true if the given rune is valid to be used in a
// [CertificateName]. It essentially allows alphanumeric symbols, underscores,
// and hyphens.
func isValidCertNameRune(r rune) (ok bool) {
switch {
case
r >= 'a' && r <= 'z',
r >= 'A' && r <= 'Z',
r >= '0' && r <= '9',
r == '_', r == '-':
return true
default:
return false
}
}

51
internal/agd/tls_test.go Normal file
View File

@@ -0,0 +1,51 @@
package agd_test
import (
"testing"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/golibs/testutil"
)
func TestNewCertificateName(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
value string
wantErrMsg string
}{{
name: "empty",
value: "",
wantErrMsg: "empty value",
}, {
name: "bad_symbol",
value: "not valid",
wantErrMsg: "at index 3: bad symbol: ' '",
}, {
name: "bad_base_name",
value: "bad/base_name",
wantErrMsg: "at index 3: bad symbol: '/'",
}, {
name: "too_long",
value: "this_is_a_very_long_certificate_name",
wantErrMsg: "length: out of range: must be no greater than 32, got 36",
}, {
name: "ok",
value: "ok_cert_name",
wantErrMsg: "",
}, {
name: "ok_numeric",
value: "1234567890",
wantErrMsg: "",
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
_, err := agd.NewCertificateName(tc.value)
testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
})
}
}

View File

@@ -270,34 +270,52 @@ var _ geoip.Interface = (*GeoIP)(nil)
// GeoIP is a [geoip.Interface] for tests.
type GeoIP struct {
OnData func(host string, ip netip.Addr) (l *geoip.Location, err error)
OnSubnetByLocation func(l *geoip.Location, fam netutil.AddrFamily) (n netip.Prefix, err error)
OnData func(
сtx context.Context,
host string,
ip netip.Addr,
) (l *geoip.Location, err error)
OnSubnetByLocation func(
ctx context.Context,
l *geoip.Location,
fam netutil.AddrFamily,
) (n netip.Prefix, err error)
}
// Data implements the [geoip.Interface] interface for *GeoIP.
func (g *GeoIP) Data(host string, ip netip.Addr) (l *geoip.Location, err error) {
return g.OnData(host, ip)
func (g *GeoIP) Data(
ctx context.Context,
host string,
ip netip.Addr,
) (l *geoip.Location, err error) {
return g.OnData(ctx, host, ip)
}
// SubnetByLocation implements the [geoip.Interface] interface for *GeoIP.
func (g *GeoIP) SubnetByLocation(
ctx context.Context,
l *geoip.Location,
fam netutil.AddrFamily,
) (n netip.Prefix, err error) {
return g.OnSubnetByLocation(l, fam)
return g.OnSubnetByLocation(ctx, l, fam)
}
// NewGeoIP returns a new *GeoIP all methods of which panic.
func NewGeoIP() (c *GeoIP) {
return &GeoIP{
OnData: func(host string, ip netip.Addr) (l *geoip.Location, err error) {
panic(testutil.UnexpectedCall(host, ip))
OnData: func(
ctx context.Context,
host string,
ip netip.Addr,
) (l *geoip.Location, err error) {
panic(testutil.UnexpectedCall(ctx, host, ip))
},
OnSubnetByLocation: func(
ctx context.Context,
l *geoip.Location,
fam netutil.AddrFamily,
) (n netip.Prefix, err error) {
panic(testutil.UnexpectedCall(l, fam))
panic(testutil.UnexpectedCall(ctx, l, fam))
},
}
}

View File

@@ -7,6 +7,7 @@ import (
"net/url"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
"github.com/AdguardTeam/golibs/timeutil"
)
@@ -76,7 +77,7 @@ var _ tlsconfig.CustomDomainStorage = (*CustomDomainStorage)(nil)
// *CustomDomainStorage.
func (s *CustomDomainStorage) CertificateData(
ctx context.Context,
name string,
name agd.CertificateName,
) (cert, key []byte, err error) {
start := s.clock.Now()
defer func() { s.metrics.ObserveRequest(ctx, time.Since(start), err) }()
@@ -84,7 +85,7 @@ func (s *CustomDomainStorage) CertificateData(
s.logger.DebugContext(ctx, "getting cert data", "name", name)
req := &CustomDomainCertificateRequest{
CertName: name,
CertName: string(name),
}
ctx = ctxWithAuthentication(ctx, s.apiKey)

View File

@@ -248,8 +248,14 @@ func (x *CustomDomain) toInternal() (c *agd.CustomDomainConfig, err error) {
switch s := x.State.(type) {
case *CustomDomain_Current_:
var certName agd.CertificateName
certName, err = agd.NewCertificateName(s.Current.CertName)
if err != nil {
return nil, fmt.Errorf("certificate name: %q: %w", s.Current.CertName, err)
}
st := &agd.CustomDomainStateCurrent{
CertName: s.Current.CertName,
CertName: certName,
NotBefore: s.Current.NotBefore.AsTime(),
NotAfter: s.Current.NotAfter.AsTime(),
Enabled: s.Current.Enabled,

View File

@@ -20,7 +20,7 @@ var _ validate.Interface = additionalInfo(nil)
func (c additionalInfo) Validate() (err error) {
var errs []error
for _, k := range slices.Sorted(maps.Keys(c)) {
if !model.LabelName(k).IsValid() {
if !model.LegacyValidation.IsValidLabelName(k) {
errs = append(errs, fmt.Errorf(
"prometheus labels must match %s, got %q",
model.LabelNameRE,

View File

@@ -50,6 +50,7 @@ import (
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/osutil"
"github.com/AdguardTeam/golibs/service"
"github.com/AdguardTeam/golibs/syncutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/c2h5oh/datasize"
"github.com/prometheus/client_golang/prometheus"
@@ -200,6 +201,32 @@ func newBuilder(c *builderConfig) (b *builder) {
}
}
// initCrashReporter initializes the crash reporter.
func (b *builder) initCrashReporter(ctx context.Context) (err error) {
crashRep, err := newCrashReporter(&crashReporterConfig{
logger: b.baseLogger.With(slogutil.KeyPrefix, "crash_reporter"),
dirPath: b.env.CrashOutputDir,
prefix: b.env.CrashOutputPrefix,
enabled: bool(b.env.CrashOutputEnabled),
})
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
err = crashRep.Start(ctx)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
b.sigHdlr.AddService(crashRep)
b.logger.DebugContext(ctx, "initialized crash reporter")
return nil
}
// startGeoIP starts the concurrent initialization of the GeoIP database. The
// GeoIP initialization is started early and concurrently, because it takes
// time. Later methods wait for the completion and continue with GeoIP.
@@ -880,11 +907,19 @@ func (b *builder) initQueryLog(ctx context.Context) (err error) {
return fmt.Errorf("registering querylog metrics: %w", err)
}
var sema syncutil.Semaphore
if b.env.QueryLogSemaphoreEnabled {
sema = syncutil.NewChanSemaphore(b.env.QueryLogSemaphoreLimit)
} else {
sema = syncutil.EmptySemaphore{}
}
b.queryLog = querylog.NewFileSystem(&querylog.FileSystemConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "querylog"),
Path: b.env.QueryLogPath,
Metrics: mtrc,
RandSeed: randutil.MustNewSeed(),
Logger: b.baseLogger.With(slogutil.KeyPrefix, "querylog"),
Path: b.env.QueryLogPath,
Metrics: mtrc,
Semaphore: sema,
RandSeed: randutil.MustNewSeed(),
})
b.logger.DebugContext(ctx, "initialized file-based query log")

View File

@@ -65,6 +65,8 @@ func Main(plugins *plugin.Registry) {
defer reportPanics(ctx, errColl, mainLogger)
setMaxThreads(ctx, mainLogger, envs.MaxThreads)
c := errors.Must(parseConfig(envs.ConfPath))
errors.Check(c.Validate())
@@ -84,6 +86,8 @@ func Main(plugins *plugin.Registry) {
profilesEnabled: profilesEnabled,
})
errors.Check(b.initCrashReporter(ctx))
errors.Check(experiment.Init(baseLogger, b.promRegisterer))
errors.Check(metrics.SetAdditionalInfo(b.promRegisterer, c.AdditionalMetricsInfo))

145
internal/cmd/crash.go Normal file
View File

@@ -0,0 +1,145 @@
package cmd
import (
"context"
"fmt"
"log/slog"
"os"
"runtime/debug"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/service"
)
// crashReporter is a helper that sets a file for Go runtime crashes and
// unhandled panics.
type crashReporter struct {
file *os.File
logger *slog.Logger
dirPath string
pattern string
}
// crashReporterConfig is the configuration structure for a [crashReporter].
type crashReporterConfig struct {
// logger is used to log the operation of the crash reporter. If enabled is
// true, logger must not be nil.
logger *slog.Logger
// dirPath is the path to the directory where the crash report is created.
// If enabled is true, dirPath should not be nil and should point to a
// directory.
dirPath string
// prefix is the prefix to use when creating the file. If enabled is true,
// prefix should not be nil.
prefix string
// enabled shows if a crash report file should be created.
enabled bool
}
// newCrashReporter returns a new properly initialized crash reporter. c must
// not be nil and must be valid.
//
// TODO(a.garipov): Consider moving to golibs.
func newCrashReporter(c *crashReporterConfig) (r *crashReporter, err error) {
defer func() { err = errors.Annotate(err, "crash reporter: %w") }()
if !c.enabled {
return nil, nil
}
err = validateDir(c.dirPath)
if err != nil {
// Don't wrap the error, because it's informative enough as is, and
// there is already errors.Annotate here.
return nil, err
}
pat := fmt.Sprintf(
"%s_%s_%07d_*.txt",
c.prefix,
time.Now().Format("20060102150405"),
os.Getpid(),
)
return &crashReporter{
logger: c.logger,
dirPath: c.dirPath,
pattern: pat,
}, nil
}
// type check
var _ service.Interface = (*crashReporter)(nil)
// Start implements the [service.Interface] for *crashReporter. If r is nil,
// err is nil.
func (r *crashReporter) Start(ctx context.Context) (err error) {
if r == nil {
return nil
}
defer func() { err = errors.Annotate(err, "starting crash reporter: %w") }()
r.logger.InfoContext(ctx, "creating crash output file")
r.file, err = os.CreateTemp(r.dirPath, r.pattern)
if err != nil {
// Don't wrap the error, because it's informative enough as is, and
// there is already errors.Annotate here.
return err
}
r.logger = r.logger.With("path", r.file.Name())
r.logger.InfoContext(ctx, "setting crash output")
err = debug.SetCrashOutput(r.file, debug.CrashOptions{})
if err != nil {
return fmt.Errorf("setting crash output: %w", err)
}
r.logger.DebugContext(ctx, "set crash output")
return nil
}
// Shutdown implements the [service.Interface] for *crashReporter. If r is nil,
// err is nil.
func (r *crashReporter) Shutdown(ctx context.Context) (err error) {
if r == nil {
return nil
}
r.logger.InfoContext(ctx, "closing crash output")
s, err := r.file.Stat()
if err != nil {
return fmt.Errorf("getting stat of crash file: %w", err)
}
if s.Size() > 0 {
r.logger.InfoContext(ctx, "crash output is not empty; not removing")
return nil
}
name := r.file.Name()
err = r.file.Close()
if err != nil {
return fmt.Errorf("closing crash file: %w", err)
}
r.logger.InfoContext(ctx, "crash output is empty; removing")
err = os.Remove(name)
if err != nil {
return fmt.Errorf("removing crash file: %w", err)
}
return nil
}

View File

@@ -55,6 +55,8 @@ type environment struct {
BackendRateLimitAPIKey string `env:"BACKEND_RATELIMIT_API_KEY"`
BillStatAPIKey string `env:"BILLSTAT_API_KEY"`
ConfPath string `env:"CONFIG_PATH" envDefault:"./config.yaml"`
CrashOutputDir string `env:"CRASH_OUTPUT_DIR"`
CrashOutputPrefix string `env:"CRASH_OUTPUT_PREFIX" envDefault:"agdns"`
CustomDomainsAPIKey string `env:"CUSTOM_DOMAINS_API_KEY"`
CustomDomainsCachePath string `env:"CUSTOM_DOMAINS_CACHE_PATH"`
DNSCheckKVType string `env:"DNSCHECK_KV_TYPE"`
@@ -94,17 +96,22 @@ type environment struct {
// TODO(a.garipov): Rename to DNSCHECK_CACHE_KV_COUNT?
DNSCheckCacheKVSize int `env:"DNSCHECK_CACHE_KV_SIZE"`
MaxThreads int `env:"MAX_THREADS"`
QueryLogSemaphoreLimit uint `env:"QUERYLOG_SEMAPHORE_LIMIT"`
ListenPort uint16 `env:"LISTEN_PORT" envDefault:"8181"`
Verbosity uint8 `env:"VERBOSE" envDefault:"0"`
AdultBlockingEnabled strictBool `env:"ADULT_BLOCKING_ENABLED" envDefault:"1"`
CrashOutputEnabled strictBool `env:"CRASH_OUTPUT_ENABLED" envDefault:"0"`
CustomDomainsEnabled strictBool `env:"CUSTOM_DOMAINS_ENABLED" envDefault:"1"`
LogTimestamp strictBool `env:"LOG_TIMESTAMP" envDefault:"1"`
NewRegDomainsEnabled strictBool `env:"NEW_REG_DOMAINS_ENABLED" envDefault:"1"`
SafeBrowsingEnabled strictBool `env:"SAFE_BROWSING_ENABLED" envDefault:"1"`
BlockedServiceEnabled strictBool `env:"BLOCKED_SERVICE_ENABLED" envDefault:"1"`
QueryLogSemaphoreEnabled strictBool `env:"QUERYLOG_SEMAPHORE_ENABLED"`
GeneralSafeSearchEnabled strictBool `env:"GENERAL_SAFE_SEARCH_ENABLED" envDefault:"1"`
YoutubeSafeSearchEnabled strictBool `env:"YOUTUBE_SAFE_SEARCH_ENABLED" envDefault:"1"`
WebStaticDirEnabled strictBool `env:"WEB_STATIC_DIR_ENABLED" envDefault:"0"`
@@ -126,7 +133,9 @@ var _ validate.Interface = (*environment)(nil)
// Validate implements the [validate.Interface] interface for *environment.
func (envs *environment) Validate() (err error) {
var errs []error
errs := []error{
validate.NotNegative("MAX_THREADS", envs.MaxThreads),
}
errs = envs.validateHTTPURLs(errs)
@@ -153,12 +162,14 @@ func (envs *environment) Validate() (err error) {
errs = append(errs, fmt.Errorf("VERBOSE: %w", err))
}
errs = envs.validateCrashOutput(errs)
errs = envs.validateCustomDomains(errs)
errs = envs.validateDNSCheck(errs)
errs = envs.validateQueryLogSemaphore(errs)
errs = envs.validateRateLimit(errs)
errs = envs.validateRateLimitURLs(errs)
errs = envs.validateSessionTickets(errs)
errs = envs.validateStandardAccess(errs)
errs = envs.validateRateLimitURLs(errs)
return errors.Join(errs...)
}
@@ -244,13 +255,19 @@ func (envs *environment) validateWebStaticDir() (err error) {
return nil
}
dir := envs.WebStaticDir
if dir == "" {
dirPath := envs.WebStaticDir
if dirPath == "" {
return errors.ErrEmptyValue
}
// Use a best-effort check to make sure the directory exists.
fi, err := os.Stat(dir)
return validateDir(dirPath)
}
// validateDir is a best-effort check to make sure the directory exists.
//
// TODO(a.garipov): Consider moving to golibs.
func validateDir(dirPath string) (err error) {
fi, err := os.Stat(dirPath)
if err != nil {
return err
}
@@ -262,6 +279,29 @@ func (envs *environment) validateWebStaticDir() (err error) {
return nil
}
// validateCrashOutput appends validation errors to errs if the environment
// variables for crash reporting contain errors.
func (envs *environment) validateCrashOutput(orig []error) (errs []error) {
errs = orig
if !envs.CrashOutputEnabled {
return errs
}
dirPath := envs.WebStaticDir
if dirPath != "" {
err := validateDir(dirPath)
if err != nil {
errs = append(errs, err)
}
}
return append(errs,
validate.NotEmpty("CRASH_OUTPUT_DIR", envs.CrashOutputDir),
validate.NotEmpty("CRASH_OUTPUT_PREFIX", envs.CrashOutputPrefix),
)
}
// validateCustomDomains appends validation errors to errs if the environment
// variables for custom domains contain errors.
func (envs *environment) validateCustomDomains(errs []error) (res []error) {
@@ -420,6 +460,24 @@ func (envs *environment) validateProfilesConf(profilesEnabled bool) (err error)
return errors.Join(errs...)
}
// validateCache appends validation errors to orig if environment variables for
// the querylog semaphore contain errors.
func (envs *environment) validateQueryLogSemaphore(orig []error) (errs []error) {
errs = orig
if !envs.QueryLogSemaphoreEnabled {
return errs
}
err := validate.Positive("QUERYLOG_SEMAPHORE_LIMIT", envs.QueryLogSemaphoreLimit)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
errs = append(errs, err)
}
return errs
}
// validateCache appends validation errors to the given errs if environment
// variables for KV Cache contain errors.
func (envs *environment) validateCache(errs []error) (res []error) {

23
internal/cmd/runtime.go Normal file
View File

@@ -0,0 +1,23 @@
package cmd
import (
"context"
"log/slog"
"runtime/debug"
"github.com/AdguardTeam/golibs/logutil/slogutil"
)
// setMaxThreads sets the maximum number of threads for the Go runtime, if
// necessary. l must not be nil, envs must not be negative.
func setMaxThreads(ctx context.Context, l *slog.Logger, n int) {
if n == 0 {
l.Log(ctx, slogutil.LevelTrace, "go max threads not set")
return
}
debug.SetMaxThreads(n)
l.InfoContext(ctx, "set go max threads", "n", n)
}

View File

@@ -2,7 +2,6 @@ package dnsserver_test
import (
"context"
"log/slog"
"net/netip"
"os"
@@ -30,7 +29,6 @@ func ExampleNewServerDNS() {
baseLogger := slogutil.New(&slogutil.Config{
Format: slogutil.FormatText,
Level: slog.LevelDebug,
}).With("server_name", "test")
// Init the server with this handler func
@@ -81,7 +79,6 @@ func ExampleWithMiddlewares() {
baseLogger := slogutil.New(&slogutil.Config{
Format: slogutil.FormatText,
Level: slog.LevelDebug,
})
middleware := querylog.NewLogMiddleware(os.Stdout, baseLogger)

View File

@@ -1,9 +1,9 @@
module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver
go 1.24.6
go 1.25.1
require (
github.com/AdguardTeam/golibs v0.34.0
github.com/AdguardTeam/golibs v0.34.1
github.com/ameshkov/dnscrypt/v2 v2.4.0
github.com/ameshkov/dnsstamps v1.0.3
github.com/bluele/gcache v0.0.2
@@ -11,32 +11,34 @@ require (
github.com/miekg/dns v1.1.68
github.com/panjf2000/ants/v2 v2.11.3
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
github.com/prometheus/client_golang v1.23.0
github.com/prometheus/client_golang v1.23.1
github.com/quic-go/quic-go v0.54.0
github.com/stretchr/testify v1.11.1
golang.org/x/net v0.43.0
golang.org/x/sys v0.35.0
golang.org/x/net v0.44.0
golang.org/x/sys v0.36.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/kr/text v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/common v0.66.0 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
go.uber.org/mock v0.6.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/tools v0.36.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/tools v0.37.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,5 +1,4 @@
github.com/AdguardTeam/golibs v0.34.0 h1:JQK024DkTYxE7vsPVsYsoyDHW/53Nun7OYb9qscniK8=
github.com/AdguardTeam/golibs v0.34.0/go.mod h1:K4C2EbfSEM1zY5YXoti9SfbTAHN/kIX97LpDtCwORrM=
github.com/AdguardTeam/golibs v0.34.1 h1:RyBpZiXnJqlO3T+xjWldlxsEZDelmaFfKvXiJHDZZFQ=
github.com/ameshkov/dnscrypt/v2 v2.4.0 h1:if6ZG2cuQmcP2TwSY+D0+8+xbPfoatufGlOQTMNkI9o=
github.com/ameshkov/dnscrypt/v2 v2.4.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI=
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
@@ -17,6 +16,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -31,12 +31,10 @@ github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
github.com/prometheus/client_golang v1.23.1 h1:w6gXMLQGgd0jXXlote9lRHMe0nG01EbnJT+C0EJru2Y=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/common v0.66.0 h1:K/rJPHrG3+AoQs50r2+0t7zMnMzek2Vbv31OFVsMeVY=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
@@ -53,26 +51,18 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -103,9 +103,12 @@ type ServerBase struct {
// that don't use UDP.
udpListener net.PacketConn
// wg tracks active workers, both listeners and workers processing queries.
// Shutdown won't finish until there's at least one active worker.
wg *sync.WaitGroup
// activeTaskWG tracks goroutines processing UDP and TCP connections and
// queries. Shutdown doesn't finish as long as there's at least one active
// task.
//
// TODO(a.garipov): Consider also using it for listeners.
activeTaskWG *sync.WaitGroup
// name is used for logging and it may be used for perf counters reporting.
//
@@ -153,7 +156,7 @@ func newServerBase(proto Protocol, c *ConfigBase) (s *ServerBase) {
disposer: cmp.Or[Disposer](c.Disposer, EmptyDisposer{}),
listenConfig: c.ListenConfig,
mu: &sync.RWMutex{},
wg: &sync.WaitGroup{},
activeTaskWG: &sync.WaitGroup{},
name: c.Name,
addr: c.Addr,
network: c.Network,
@@ -465,21 +468,12 @@ func (s *ServerBase) handlePanicAndExit(ctx context.Context) {
func (s *ServerBase) handlePanic(ctx context.Context, v any) {
s.metrics.OnPanic(ctx, v)
logger, ok := slogutil.LoggerFromContext(ctx)
l, ok := slogutil.LoggerFromContext(ctx)
if !ok {
logger = s.baseLogger
l = s.baseLogger
}
var args []any
err, ok := v.(error)
if ok {
args = []any{slogutil.KeyError, err}
} else {
args = []any{"value", v}
}
logger.ErrorContext(ctx, "recovered from panic", args...)
slogutil.PrintStack(ctx, logger, slog.LevelError)
slogutil.PrintRecovered(ctx, l, v)
}
// handlePanicAndRecover writes panic info to log, reports it to the registered
@@ -558,20 +552,17 @@ func (s *ServerBase) waitShutdown(ctx context.Context) (err error) {
go func() {
defer slogutil.RecoverAndLog(ctx, s.baseLogger)
// wait until all queries are processed
s.wg.Wait()
// Wait until all tasks exit.
s.activeTaskWG.Wait()
close(closed)
}()
var ctxErr error
select {
case <-closed:
// Do nothing here
return nil
case <-ctx.Done():
ctxErr = ctx.Err()
return ctx.Err()
}
return ctxErr
}
// isStarted returns true if the server is started.

View File

@@ -15,7 +15,6 @@ import (
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns"
"github.com/panjf2000/ants/v2"
)
const (
@@ -84,11 +83,9 @@ type ConfigDNS struct {
type ServerDNS struct {
*ServerBase
// workerPool is a goroutine workerPool we use to process DNS queries.
// Complicated logic may require growing the goroutine's stack, and we
// experienced it in AdGuard DNS. The easiest way to avoid spending extra
// time on this is to reuse already existing goroutines.
workerPool *ants.Pool
// taskPool is a goroutine pool used to process DNS queries. It is used to
// prevent excessive growth of goroutine stacks.
taskPool *taskPool
// udpPool is a pool for UDP request buffers.
udpPool *syncutil.Pool[[]byte]
@@ -171,7 +168,9 @@ func newServerDNS(proto Protocol, c *ConfigDNS) (s *ServerDNS) {
maxPipelineEnabled: c.MaxPipelineEnabled,
}
s.workerPool = mustNewPoolNonblocking(s.baseLogger)
s.taskPool = mustNewTaskPool(&taskPoolConfig{
logger: s.baseLogger,
})
return s
}
@@ -206,8 +205,9 @@ func (s *ServerDNS) Start(ctx context.Context) (err error) {
return err
}
s.wg.Add(1)
go s.startServeUDP(ctx)
s.activeTaskWG.Go(func() {
s.serveUDP(ctx, s.udpListener)
})
}
// Start listening to TCP on the specified address.
@@ -217,8 +217,9 @@ func (s *ServerDNS) Start(ctx context.Context) (err error) {
return err
}
s.wg.Add(1)
go s.startServeTCP(ctx)
s.activeTaskWG.Go(func() {
s.serveTCP(ctx, s.tcpListener, "tcp")
})
}
s.started = true
@@ -245,42 +246,13 @@ func (s *ServerDNS) Shutdown(ctx context.Context) (err error) {
err = s.waitShutdown(ctx)
// Close the workerPool and releases all workers.
s.workerPool.Release()
s.taskPool.Release()
s.baseLogger.InfoContext(ctx, "server has been shut down")
return err
}
// startServeUDP starts the UDP listener loop.
func (s *ServerDNS) startServeUDP(ctx context.Context) {
// Do not recover from panics here since if this goroutine panics, the
// application won't be able to continue listening to UDP.
defer s.handlePanicAndExit(ctx)
defer s.wg.Done()
s.baseLogger.InfoContext(ctx, "starting listening udp")
err := s.serveUDP(ctx, s.udpListener)
if err != nil {
s.baseLogger.WarnContext(ctx, "listening udp failed", slogutil.KeyError, err)
}
}
// startServeTCP starts the TCP listener loop.
func (s *ServerDNS) startServeTCP(ctx context.Context) {
// Do not recover from panics here since if this goroutine panics, the
// application won't be able to continue listening to TCP.
defer s.handlePanicAndExit(ctx)
defer s.wg.Done()
s.baseLogger.InfoContext(ctx, "starting listening tcp")
err := s.serveTCP(ctx, s.tcpListener)
if err != nil {
s.baseLogger.WarnContext(ctx, "listening tcp failed", slogutil.KeyError, err)
}
}
// shutdown marks the server as stopped and closes active listeners.
func (s *ServerDNS) shutdown(ctx context.Context) (err error) {
s.mu.Lock()

View File

@@ -18,22 +18,42 @@ import (
"github.com/miekg/dns"
)
// serveTCP runs the TCP serving loop.
func (s *ServerDNS) serveTCP(ctx context.Context, l net.Listener) (err error) {
// serveTCP runs the TCP serving loop. It is intended to be used as a
// goroutine. l must not be nil.
func (s *ServerDNS) serveTCP(ctx context.Context, l net.Listener, proto string) {
// Do not recover from panics here since if this goroutine panics, the
// application won't be able to continue listening to TCP.
defer s.handlePanicAndExit(ctx)
s.baseLogger.InfoContext(ctx, "starting listening tcp")
defer func() { closeWithLog(ctx, s.baseLogger, "closing tcp listener", l) }()
for s.isStarted() {
err = s.acceptTCPConn(ctx, l)
if err != nil {
if !s.isStarted() {
return nil
}
return err
err := s.acceptTCPConn(ctx, l)
if err == nil {
continue
}
}
return nil
// TODO(ameshkov): Consider the situation where the server is shut down
// and restarted between the two calls to isStarted.
if !s.isStarted() {
s.baseLogger.DebugContext(
ctx,
"listening tcp failed: server not started",
"proto", proto,
slogutil.KeyError, err,
)
} else {
s.baseLogger.ErrorContext(
ctx,
"listening tcp failed",
"proto", proto,
slogutil.KeyError, err,
)
}
return
}
}
// acceptTCPConn reads and starts processing a single TCP connection.
@@ -60,9 +80,7 @@ func (s *ServerDNS) acceptTCPConn(ctx context.Context, l net.Listener) (err erro
s.tcpConns.Add(conn)
}()
s.wg.Add(1)
return s.workerPool.Submit(func() {
return s.taskPool.submitWG(s.activeTaskWG, func() {
s.serveTCPConn(ctx, conn)
})
}
@@ -89,16 +107,14 @@ func handshake(conn net.Conn, timeout time.Duration) (err error) {
return shaker.HandshakeContext(ctx)
}
// serveTCPConn serves a single TCP connection.
// serveTCPConn serves a single TCP connection. It is intended to be used as a
// goroutine. conn must not be nil.
func (s *ServerDNS) serveTCPConn(ctx context.Context, conn net.Conn) {
// Use this to wait until all queries from this connection has been
// processed before closing it.
wg := &sync.WaitGroup{}
defer s.handlePanicAndRecover(ctx)
connWG := &sync.WaitGroup{}
defer func() {
defer s.wg.Done()
wg.Wait()
connWG.Wait()
closeWithLog(ctx, s.baseLogger, "closing tcp conn", conn)
@@ -108,8 +124,6 @@ func (s *ServerDNS) serveTCPConn(ctx context.Context, conn net.Conn) {
s.tcpConns.Delete(conn)
}()
defer s.handlePanicAndRecover(ctx)
var msgSema syncutil.Semaphore = syncutil.EmptySemaphore{}
if s.maxPipelineEnabled {
msgSema = syncutil.NewChanSemaphore(s.maxPipelineCount)
@@ -129,7 +143,7 @@ func (s *ServerDNS) serveTCPConn(ctx context.Context, conn net.Conn) {
}
for s.isStarted() {
err = s.acceptTCPMsg(conn, wg, writeMu, timeout, msgSema)
err = s.acceptTCPMsg(conn, connWG, writeMu, timeout, msgSema)
if err != nil {
s.logReadErr(ctx, "reading from conn", err)
@@ -155,7 +169,7 @@ func (s *ServerDNS) logReadErr(ctx context.Context, msg string, err error) {
// TLS connection, the handshake must have already been performed.
func (s *ServerDNS) acceptTCPMsg(
conn net.Conn,
wg *sync.WaitGroup,
connWG *sync.WaitGroup,
writeMu *sync.Mutex,
timeout time.Duration,
msgSema syncutil.Semaphore,
@@ -182,13 +196,11 @@ func (s *ServerDNS) acceptTCPMsg(
// RFC 7766 recommends implementing query pipelining, i.e. process all
// incoming queries concurrently and write responses out of order.
wg.Add(1)
return s.workerPool.Submit(func() {
return s.taskPool.submitWG(connWG, func() {
defer reqCancel()
defer msgSema.Release()
s.serveTCPMessage(reqCtx, wg, writeMu, *bufPtr, conn)
s.serveTCPMessage(reqCtx, writeMu, *bufPtr, conn)
s.tcpPool.Put(bufPtr)
})
}
@@ -199,31 +211,27 @@ type tlsConnectionStater interface {
ConnectionState() tls.ConnectionState
}
// serveTCPMessage processes a single TCP message.
// serveTCPMessage processes a single TCP message. It is intended to be used as
// a goroutine. All arguments must not be nil.
func (s *ServerDNS) serveTCPMessage(
ctx context.Context,
wg *sync.WaitGroup,
writeMu *sync.Mutex,
buf []byte,
conn net.Conn,
) {
defer wg.Done()
defer s.handlePanicAndRecover(ctx)
rw := &tcpResponseWriter{
written := s.serveDNS(ctx, buf, &tcpResponseWriter{
respPool: s.respPool,
writeMu: writeMu,
conn: conn,
writeTimeout: s.writeTimeout,
idleTimeout: s.tcpIdleTimeout,
}
written := s.serveDNS(ctx, buf, rw)
})
if !written {
// Nothing has been written, we should close the connection in order to
// avoid hanging connections. Than might happen if the handler
// rate-limited connections or if we received garbage data instead of
// a DNS query.
// Nothing has been written, so close the connection in order to avoid
// hanging connections. That can happen when the handler rate-limited a
// connection or if garbage data has been received.
slogutil.CloseAndLog(ctx, s.baseLogger, conn, slog.LevelDebug)
}
}

View File

@@ -8,28 +8,41 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns"
)
// serveUDP runs the UDP serving loop.
func (s *ServerDNS) serveUDP(ctx context.Context, conn net.PacketConn) (err error) {
// serveUDP runs the UDP serving loop. It is intended to be used as a
// goroutine. conn must not be nil.
func (s *ServerDNS) serveUDP(ctx context.Context, conn net.PacketConn) {
// Do not recover from panics here since if this goroutine panics, the
// application won't be able to continue listening to UDP.
defer s.handlePanicAndExit(ctx)
s.baseLogger.InfoContext(ctx, "starting listening udp")
defer func() { closeWithLog(ctx, s.baseLogger, "closing udp conn", conn) }()
for s.isStarted() {
err = s.acceptUDPMsg(ctx, conn)
if err != nil {
// TODO(ameshkov): Consider the situation where the server is shut
// down and restarted between the two calls to isStarted.
if !s.isStarted() {
return nil
}
return err
err := s.acceptUDPMsg(ctx, conn)
if err == nil {
continue
}
}
return nil
// TODO(ameshkov): Consider the situation where the server is shut down
// and restarted between the two calls to isStarted.
if !s.isStarted() {
s.baseLogger.DebugContext(
ctx,
"listening udp failed: server not started",
slogutil.KeyError, err,
)
} else {
s.baseLogger.ErrorContext(ctx, "listening udp failed", slogutil.KeyError, err)
}
return
}
}
// acceptUDPMsg reads and starts processing a single UDP message.
@@ -48,8 +61,6 @@ func (s *ServerDNS) acceptUDPMsg(ctx context.Context, conn net.PacketConn) (err
return err
}
s.wg.Add(1)
// Save the start time here, but create the context inside the goroutine,
// since s.requestContext can be slow.
//
@@ -58,7 +69,7 @@ func (s *ServerDNS) acceptUDPMsg(ctx context.Context, conn net.PacketConn) (err
// version.
startTime := time.Now()
return s.workerPool.Submit(func() {
return s.taskPool.submitWG(s.activeTaskWG, func() {
reqCtx, reqCancel := s.requestContext(context.Background())
defer reqCancel()
@@ -71,24 +82,23 @@ func (s *ServerDNS) acceptUDPMsg(ctx context.Context, conn net.PacketConn) (err
})
}
// serveUDPPacket serves a new UDP request.
// serveUDPPacket serves a new UDP request. It is intended to be used as a
// goroutine. buf, conn, and sess must not be nil.
func (s *ServerDNS) serveUDPPacket(
ctx context.Context,
buf []byte,
conn net.PacketConn,
sess netext.PacketSession,
) {
defer s.wg.Done()
defer s.handlePanicAndRecover(ctx)
rw := &udpResponseWriter{
s.serveDNS(ctx, buf, &udpResponseWriter{
respPool: s.respPool,
udpSession: sess,
conn: conn,
writeTimeout: s.writeTimeout,
maxRespSize: s.maxUDPRespSize,
}
s.serveDNS(ctx, buf, rw)
})
}
// readUDPMsg reads the next incoming DNS message.

View File

@@ -231,8 +231,9 @@ func (s *ServerHTTPS) startHTTPSServer(ctx context.Context) (err error) {
}
// Start the server worker goroutine.
s.wg.Add(1)
go s.serveHTTPS(ctx, s.httpServer, s.tcpListener)
s.activeTaskWG.Go(func() {
s.serveHTTPS(ctx, s.httpServer, s.tcpListener)
})
return nil
}
@@ -257,8 +258,9 @@ func (s *ServerHTTPS) startH3Server(ctx context.Context) (err error) {
}
// Start the server worker goroutine.
s.wg.Add(1)
go s.serveH3(ctx, s.h3Server, s.quicListener)
s.activeTaskWG.Go(func() {
s.serveH3(ctx, s.h3Server, s.quicListener)
})
return nil
}
@@ -320,10 +322,9 @@ func (s *ServerHTTPS) shutdownH3(ctx context.Context) {
}
// serveHTTPS is launched in a worker goroutine and serves HTTP/1.1 and HTTP/2
// requests.
// requests. It is intended to be used as a goroutine. All arguments must not
// be nil.
func (s *ServerHTTPS) serveHTTPS(ctx context.Context, hs *http.Server, l net.Listener) {
defer s.wg.Done()
// Do not recover from panics here since if this goroutine panics, the
// application won't be able to continue listening to DoH.
defer s.handlePanicAndExit(ctx)
@@ -341,10 +342,9 @@ func (s *ServerHTTPS) serveHTTPS(ctx context.Context, hs *http.Server, l net.Lis
}
}
// serveH3 is launched in a worker goroutine and serves HTTP/3 requests.
// serveH3 is launched in a worker goroutine and serves HTTP/3 requests. It is
// intended to be used as a goroutine. All arguments must not be nil.
func (s *ServerHTTPS) serveH3(ctx context.Context, hs *http3.Server, ql *quic.EarlyListener) {
defer s.wg.Done()
// Do not recover from panics here since if this goroutine panics, the
// application won't be able to continue listening to DoH.
defer s.handlePanicAndExit(ctx)

View File

@@ -18,7 +18,6 @@ import (
"github.com/AdguardTeam/golibs/syncutil"
"github.com/bluele/gcache"
"github.com/miekg/dns"
"github.com/panjf2000/ants/v2"
"github.com/quic-go/quic-go"
)
@@ -94,11 +93,9 @@ type ConfigQUIC struct {
type ServerQUIC struct {
*ServerBase
// pool is a goroutine pool we use to process DNS queries. Complicated
// logic may require growing the goroutine's stack and we experienced it
// in AdGuard DNS. The easiest way to avoid spending extra time on this is
// to reuse already existing goroutines.
pool *ants.Pool
// taskPool is a goroutine pool used to process DNS queries. It is used to
// prevent excessive growth of goroutine stacks.
taskPool *taskPool
// reqPool is a pool to avoid unnecessary allocations when reading
// DNS packets.
@@ -140,7 +137,9 @@ func NewServerQUIC(c *ConfigQUIC) (s *ServerQUIC) {
quicLimitsEnabled: c.QUICLimitsEnabled,
}
s.pool = mustNewPoolNonblocking(s.baseLogger)
s.taskPool = mustNewTaskPool(&taskPoolConfig{
logger: s.baseLogger,
})
return s
}
@@ -173,9 +172,9 @@ func (s *ServerQUIC) Start(ctx context.Context) (err error) {
return err
}
// Run the serving goroutine.
s.wg.Add(1)
go s.startServeQUIC(ctx)
s.activeTaskWG.Go(func() {
s.serveQUIC(ctx, s.quicListener)
})
s.started = true
@@ -199,8 +198,8 @@ func (s *ServerQUIC) Shutdown(ctx context.Context) (err error) {
err = s.waitShutdown(ctx)
// Close the workerPool and releases all workers.
s.pool.Release()
// Close the taskPool and release all workers.
s.taskPool.Release()
s.baseLogger.InfoContext(ctx, "server has been shut down")
@@ -234,45 +233,43 @@ func (s *ServerQUIC) shutdown(ctx context.Context) (err error) {
return nil
}
// startServeQUIC starts the QUIC listener loop.
func (s *ServerQUIC) startServeQUIC(ctx context.Context) {
// serveQUIC listens for incoming QUIC connections.
func (s *ServerQUIC) serveQUIC(ctx context.Context, l *quic.Listener) {
// We do not recover from panics here since if this go routine panics
// the application won't be able to continue listening to DoQ.
defer s.handlePanicAndExit(ctx)
defer s.wg.Done()
s.baseLogger.InfoContext(ctx, "starting listening quic")
err := s.serveQUIC(ctx, s.quicListener)
if err != nil {
s.baseLogger.WarnContext(ctx, "listening quic failed", slogutil.KeyError, err)
}
}
// serveQUIC listens for incoming QUIC connections.
func (s *ServerQUIC) serveQUIC(ctx context.Context, l *quic.Listener) (err error) {
wg := &sync.WaitGroup{}
// Wait until all conns are processed before exiting this method
defer wg.Wait()
// Use a context that is canceled once this connection ends to mitigate
// quic-go's mishandling of contexts. See TODO in serveQUICConn.
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
// quic-go's mishandling of contexts. See the TODO in
// [ServerQUIC.serveQUICConn].
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for s.isStarted() {
err = s.acceptQUICConn(ctx, l, wg)
if err != nil {
if !s.isStarted() {
return nil
}
return err
err := s.acceptQUICConn(ctx, l, wg)
if err == nil {
continue
}
}
return nil
// TODO(ameshkov): Consider the situation where the server is shut down
// and restarted between the two calls to isStarted.
if !s.isStarted() {
s.baseLogger.DebugContext(
ctx,
"listening quic failed: server not started",
slogutil.KeyError, err,
)
} else {
s.baseLogger.ErrorContext(ctx, "listening quic failed", slogutil.KeyError, err)
}
return
}
}
// acceptQUICConn reads and starts processing a single QUIC connection.
@@ -295,10 +292,8 @@ func (s *ServerQUIC) acceptQUICConn(
return err
}
wg.Add(1)
err = s.pool.Submit(func() {
s.serveQUICConnAsync(ctx, conn, wg)
err = s.taskPool.submitWG(wg, func() {
s.serveQUICConnAsync(ctx, conn)
})
if err != nil {
// Most likely the workerPool is closed, and we can exit right away.
@@ -311,15 +306,13 @@ func (s *ServerQUIC) acceptQUICConn(
return nil
}
// serveQUICConnAsync wraps serveQUICConn call and handles all possible errors
// that might happen there. It also makes sure that the WaitGroup will be
// decremented.
func (s *ServerQUIC) serveQUICConnAsync(
ctx context.Context,
conn *quic.Conn,
connWg *sync.WaitGroup,
) {
defer connWg.Done()
// serveQUICConnAsync wraps [ServerQUIC.serveQUICConn] call and handles errors
// that could happen in it. It is intended to be used as a goroutine. conn
// must not be nil.
//
// TODO(a.garipov): Refactor ServerQUIC.serveQUICConn and merge this one into
// it.
func (s *ServerQUIC) serveQUICConnAsync(ctx context.Context, conn *quic.Conn) {
defer s.handlePanicAndRecover(ctx)
err := s.serveQUICConn(ctx, conn)
@@ -379,12 +372,10 @@ func (s *ServerQUIC) serveQUICConn(ctx context.Context, conn *quic.Conn) (err er
reqCtx, reqCancel := s.requestContext(context.Background())
reqCtx = ContextWithRequestInfo(reqCtx, ri)
streamWg.Add(1)
err = s.pool.Submit(func() {
err = s.taskPool.submitWG(streamWg, func() {
defer reqCancel()
s.serveQUICStreamAsync(reqCtx, stream, conn, streamWg)
s.serveQUICStreamAsync(reqCtx, stream, conn)
})
if err != nil {
// The workerPool is closed, we should simply exit. Make sure that
@@ -398,16 +389,17 @@ func (s *ServerQUIC) serveQUICConn(ctx context.Context, conn *quic.Conn) (err er
return nil
}
// serveQUICStreamAsync wraps serveQUICStream call and handles all possible
// errors that might happen there. It also makes sure that the WaitGroup will
// be decremented.
// serveQUICStreamAsync wraps [ServerQUIC.serveQUICStream] call and handle
// errors that could happen in it. It is intended to be used as a goroutine.
// stream and conn must not be nil.
//
// TODO(a.garipov): Refactor ServerQUIC.serveQUICStream and merge this one into
// it.
func (s *ServerQUIC) serveQUICStreamAsync(
ctx context.Context,
stream *quic.Stream,
conn *quic.Conn,
wg *sync.WaitGroup,
) {
defer wg.Done()
defer s.handlePanicAndRecover(ctx)
err := s.serveQUICStream(ctx, stream, conn)

View File

@@ -5,7 +5,6 @@ import (
"crypto/tls"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
)
// ConfigTLS is a struct that needs to be passed to NewServerTLS to
@@ -71,10 +70,10 @@ func (s *ServerTLS) Start(ctx context.Context) (err error) {
// Start the TLS server loop
if s.tcpListener != nil {
go s.startServeTCP(ctx)
go s.serveTCP(ctx, s.tcpListener, "tls")
}
// TODO(ameshkov): Consider only setting s.started to true once the
// TODO(ameshkov): Consider only setting s.started to true once the
// listeners are up.
s.started = true
@@ -90,20 +89,6 @@ func (s *ServerTLS) Shutdown(ctx context.Context) (err error) {
return s.ServerDNS.Shutdown(ctx)
}
// startServeTCP starts the TCP listen loop and handles errors if any.
func (s *ServerTLS) startServeTCP(ctx context.Context) {
// We do not recover from panics here since if this go routine panics
// the application won't be able to continue listening to DoT
defer s.handlePanicAndExit(ctx)
s.baseLogger.InfoContext(ctx, "starting listening tls")
err := s.serveTCP(ctx, s.tcpListener)
if err != nil {
s.baseLogger.WarnContext(ctx, "listening tls failed", slogutil.KeyError, err)
}
}
// listenTLS creates the TLS listener for s.addr.
func (s *ServerTLS) listenTLS(ctx context.Context) (err error) {
l, err := s.listenConfig.Listen(ctx, "tcp", s.addr)

View File

@@ -0,0 +1,81 @@
package dnsserver
import (
"fmt"
"log/slog"
"sync"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/panjf2000/ants/v2"
)
// task is a function that is intended to be used as a goroutine in a
// [taskPool].
type task func()
// taskPool is a wrapper around [ants.Pool] with convenience methods for using
// it with [sync.WaitGroup]s.
type taskPool struct {
ants.Pool
}
// taskPoolConfig is the configuration for a [taskPool].
type taskPoolConfig struct {
// logger is used for logging the operation of the task pool. It must not
// be nil.
logger *slog.Logger
}
// mustNewTaskPool creates a new properly initialized *taskPool configured
// optimally for using it in DNS servers. It panics if there are errors.
// c must not be nil and must be valid.
func mustNewTaskPool(c *taskPoolConfig) (p *taskPool) {
pool, err := ants.NewPool(0, ants.WithOptions(ants.Options{
ExpiryDuration: time.Minute,
PreAlloc: false,
Nonblocking: true,
DisablePurge: false,
Logger: &antsLogger{
logger: c.logger,
},
}))
errors.Check(err)
return &taskPool{
Pool: *pool,
}
}
// submitWG is a convenience method that submits t to the pool and accounts
// for it in wg. All arguments must not be nil.
func (p *taskPool) submitWG(wg *sync.WaitGroup, t task) (err error) {
wg.Add(1)
err = p.Submit(func() {
defer wg.Done()
t()
})
if err != nil {
// Decrease the counter if the goroutine hasn't been started.
wg.Done()
}
return err
}
// antsLogger implements the [ants.Logger] interface and writes everything
// to its logger.
type antsLogger struct {
logger *slog.Logger
}
// type check
var _ ants.Logger = (*antsLogger)(nil)
// Printf implements the [ants.Logger] interface for *antsLogger.
func (l *antsLogger) Printf(format string, args ...any) {
l.logger.Info("ants pool", slogutil.KeyMessage, fmt.Sprintf(format, args...))
}

View File

@@ -1,43 +0,0 @@
package dnsserver
import (
"fmt"
"log/slog"
"time"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/panjf2000/ants/v2"
)
// antsLogger implements the [ants.Logger] interface and writes everything
// to its logger.
type antsLogger struct {
logger *slog.Logger
}
// type check
var _ ants.Logger = (*antsLogger)(nil)
// Printf implements the [ants.Logger] interface for *antsLogger.
func (l *antsLogger) Printf(format string, args ...interface{}) {
l.logger.Info("ants pool", slogutil.KeyMessage, fmt.Sprintf(format, args...))
}
// mustNewPoolNonblocking creates a new instance of [*ants.Pool] configured
// optimally for using it in DNS servers. It panics if there are errors.
// logger must not be nil.
func mustNewPoolNonblocking(logger *slog.Logger) (p *ants.Pool) {
p, err := ants.NewPool(0, ants.WithOptions(ants.Options{
ExpiryDuration: time.Minute,
PreAlloc: false,
Nonblocking: true,
DisablePurge: false,
Logger: &antsLogger{
logger: logger,
},
}))
errors.Check(err)
return p
}

View File

@@ -126,7 +126,11 @@ func newTestService(
}
geoIP := agdtest.NewGeoIP()
geoIP.OnData = func(host string, _ netip.Addr) (l *geoip.Location, err error) {
geoIP.OnData = func(
_ context.Context,
host string,
_ netip.Addr,
) (l *geoip.Location, err error) {
testutil.RequireSend(pt, geoIPCh, host, dnssvctest.Timeout)
return loc, nil

View File

@@ -118,7 +118,11 @@ func TestMiddleware_Wrap(t *testing.T) {
}
geoIP := agdtest.NewGeoIP()
geoIP.OnData = func(host string, addr netip.Addr) (l *geoip.Location, err error) {
geoIP.OnData = func(
_ context.Context,
host string,
addr netip.Addr,
) (l *geoip.Location, err error) {
pt := testutil.PanicT{}
require.Equal(pt, dnssvctest.Domain, host)
if addr.Is4() {
@@ -419,7 +423,11 @@ func TestMiddleware_Wrap_filtering(t *testing.T) {
)
geoIP := agdtest.NewGeoIP()
geoIP.OnData = func(_ string, _ netip.Addr) (l *geoip.Location, err error) {
geoIP.OnData = func(
_ context.Context,
_ string,
_ netip.Addr,
) (l *geoip.Location, err error) {
return nil, nil
}

View File

@@ -226,7 +226,7 @@ func ipFromHTTPSRRKV(kv dns.SVCBKeyValue) (fam netutil.AddrFamily, netIP net.IP)
// country is a wrapper around the GeoIP call that contains the handling of
// non-critical GeoIP errors.
func (mw *Middleware) country(ctx context.Context, host string, ip netip.Addr) (c geoip.Country) {
l, err := mw.geoIP.Data(host, ip)
l, err := mw.geoIP.Data(ctx, host, ip)
if err != nil {
// Consider GeoIP errors non-critical.
errcoll.Collect(ctx, mw.errColl, mw.logger, "getting geoip data", err)

View File

@@ -135,7 +135,11 @@ func TestMiddleware_recordQueryInfo_respCtry(t *testing.T) {
}
geoIP := agdtest.NewGeoIP()
geoIP.OnData = func(_ string, _ netip.Addr) (l *geoip.Location, err error) {
geoIP.OnData = func(
_ context.Context,
_ string,
_ netip.Addr,
) (l *geoip.Location, err error) {
if !tc.wantGeoIP {
t.Error("unexpected call to geoip")
}

View File

@@ -56,7 +56,7 @@ func TestMiddleware_Wrap_access(t *testing.T) {
require.NoError(t, accessErr)
geoIP := agdtest.NewGeoIP()
geoIP.OnData = func(_ string, _ netip.Addr) (l *geoip.Location, err error) {
geoIP.OnData = func(_ context.Context, _ string, _ netip.Addr) (l *geoip.Location, err error) {
return nil, nil
}

View File

@@ -102,7 +102,7 @@ func (mw *Middleware) locationData(
ip netip.Addr,
typ string,
) (l *geoip.Location) {
l, err := mw.geoIP.Data("", ip)
l, err := mw.geoIP.Data(ctx, "", ip)
if err != nil {
// Consider GeoIP errors non-critical. Report and go on.
err = fmt.Errorf("getting data for %s ip: %w", typ, err)

View File

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,8 @@ func main() {
resp := errors.Must(c.Do(req))
defer slogutil.CloseAndLog(ctx, logger, resp.Body, slog.LevelError)
errors.Check(agdhttp.CheckStatus(resp, http.StatusOK))
out := errors.Must(os.OpenFile("./ecsblocklist.go", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o664))
defer slogutil.CloseAndLog(ctx, logger, out, slog.LevelError)
@@ -48,8 +50,7 @@ func main() {
tmpl := template.Must(template.New("main").Parse(tmplStr))
err := tmpl.Execute(out, lines)
errors.Check(err)
errors.Check(tmpl.Execute(out, lines))
}
// fakeECSBlocklistURL is the default URL from where to get ECS fake domains.

View File

@@ -322,7 +322,7 @@ func (mh *mwHandler) ServeDNS(
cr.subnet = netutil.ZeroPrefix(ecsFam)
} else {
loc := locFromReq(ri)
cr.subnet, err = mw.geoIP.SubnetByLocation(loc, ecsFam)
cr.subnet, err = mw.geoIP.SubnetByLocation(ctx, loc, ecsFam)
if err != nil {
return fmt.Errorf(
"getting subnet for country %s (family: %d): %w",

View File

@@ -670,6 +670,7 @@ func newWithCache(
// TODO(a.garipov): Actually test ASNs once we have the data.
geoIP.OnSubnetByLocation = func(
_ context.Context,
l *geoip.Location,
_ netutil.AddrFamily,
) (n netip.Prefix, err error) {

View File

@@ -25,7 +25,7 @@ import (
func Init(l *slog.Logger, reg prometheus.Registerer) (err error) {
expStr := os.Getenv("EXPERIMENTS")
if expStr == "" {
return
return nil
}
expIDs := stringutil.SplitTrimmed(expStr, ",")

View File

@@ -71,7 +71,7 @@ func TestFilter(t *testing.T) {
require.True(t, ok)
require.NotNil(t, res.NetworkRule)
assert.Equal(t, tc.wantRuleStr, res.NetworkRule.RuleText)
assert.Equal(t, tc.wantRuleStr, res.NetworkRule.Text())
})
}
}

View File

@@ -81,7 +81,10 @@ type StandardAccess struct {
// NewStandardAccess creates a new properly initialized standard access. c must
// be valid. It uses the latest cached settings if available, use the
// [StandardAccess.Refresh] method to update them.
func NewStandardAccess(ctx context.Context, c *StandardAccessConfig) (s *StandardAccess, err error) {
func NewStandardAccess(
ctx context.Context,
c *StandardAccessConfig,
) (s *StandardAccess, err error) {
cachePath := filepath.Join(c.CacheDir, indexFileNameStandardProfileAccess)
refr, err := refreshable.New(&refreshable.Config{

View File

@@ -75,6 +75,8 @@ func (s *Storage) Hashes(prefs []Prefix) (hashes []string) {
//
// The fact that we iterate over the [s.hashSuffixes] map twice shouldn't
// matter, since we assume that len(hps) will be below 5 most of the time.
//
// TODO(a.garipov): Reuse in pools.
b := &strings.Builder{}
b.Grow(l * hashEncLen)

View File

@@ -106,15 +106,14 @@ func BenchmarkStorage_Hashes(b *testing.B) {
}
// Most recent results:
//
// goos: darwin
// goarch: arm64
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix
// cpu: Apple M1 Pro
// BenchmarkStorage_Hashes/1-8 10519970 102.7 ns/op 80 B/op 2 allocs/op
// BenchmarkStorage_Hashes/2-8 10045784 118.1 ns/op 80 B/op 2 allocs/op
// BenchmarkStorage_Hashes/3-8 9088449 129.2 ns/op 80 B/op 2 allocs/op
// BenchmarkStorage_Hashes/4-8 8577764 139.4 ns/op 80 B/op 2 allocs/op
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkStorage_Hashes/1-16 6934150 174.6 ns/op 80 B/op 2 allocs/op
// BenchmarkStorage_Hashes/2-16 5912390 203.5 ns/op 80 B/op 2 allocs/op
// BenchmarkStorage_Hashes/3-16 5288085 225.6 ns/op 80 B/op 2 allocs/op
// BenchmarkStorage_Hashes/4-16 4613066 259.9 ns/op 80 B/op 2 allocs/op
}
func BenchmarkStorage_ResetHosts(b *testing.B) {
@@ -137,10 +136,9 @@ func BenchmarkStorage_ResetHosts(b *testing.B) {
require.NoError(b, err)
// Most recent results:
//
// goos: darwin
// goarch: arm64
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/hashprefix
// cpu: Apple M1 Pro
// BenchmarkStorage_ResetHosts-8 8610 128756 ns/op 118380 B/op 1009 allocs/op
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkStorage_ResetHosts-16 6469 186021 ns/op 118392 B/op 1009 allocs/op
}

View File

@@ -39,10 +39,9 @@ func BenchmarkFilter_FilterReqWithRuleLists(b *testing.B) {
assert.NotNil(b, result)
// Most recent results:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/composite
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkFilter_FilterReqWithRuleLists-16 807964 1904 ns/op 469 B/op 8 allocs/op
// BenchmarkFilter_FilterReqWithRuleLists-16 748243 1394 ns/op 468 B/op 8 allocs/op
}

View File

@@ -13,6 +13,8 @@ import (
// urlFilterResultCollector is an entity simplifying the collection and
// compilation of urlfilter results. It contains per-pointer indexes of the IDs
// of filters producing network and host rules.
//
// TODO(a.garipov): Reuse these structures.
type urlFilterResultCollector struct {
netRuleIDs map[*rules.NetworkRule]filter.ID
hostRuleIDs map[*rules.HostRule]filter.ID
@@ -109,7 +111,7 @@ func (c *urlFilterResultCollector) netRuleDataToResult(nr *rules.NetworkRule) (r
rule = filter.RuleText(svcID)
} else {
rule = filter.RuleText(nr.RuleText)
rule = filter.RuleText(nr.Text())
}
if nr.Whitelist {
@@ -174,7 +176,7 @@ func (c *urlFilterResultCollector) hostRuleDataToResult(hr *rules.HostRule) (res
rule = filter.RuleText(svcID)
} else {
rule = filter.RuleText(hr.RuleText)
rule = filter.RuleText(hr.Text())
}
return &filter.ResultBlocked{

View File

@@ -23,9 +23,14 @@ func ProcessDNSRewrites(
return nil
}
dnsRewriteResult := processDNSRewriteRules(dnsr)
// Use a value and not a pointer so that the dnsRewriteResult value stays on
// the stack and not produce an extra allocation.
dnsRwRes := dnsRewriteResult{
Response: dnsRewriteResultResponse{},
}
processDNSRewriteRules(dnsr, &dnsRwRes)
if resCanonName := dnsRewriteResult.CanonName; resCanonName != "" {
if resCanonName := dnsRwRes.CanonName; resCanonName != "" {
// Rewrite the question name to a matched CNAME.
if strings.EqualFold(resCanonName, req.Host) {
// A rewrite of a host to itself.
@@ -38,24 +43,24 @@ func ProcessDNSRewrites(
return &filter.ResultModifiedRequest{
Msg: modReq,
List: id,
Rule: dnsRewriteResult.ResRuleText,
Rule: dnsRwRes.ResRuleText,
}
}
if dnsRewriteResult.RCode != dns.RcodeSuccess {
if dnsRwRes.RCode != dns.RcodeSuccess {
// #nosec G115 -- The value of dnsRewriteResult.RCode comes from the
// urlfilter package, where it either parsed by [dns.StringToRcode] or
// defined statically.
resp := req.Messages.NewBlockedRespRCode(req.DNS, dnsmsg.RCode(dnsRewriteResult.RCode))
resp := req.Messages.NewBlockedRespRCode(req.DNS, dnsmsg.RCode(dnsRwRes.RCode))
return &filter.ResultModifiedResponse{
Msg: resp,
List: id,
Rule: dnsRewriteResult.ResRuleText,
Rule: dnsRwRes.ResRuleText,
}
}
resp, err := filterDNSRewrite(req, dnsRewriteResult)
resp, err := filterDNSRewrite(req, &dnsRwRes)
if err != nil {
return nil
}
@@ -71,7 +76,6 @@ type dnsRewriteResult struct {
Response dnsRewriteResultResponse
CanonName string
ResRuleText filter.RuleText
Rules []*rules.NetworkRule
RCode rules.RCode
}
@@ -79,48 +83,39 @@ type dnsRewriteResult struct {
// the server returns.
type dnsRewriteResultResponse map[rules.RRType][]rules.RRValue
// processDNSRewriteRules processes DNS rewrite rules in dnsr. The result will
// have either CanonName or RCode or Response set.
//
// TODO(a.garipov): Reuse dnsRewriteResult structures.
func processDNSRewriteRules(dnsr []*rules.NetworkRule) (res *dnsRewriteResult) {
dnsrr := &dnsRewriteResult{
Response: dnsRewriteResultResponse{},
}
// processDNSRewriteRules processes DNS rewrite rules in dnsr. res will have
// either CanonName or RCode or Response set. res and res.Response must not be
// nil.
func processDNSRewriteRules(dnsr []*rules.NetworkRule, res *dnsRewriteResult) {
for _, rule := range dnsr {
dr := rule.DNSRewrite
if dr.NewCNAME != "" {
// NewCNAME rules have a higher priority than other rules.
return &dnsRewriteResult{
ResRuleText: filter.RuleText(rule.RuleText),
CanonName: dr.NewCNAME,
}
res.CanonName = dr.NewCNAME
res.ResRuleText = filter.RuleText(rule.Text())
return
}
switch dr.RCode {
case dns.RcodeSuccess:
dnsrr.RCode = dr.RCode
dnsrr.Response[dr.RRType] = append(dnsrr.Response[dr.RRType], dr.Value)
dnsrr.Rules = append(dnsrr.Rules, rule)
default:
// RcodeRefused and other such codes have higher priority. Return
// immediately.
return &dnsRewriteResult{
ResRuleText: filter.RuleText(rule.RuleText),
RCode: dr.RCode,
}
if dr.RCode != dns.RcodeSuccess {
// [dns.RcodeRefused] and other such codes have higher priority.
// Set and return immediately.
res.ResRuleText = filter.RuleText(rule.Text())
res.RCode = dr.RCode
return
}
res.Response[dr.RRType] = append(res.Response[dr.RRType], dr.Value)
res.RCode = dr.RCode
}
return dnsrr
}
// filterDNSRewrite handles dnsrewrite filters. It constructs a DNS response
// and returns it. dnsrr.RCode should be [dns.RcodeSuccess] and contain a
// non-empty dnsrr.Response.
func filterDNSRewrite(req *filter.Request, dnsrr *dnsRewriteResult) (resp *dns.Msg, err error) {
if dnsrr.Response == nil {
// and returns it. req and res must not be nil. res.RCode should be
// [dns.RcodeSuccess] and contain a non-empty Response.
func filterDNSRewrite(req *filter.Request, res *dnsRewriteResult) (resp *dns.Msg, err error) {
if res.Response == nil {
return nil, errors.Error("no dns rewrite rule responses")
}
@@ -130,7 +125,7 @@ func filterDNSRewrite(req *filter.Request, dnsrr *dnsRewriteResult) (resp *dns.M
resp = req.Messages.NewBlockedRespRCode(dnsReq, dns.RcodeSuccess)
rr := dnsReq.Question[0].Qtype
values := dnsrr.Response[rr]
values := res.Response[rr]
for i, v := range values {
var ans dns.RR
ans, err = filterDNSRewriteResponse(req, rr, v)

View File

@@ -132,7 +132,6 @@ func BenchmarkRefreshable_SetURLFilterResult(b *testing.B) {
)
ctx := testutil.ContextWithTimeout(b, filtertest.Timeout)
res := &urlfilter.DNSResult{}
b.Run("blocked", func(b *testing.B) {
req := &urlfilter.DNSRequest{
@@ -141,7 +140,12 @@ func BenchmarkRefreshable_SetURLFilterResult(b *testing.B) {
DNSType: dns.TypeA,
}
var ok bool
res := &urlfilter.DNSResult{}
// Warmup to fill the slices.
ok := rl.SetURLFilterResult(ctx, req, res)
require.True(b, ok)
b.ReportAllocs()
for b.Loop() {
res.Reset()
@@ -158,7 +162,12 @@ func BenchmarkRefreshable_SetURLFilterResult(b *testing.B) {
DNSType: dns.TypeA,
}
var ok bool
res := &urlfilter.DNSResult{}
// Warmup to fill the slices.
ok := rl.SetURLFilterResult(ctx, req, res)
require.False(b, ok)
b.ReportAllocs()
for b.Loop() {
res.Reset()
@@ -169,11 +178,10 @@ func BenchmarkRefreshable_SetURLFilterResult(b *testing.B) {
})
// Most recent results:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkRefreshable_SetURLFilterResult/blocked-16 1340384 918.6 ns/op 24 B/op 1 allocs/op
// BenchmarkRefreshable_SetURLFilterResult/other-16 2127038 589.3 ns/op 24 B/op 1 allocs/op
// BenchmarkRefreshable_SetURLFilterResult/blocked-16 1352236 887.3 ns/op 24 B/op 1 allocs/op
// BenchmarkRefreshable_SetURLFilterResult/other-16 2772519 432.6 ns/op 24 B/op 1 allocs/op
}

View File

@@ -96,7 +96,7 @@ func (f *baseFilter) SetURLFilterResult(
return false
}
shallowCloneInto(res, cachedRes)
cachedRes.ShallowCloneInto(res)
return true
}
@@ -111,7 +111,7 @@ func (f *baseFilter) SetURLFilterResult(
if ok {
cachedRes = &urlfilter.DNSResult{}
shallowCloneInto(cachedRes, res)
res.ShallowCloneInto(cachedRes)
}
f.cache.Set(cacheKey, cachedRes)
@@ -119,17 +119,6 @@ func (f *baseFilter) SetURLFilterResult(
return ok
}
// shallowCloneInto sets properties in other, as if making a shallow clone.
// other must not be nil and should be empty or reset using [DNSResult.Reset].
//
// TODO(a.garipov): Add to urlfilter.
func shallowCloneInto(other, res *urlfilter.DNSResult) {
other.NetworkRule = res.NetworkRule
other.HostRulesV4 = append(other.HostRulesV4, res.HostRulesV4...)
other.HostRulesV6 = append(other.HostRulesV6, res.HostRulesV6...)
other.NetworkRules = append(other.NetworkRules, res.NetworkRules...)
}
// ID returns the filter list ID of this rule list filter, as well as the ID of
// the blocked service, if any.
func (f *baseFilter) ID() (id filter.ID, svcID filter.BlockedServiceID) {

View File

@@ -41,7 +41,6 @@ func BenchmarkBaseFilter_SetURLFilterResult(b *testing.B) {
const qt = dns.TypeA
ctx := context.Background()
res := &urlfilter.DNSResult{}
b.Run("blocked", func(b *testing.B) {
req := &urlfilter.DNSRequest{
@@ -50,7 +49,12 @@ func BenchmarkBaseFilter_SetURLFilterResult(b *testing.B) {
DNSType: qt,
}
var ok bool
res := &urlfilter.DNSResult{}
// Warmup to fill the slices.
ok := f.SetURLFilterResult(ctx, req, res)
require.True(b, ok)
b.ReportAllocs()
for b.Loop() {
res.Reset()
@@ -67,7 +71,12 @@ func BenchmarkBaseFilter_SetURLFilterResult(b *testing.B) {
DNSType: qt,
}
var ok bool
res := &urlfilter.DNSResult{}
// Warmup to fill the slices.
ok := f.SetURLFilterResult(ctx, req, res)
require.False(b, ok)
b.ReportAllocs()
for b.Loop() {
res.Reset()
@@ -78,11 +87,10 @@ func BenchmarkBaseFilter_SetURLFilterResult(b *testing.B) {
})
// Most recent results:
//
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/rulelist
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkBaseFilter_SetURLFilterResult/blocked-16 906486 1372 ns/op 24 B/op 1 allocs/op
// BenchmarkBaseFilter_SetURLFilterResult/other-16 2203561 609.1 ns/op 24 B/op 1 allocs/op
// BenchmarkBaseFilter_SetURLFilterResult/blocked-16 911409 1315 ns/op 24 B/op 1 allocs/op
// BenchmarkBaseFilter_SetURLFilterResult/other-16 2824462 425.0 ns/op 24 B/op 1 allocs/op
}

View File

@@ -146,9 +146,6 @@ func BenchmarkFilter_FilterRequestUF(b *testing.B) {
f := newTestFilter(b)
b.Run("no_match", func(b *testing.B) {
var res filter.Result
var err error
ctx := testutil.ContextWithTimeout(b, filtertest.Timeout)
req := newRequest(b, testOther, qt)
@@ -159,20 +156,22 @@ func BenchmarkFilter_FilterRequestUF(b *testing.B) {
ufRes := &urlfilter.DNSResult{}
// Warmup to fill the slices.
res, err := f.FilterRequestUF(ctx, req, ufReq, ufRes)
require.NoError(b, err)
require.Nil(b, res)
b.ReportAllocs()
for b.Loop() {
ufRes.Reset()
res, err = f.FilterRequestUF(ctx, req, ufReq, ufRes)
}
assert.NoError(b, err)
assert.Nil(b, res)
require.NoError(b, err)
require.Nil(b, res)
})
b.Run("ip", func(b *testing.B) {
var res filter.Result
var err error
ctx := testutil.ContextWithTimeout(b, filtertest.Timeout)
req := newRequest(b, testEngineWithIP, qt)
@@ -183,20 +182,22 @@ func BenchmarkFilter_FilterRequestUF(b *testing.B) {
ufRes := &urlfilter.DNSResult{}
// Warmup to fill the slices.
res, err := f.FilterRequestUF(ctx, req, ufReq, ufRes)
require.NoError(b, err)
require.NotNil(b, res)
b.ReportAllocs()
for b.Loop() {
ufRes.Reset()
res, err = f.FilterRequestUF(ctx, req, ufReq, ufRes)
}
assert.NoError(b, err)
assert.NotNil(b, res)
require.NoError(b, err)
require.NotNil(b, res)
})
b.Run("domain", func(b *testing.B) {
var res filter.Result
var err error
ctx := testutil.ContextWithTimeout(b, filtertest.Timeout)
req := newRequest(b, testEngineWithDomain, qt)
@@ -207,25 +208,29 @@ func BenchmarkFilter_FilterRequestUF(b *testing.B) {
ufRes := &urlfilter.DNSResult{}
// Warmup to fill the slices.
res, err := f.FilterRequestUF(ctx, req, ufReq, ufRes)
require.NoError(b, err)
require.NotNil(b, res)
b.ReportAllocs()
for b.Loop() {
ufRes.Reset()
res, err = f.FilterRequestUF(ctx, req, ufReq, ufRes)
}
assert.NoError(b, err)
assert.NotNil(b, res)
require.NoError(b, err)
require.NotNil(b, res)
})
// Most recent results:
//
// goos: darwin
// goarch: arm64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/safesearch
// cpu: Apple M1 Pro
// BenchmarkFilter_FilterRequestUF/no_match-8 27804783 43.23 ns/op 0 B/op 0 allocs/op
// BenchmarkFilter_FilterRequestUF/ip-8 3136018 382.3 ns/op 672 B/op 11 allocs/op
// BenchmarkFilter_FilterRequestUF/domain-8 4929343 237.8 ns/op 400 B/op 7 allocs/op
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/filter/internal/safesearch
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkFilter_FilterRequestUF/no_match-16 18863545 62.65 ns/op 0 B/op 0 allocs/op
// BenchmarkFilter_FilterRequestUF/ip-16 1516423 784.0 ns/op 664 B/op 10 allocs/op
// BenchmarkFilter_FilterRequestUF/domain-16 2859573 424.6 ns/op 320 B/op 6 allocs/op
}
// newTestFilter creates a new [*safesearch.Filter] for testing, and refreshes

View File

@@ -35,15 +35,13 @@ func main() {
resp := errors.Must(c.Do(req))
defer slogutil.CloseAndLog(ctx, logger, resp.Body, slog.LevelError)
err := agdhttp.CheckStatus(resp, http.StatusOK)
errors.Check(err)
errors.Check(agdhttp.CheckStatus(resp, http.StatusOK))
out := errors.Must(os.OpenFile("./asntops.go", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o664))
defer slogutil.CloseAndLog(ctx, logger, out, slog.LevelError)
defaultCountryTopASNs := map[geoip.Country][]geoip.ASN{}
err = json.NewDecoder(resp.Body).Decode(&defaultCountryTopASNs)
errors.Check(err)
errors.Check(json.NewDecoder(resp.Body).Decode(&defaultCountryTopASNs))
// Don't use a *container.MapSet here, because the map is iterated over in
// the template.
@@ -67,9 +65,7 @@ func main() {
}
tmpl := template.Must(template.New("main").Parse(tmplStr))
err = tmpl.Execute(out, tmplData)
errors.Check(err)
errors.Check(tmpl.Execute(out, tmplData))
}
// countriesASNURL is the default URL to get the per-country top ASN statistics

View File

@@ -36,6 +36,8 @@ func main() {
resp := errors.Must(c.Do(req))
defer slogutil.CloseAndLog(ctx, logger, resp.Body, slog.LevelError)
errors.Check(agdhttp.CheckStatus(resp, http.StatusOK))
out := errors.Must(os.OpenFile("./country.go", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o664))
defer slogutil.CloseAndLog(ctx, logger, out, slog.LevelError)
@@ -52,9 +54,7 @@ func main() {
})
tmpl := template.Must(template.New("main").Parse(tmplStr))
err := tmpl.Execute(out, rows)
errors.Check(err)
errors.Check(tmpl.Execute(out, rows))
}
// csvURL is the default URL of the information about country codes.

View File

@@ -206,7 +206,11 @@ var _ Interface = (*File)(nil)
// subnet is returned. If the information about the most used ASNs is not
// available, the first subnet from the country that is broad enough (see
// resetCountrySubnets) is chosen.
func (f *File) SubnetByLocation(l *Location, fam netutil.AddrFamily) (n netip.Prefix, err error) {
func (f *File) SubnetByLocation(
_ context.Context,
l *Location,
fam netutil.AddrFamily,
) (n netip.Prefix, err error) {
var ctrySubnets countrySubnets
var locSubnets locationSubnets
@@ -247,10 +251,7 @@ func (f *File) SubnetByLocation(l *Location, fam netutil.AddrFamily) (n netip.Pr
// Data implements the Interface interface for *File. If ip is netip.Addr{},
// Data tries to lookup and return the data based on host, unless it's empty.
func (f *File) Data(host string, ip netip.Addr) (l *Location, err error) {
// TODO(e.burkov): Add context to the [Interface] methods.
ctx := context.TODO()
func (f *File) Data(ctx context.Context, host string, ip netip.Addr) (l *Location, err error) {
if ip == (netip.Addr{}) {
return f.dataByHost(ctx, host), nil
} else if ip.Is4In6() {
@@ -425,14 +426,11 @@ func (f *File) resetSubnetMappings(
asn *maxminddb.Reader,
country *maxminddb.Reader,
) (err error) {
var wg sync.WaitGroup
wg.Add(2)
wg := &sync.WaitGroup{}
var locErr, ctryErr error
go func() {
defer wg.Done()
wg.Go(func() {
var ipv4, ipv6 locationSubnets
ipv4, ipv6, locErr = f.resetLocationSubnets(ctx, asn, country)
@@ -448,11 +446,9 @@ func (f *File) resetSubnetMappings(
defer f.mu.Unlock()
f.ipv4LocationSubnets, f.ipv6LocationSubnets = ipv4, ipv6
}()
go func() {
defer wg.Done()
})
wg.Go(func() {
var ipv4, ipv6 countrySubnets
ipv4, ipv6, ctryErr = resetCountrySubnets(ctx, f.logger, country)
@@ -466,7 +462,7 @@ func (f *File) resetSubnetMappings(
defer f.mu.Unlock()
f.ipv4CountrySubnets, f.ipv6CountrySubnets = ipv4, ipv6
}()
})
wg.Wait()

View File

@@ -43,13 +43,14 @@ func TestFile_Data_cityDB(t *testing.T) {
}
g := newFile(t, conf)
ctx := testutil.ContextWithTimeout(t, testTimeout)
d, err := g.Data(testHost, testIPWithASN)
d, err := g.Data(ctx, testHost, testIPWithASN)
require.NoError(t, err)
assert.Equal(t, testASN, d.ASN)
d, err = g.Data(testHost, testIPWithSubdiv)
d, err = g.Data(ctx, testHost, testIPWithSubdiv)
require.NoError(t, err)
assert.Equal(t, testCtry, d.Country)
@@ -71,13 +72,14 @@ func TestFile_Data_countryDB(t *testing.T) {
}
g := newFile(t, conf)
ctx := testutil.ContextWithTimeout(t, testTimeout)
d, err := g.Data(testHost, testIPWithASN)
d, err := g.Data(ctx, testHost, testIPWithASN)
require.NoError(t, err)
assert.Equal(t, testASN, d.ASN)
d, err = g.Data(testHost, testIPWithSubdiv)
d, err = g.Data(ctx, testHost, testIPWithSubdiv)
require.NoError(t, err)
assert.Equal(t, testCtry, d.Country)
@@ -100,17 +102,19 @@ func TestFile_Data_hostCache(t *testing.T) {
g := newFile(t, conf)
d, err := g.Data(testHost, testIPWithASN)
ctx := testutil.ContextWithTimeout(t, testTimeout)
d, err := g.Data(ctx, testHost, testIPWithASN)
require.NoError(t, err)
assert.Equal(t, testASN, d.ASN)
d, err = g.Data(testHost, netip.Addr{})
d, err = g.Data(ctx, testHost, netip.Addr{})
require.NoError(t, err)
assert.Equal(t, testASN, d.ASN)
d, err = g.Data(testOtherHost, netip.Addr{})
d, err = g.Data(ctx, testOtherHost, netip.Addr{})
require.NoError(t, err)
assert.Nil(t, d)
@@ -177,12 +181,15 @@ func TestFile_SubnetByLocation(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctrySubnet, err := g.SubnetByLocation(&geoip.Location{
Country: tc.country,
Continent: "",
TopSubdivision: tc.subdiv,
ASN: tc.asn,
}, tc.fam)
ctx := testutil.ContextWithTimeout(t, testTimeout)
ctrySubnet, err := g.SubnetByLocation(
ctx,
&geoip.Location{
Country: tc.country,
Continent: "",
TopSubdivision: tc.subdiv,
ASN: tc.asn,
}, tc.fam)
require.NoError(t, err)
assert.Equal(t, tc.want, ctrySubnet)
@@ -204,6 +211,7 @@ func BenchmarkFile_Data(b *testing.B) {
}
g := newFile(b, conf)
ctx := testutil.ContextWithTimeout(b, testTimeout)
ipCountry1 := testIPWithCountry
@@ -218,7 +226,7 @@ func BenchmarkFile_Data(b *testing.B) {
b.Run("cache", func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
loc, err = g.Data(testHost, testIPWithASN)
loc, err = g.Data(ctx, testHost, testIPWithASN)
}
assert.Equal(b, testASN, loc.ASN)
@@ -231,9 +239,9 @@ func BenchmarkFile_Data(b *testing.B) {
for i := 0; b.Loop(); i++ {
// Alternate between the two IPs to force cache misses.
if i%2 == 0 {
loc, err = g.Data(testHost, ipCountry1)
loc, err = g.Data(ctx, testHost, ipCountry1)
} else {
loc, err = g.Data(testHost, ipCountry2)
loc, err = g.Data(ctx, testHost, ipCountry2)
}
}

View File

@@ -2,6 +2,7 @@
package geoip
import (
"context"
"net/netip"
"github.com/AdguardTeam/golibs/netutil"
@@ -13,9 +14,13 @@ type Interface interface {
// SubnetByLocation returns the default subnet for location, if there is
// one. If there isn't, n is an unspecified subnet. fam must be either
// [netutil.AddrFamilyIPv4] or [netutil.AddrFamilyIPv6].
SubnetByLocation(l *Location, fam netutil.AddrFamily) (n netip.Prefix, err error)
SubnetByLocation(
ctx context.Context,
l *Location,
fam netutil.AddrFamily,
) (n netip.Prefix, err error)
// Data returns the GeoIP data for ip. It may use host to get cached GeoIP
// data if ip is netip.Addr{}.
Data(host string, ip netip.Addr) (l *Location, err error)
Data(ctx context.Context, host string, ip netip.Addr) (l *Location, err error)
}

View File

@@ -124,7 +124,7 @@ func IncrementCond(cond bool, trueCounter, falseCounter prometheus.Counter) {
// SetAdditionalInfo does nothing. reg must not be nil.
func SetAdditionalInfo(reg prometheus.Registerer, info map[string]string) (err error) {
if info == nil {
return
return nil
}
gauge := prometheus.NewGauge(prometheus.GaugeOpts{

View File

@@ -16,7 +16,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
"github.com/AdguardTeam/AdGuardDNS/internal/rulestat"
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
"github.com/AdguardTeam/AdGuardDNS/internal/websvc"
@@ -48,7 +47,6 @@ var (
_ geoip.Metrics = (*metrics.GeoIP)(nil)
_ hashprefix.Metrics = (*metrics.HashPrefixFilter)(nil)
_ profiledb.Metrics = (*metrics.ProfileDB)(nil)
_ querylog.Metrics = (*metrics.QueryLog)(nil)
_ rulestat.Metrics = (*metrics.RuleStat)(nil)
_ tlsconfig.CustomDomainDBMetrics = (*metrics.CustomDomainDB)(nil)
_ tlsconfig.ManagerMetrics = (*metrics.TLSConfigManager)(nil)

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/c2h5oh/datasize"
@@ -82,19 +83,17 @@ func NewQueryLog(namespace string, reg prometheus.Registerer) (m *QueryLog, err
return m, nil
}
// IncrementItemsCount implements the [querylog.Metrics] interface for
// *QueryLog.
func (m *QueryLog) IncrementItemsCount(_ context.Context) {
m.itemsTotal.Inc()
}
// type check
var _ querylog.Metrics = (*QueryLog)(nil)
// ObserveItemSize implements the [querylog.Metrics] interface for *QueryLog.
func (m *QueryLog) ObserveItemSize(_ context.Context, size datasize.ByteSize) {
m.itemSize.Observe(float64(size))
}
// ObserveWriteDuration implements the [querylog.Metrics] interface for
// ObserveWrite implements the [querylog.Metrics] interface for
// *QueryLog.
func (m *QueryLog) ObserveWriteDuration(_ context.Context, dur time.Duration) {
func (m *QueryLog) ObserveWrite(_ context.Context, dur time.Duration) {
m.itemsTotal.Inc()
m.writeDuration.Observe(dur.Seconds())
}

View File

@@ -284,16 +284,13 @@ func matchServerNames(sni string, devDomains []string, srvCerts []*tls.Certifica
// matchDeviceDomains matches sni to device domains.
func matchDeviceDomains(sni string, domains []string) (matchedDomain string) {
matchedDomain = ""
for _, domain := range domains {
if netutil.IsImmediateSubdomain(sni, domain) {
matchedDomain = domain
break
return domain
}
}
return matchedDomain
return ""
}
// matchSrvCerts matches sni to DNSNames in srvCerts.

View File

@@ -293,7 +293,7 @@ func (db *Default) setProfileCerts(ctx context.Context, p *agd.Profile) (err err
if !cd.Enabled {
// Assume that the expired pending paths are cleaned up either by
// [db.IsValidWellKnownRequest] or in a full sync.
return
return nil
}
var errs []error

View File

@@ -287,8 +287,9 @@ func (x *CustomDomainConfig) toInternal() (c *agd.CustomDomainConfig, err error)
state = &agd.CustomDomainStateCurrent{
NotBefore: s.StateCurrent.NotBefore.AsTime(),
NotAfter: s.StateCurrent.NotAfter.AsTime(),
CertName: s.StateCurrent.CertName,
Enabled: s.StateCurrent.Enabled,
// Consider certificate names to have been prevalidated.
CertName: agd.CertificateName(s.StateCurrent.CertName),
Enabled: s.StateCurrent.Enabled,
}
case *CustomDomainConfig_StatePending_:
state = &agd.CustomDomainStatePending{
@@ -524,7 +525,7 @@ func customDomainConfigsToProtobuf(
curr := &CustomDomainConfig_StateCurrent{
NotBefore: timestamppb.New(s.NotBefore),
NotAfter: timestamppb.New(s.NotAfter),
CertName: s.CertName,
CertName: string(s.CertName),
Enabled: s.Enabled,
}

View File

@@ -22,7 +22,7 @@ import (
"github.com/c2h5oh/datasize"
)
// FileSystemConfig is the configuration of the file system query log. All
// FileSystemConfig is the configuration of the filesystem query log. All
// fields must not be empty.
type FileSystemConfig struct {
// Logger is used for debug logging. It must not be nil.
@@ -32,6 +32,11 @@ type FileSystemConfig struct {
// not be nil.
Metrics Metrics
// Semaphore is used to limit the number of simultaneous writes. This is
// important to prevent excessive thread creation arising from i/o latency
// issues. It must not be nil.
Semaphore syncutil.Semaphore
// Path is the path to the log file. It must not be empty.
Path string
@@ -46,27 +51,24 @@ type entryBuffer struct {
buf *bytes.Buffer
}
// FileSystem is the file system implementation of the AdGuard DNS query log.
// FileSystem is the filesystem implementation of the AdGuard DNS query log.
type FileSystem struct {
// logger is used for debug logging.
logger *slog.Logger
// bufferPool is a pool with [*entryBuffer] instances used to avoid extra
// allocations when serializing query log items to JSON and writing them.
bufferPool *syncutil.Pool[entryBuffer]
logger *slog.Logger
// rng is used to generate random numbers for the "rn" property in the
// resulting JSON.
rng *rand.Rand
// metrics is used for the collection of the query log statistics.
metrics Metrics
// path is the path to the query log file.
path string
sema syncutil.Semaphore
path string
}
// NewFileSystem creates a new file system query log. The log is safe for
// NewFileSystem creates a new filesystem query log. The log is safe for
// concurrent use. c must not be nil.
func NewFileSystem(c *FileSystemConfig) (l *FileSystem) {
src := rand.NewChaCha8(c.RandSeed)
@@ -83,6 +85,7 @@ func NewFileSystem(c *FileSystemConfig) (l *FileSystem) {
}),
rng: rng,
metrics: c.Metrics,
sema: c.Semaphore,
path: c.Path,
}
}
@@ -104,10 +107,13 @@ func (l *FileSystem) Write(ctx context.Context, e *Entry) (err error) {
}()
startTime := time.Now()
defer func() {
l.metrics.ObserveWriteDuration(ctx, time.Since(startTime))
l.metrics.IncrementItemsCount(ctx)
}()
defer func() { l.metrics.ObserveWrite(ctx, time.Since(startTime)) }()
err = l.sema.Acquire(ctx)
if err != nil {
return fmt.Errorf("acquiring semaphore: %w", err)
}
defer l.sema.Release()
entBuf := l.bufferPool.Get()
defer l.bufferPool.Put(entBuf)
@@ -141,8 +147,7 @@ func (l *FileSystem) Write(ctx context.Context, e *Entry) (err error) {
RemoteIP: remoteIP,
}
var f *os.File
f, err = os.OpenFile(l.path, agd.DefaultWOFlags, agd.DefaultPerm)
f, err := os.OpenFile(l.path, agd.DefaultWOFlags, agd.DefaultPerm)
if err != nil {
return fmt.Errorf("opening query log file: %w", err)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -20,10 +21,11 @@ func TestFileSystem_Write(t *testing.T) {
require.NoError(t, err)
l := querylog.NewFileSystem(&querylog.FileSystemConfig{
Logger: slogutil.NewDiscardLogger(),
Metrics: querylog.EmptyMetrics{},
Path: f.Name(),
RandSeed: [32]byte{},
Logger: slogutil.NewDiscardLogger(),
Metrics: querylog.EmptyMetrics{},
Semaphore: syncutil.EmptySemaphore{},
Path: f.Name(),
RandSeed: [32]byte{},
})
ctx := context.Background()
@@ -110,10 +112,11 @@ func BenchmarkFileSystem_Write_file(b *testing.B) {
require.NoError(b, err)
l := querylog.NewFileSystem(&querylog.FileSystemConfig{
Logger: slogutil.NewDiscardLogger(),
Metrics: querylog.EmptyMetrics{},
Path: f.Name(),
RandSeed: [32]byte{},
Logger: slogutil.NewDiscardLogger(),
Metrics: querylog.EmptyMetrics{},
Semaphore: syncutil.EmptySemaphore{},
Path: f.Name(),
RandSeed: [32]byte{},
})
e := testEntry()

View File

@@ -10,15 +10,12 @@ import (
// Metrics is an interface that is used for the collection of the query log
// statistics.
type Metrics interface {
// IncrementItemsCount increments the total number of query log entries
// written.
IncrementItemsCount(ctx context.Context)
// ObserveItemSize stores the size of written query log entry.
ObserveItemSize(ctx context.Context, size datasize.ByteSize)
// ObserveWriteDuration stores the duration of the write operation.
ObserveWriteDuration(ctx context.Context, dur time.Duration)
// ObserveWrite stores the duration of the write operation and increments
// the write counter.
ObserveWrite(ctx context.Context, dur time.Duration)
}
// EmptyMetrics is the implementation of the [Metrics] interface that does
@@ -28,11 +25,8 @@ type EmptyMetrics struct{}
// type check
var _ Metrics = EmptyMetrics{}
// IncrementItemsCount implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) IncrementItemsCount(_ context.Context) {}
// ObserveItemSize implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) ObserveItemSize(_ context.Context, _ datasize.ByteSize) {}
// ObserveWriteDuration implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) ObserveWriteDuration(_ context.Context, _ time.Duration) {}
// ObserveWrite implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) ObserveWrite(_ context.Context, _ time.Duration) {}

View File

@@ -16,11 +16,11 @@ type certPaths struct {
isCustom bool
}
// certStorage holds TLS certificates and their associated file paths. Each
// entry in the slices corresponds to a certificate and its respective paths for
// the certificate and key files. Using this struct allows us to reduce
// certIndex holds TLS certificates and their associated file paths. Each entry
// in the slices corresponds to a certificate and its respective paths for the
// certificate and key files. Using this struct allows us to reduce
// allocations.
type certStorage struct {
type certIndex struct {
// certs contains the list of TLS certificates. All elements must not be
// nil.
certs []*tls.Certificate
@@ -32,21 +32,21 @@ type certStorage struct {
// add saves the TLS certificate and its paths. Certificate paths must only be
// added once, see [certStorage.contains]. cert and cp must not be nil.
func (s *certStorage) add(cert *tls.Certificate, cp *certPaths) {
func (s *certIndex) add(cert *tls.Certificate, cp *certPaths) {
s.certs = append(s.certs, cert)
s.paths = append(s.paths, cp)
}
// contains returns true if the TLS certificate has already been added using the
// provided file paths. cp must not be nil.
func (s *certStorage) contains(cp *certPaths) (ok bool) {
func (s *certIndex) contains(cp *certPaths) (ok bool) {
return slices.ContainsFunc(s.paths, func(p *certPaths) (found bool) {
return *cp == *p
})
}
// count returns the number of saved TLS certificates.
func (s *certStorage) count() (n int) {
func (s *certIndex) count() (n int) {
return len(s.certs)
}
@@ -59,7 +59,7 @@ func (s *certStorage) count() (n int) {
//
// TODO(a.garipov): Explore the above situation and consider fixes to allow
// custom IP-only certs.
func (s *certStorage) certFor(chi *tls.ClientHelloInfo) (cert *tls.Certificate, err error) {
func (s *certIndex) certFor(chi *tls.ClientHelloInfo) (cert *tls.Certificate, err error) {
var errs []error
for _, c := range s.certs {
err = chi.SupportsCertificate(c)
@@ -75,7 +75,7 @@ func (s *certStorage) certFor(chi *tls.ClientHelloInfo) (cert *tls.Certificate,
// rangeFn calls fn for each stored TLS certificate and its paths. fn must not
// be nil. Neither cert nor cp must be modified.
func (s *certStorage) rangeFn(fn func(cert *tls.Certificate, cp *certPaths) (cont bool)) {
func (s *certIndex) rangeFn(fn func(cert *tls.Certificate, cp *certPaths) (cont bool)) {
for i, p := range s.paths {
if !fn(s.certs[i], p) {
return
@@ -84,7 +84,7 @@ func (s *certStorage) rangeFn(fn func(cert *tls.Certificate, cp *certPaths) (con
}
// remove deletes the certificate from s. cp must not be nil.
func (s *certStorage) remove(cp *certPaths) {
func (s *certIndex) remove(cp *certPaths) {
i := slices.IndexFunc(s.paths, func(p *certPaths) (found bool) {
return *cp == *p
})
@@ -97,7 +97,7 @@ func (s *certStorage) remove(cp *certPaths) {
}
// stored returns the list of saved TLS certificates.
func (s *certStorage) stored() (certs []*tls.Certificate) {
func (s *certIndex) stored() (certs []*tls.Certificate) {
return s.certs
}
@@ -106,7 +106,7 @@ func (s *certStorage) stored() (certs []*tls.Certificate) {
//
// TODO(a.garipov): Think of a better way to do this that doesn't involve code
// that looks like iterator invalidation.
func (s *certStorage) update(cp *certPaths, c *tls.Certificate) (ok bool) {
func (s *certIndex) update(cp *certPaths, c *tls.Certificate) (ok bool) {
for i, p := range s.paths {
if *cp == *p {
s.certs[i] = c

View File

@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestCertStorage(t *testing.T) {
func TestCertIndex(t *testing.T) {
const (
domainA = "a.com"
domainB = "b.org"
@@ -51,19 +51,19 @@ func TestCertStorage(t *testing.T) {
paths: pathsDomainB,
}}
s := &certStorage{}
idx := &certIndex{}
for _, cp := range certWithPaths {
s.add(cp.cert, cp.paths)
idx.add(cp.cert, cp.paths)
}
assert.True(t, s.contains(pathsDomainA))
assert.True(t, idx.contains(pathsDomainA))
copyPathsDomainsB := *pathsDomainB
assert.True(t, s.contains(&copyPathsDomainsB))
assert.False(t, s.contains(nonAddedPaths))
assert.Equal(t, len(certWithPaths), s.count())
assert.True(t, idx.contains(&copyPathsDomainsB))
assert.False(t, idx.contains(nonAddedPaths))
assert.Equal(t, len(certWithPaths), idx.count())
got, err := s.certFor(&tls.ClientHelloInfo{
got, err := idx.certFor(&tls.ClientHelloInfo{
ServerName: domainA,
SupportedVersions: []uint16{tls.VersionTLS13},
})
@@ -71,17 +71,17 @@ func TestCertStorage(t *testing.T) {
assert.Equal(t, certDomainA, got)
got, err = s.certFor(&tls.ClientHelloInfo{
got, err = idx.certFor(&tls.ClientHelloInfo{
ServerName: domainB,
SupportedVersions: []uint16{tls.VersionTLS13},
})
require.NoError(t, err)
assert.Equal(t, certDomainB, got)
assert.Equal(t, []*tls.Certificate{certDomainA, certDomainB}, s.stored())
assert.Equal(t, []*tls.Certificate{certDomainA, certDomainB}, idx.stored())
i := 0
s.rangeFn(func(c *tls.Certificate, cp *certPaths) (cont bool) {
idx.rangeFn(func(c *tls.Certificate, cp *certPaths) (cont bool) {
assert.Equal(t, certWithPaths[i].cert, c)
assert.Equal(t, certWithPaths[i].paths, cp)

View File

@@ -176,7 +176,7 @@ func (db *CustomDomainDB) AddCertificate(
func (db *CustomDomainDB) removeCertData(
ctx context.Context,
l *slog.Logger,
certName string,
certName agd.CertificateName,
profID agd.ProfileID,
domains []string,
reason string,
@@ -228,9 +228,9 @@ const (
)
// cachePaths returns the cache paths for the given certificate name.
func (db *CustomDomainDB) cachePaths(certName string) (certPath, keyPath string) {
certPath = filepath.Join(db.cacheDir, certName+".crt.pem")
keyPath = filepath.Join(db.cacheDir, certName+".key.pem")
func (db *CustomDomainDB) cachePaths(certName agd.CertificateName) (certPath, keyPath string) {
certPath = filepath.Join(db.cacheDir, string(certName)+".crt.pem")
keyPath = filepath.Join(db.cacheDir, string(certName)+".key.pem")
return certPath, keyPath
}
@@ -345,8 +345,8 @@ var _ service.Refresher = (*CustomDomainDB)(nil)
// Refresh implements the [service.Refresher] interface for *CustomDomainDB.
// Refresh will retry network and ratelimiting errors.
func (db *CustomDomainDB) Refresh(ctx context.Context) (err error) {
var updatedCertNames []string
retries := map[string]*customDomainRetry{}
var updatedCertNames []agd.CertificateName
retries := map[agd.CertificateName]*customDomainRetry{}
func() {
db.customCertsMu.Lock()
defer db.customCertsMu.Unlock()
@@ -403,7 +403,7 @@ func (db *CustomDomainDB) Refresh(ctx context.Context) (err error) {
// true, refreshCert should be retried later.
func (db *CustomDomainDB) refreshCert(
ctx context.Context,
certName string,
certName agd.CertificateName,
) (needsRetry bool, err error) {
l := db.logger.With("cert_name", certName)
@@ -527,7 +527,7 @@ func (db *CustomDomainDB) certDataToPEM(
// filesystem. Errors are logged and reported to Sentry.
func (db *CustomDomainDB) needsRefresh(
ctx context.Context,
certName string,
certName agd.CertificateName,
certPath string,
keyPath string,
) (ok bool) {
@@ -567,8 +567,8 @@ func (db *CustomDomainDB) needsRefresh(
// addRetry adds the certificate into the retry map. retries must not be nil.
func (db *CustomDomainDB) addRetry(
ctx context.Context,
retries map[string]*customDomainRetry,
certName string,
retries map[agd.CertificateName]*customDomainRetry,
certName agd.CertificateName,
now time.Time,
) {
db.logger.DebugContext(ctx, "retrying later", "cert_name", certName)
@@ -590,7 +590,7 @@ func (db *CustomDomainDB) addRetry(
// are reported to db.errColl and logged.
func (db *CustomDomainDB) performRetries(
ctx context.Context,
retries map[string]*customDomainRetry,
retries map[agd.CertificateName]*customDomainRetry,
now time.Time,
) {
var errs []error

View File

@@ -27,7 +27,7 @@ import (
)
// testCertName is the common certificate name for tests.
const testCertName = "cert1234"
const testCertName agd.CertificateName = "cert1234"
// testProfileID is the common profile ID for tests.
const testProfileID agd.ProfileID = "prof1234"
@@ -468,7 +468,10 @@ func TestCustomDomainDB_Match_futureCert(t *testing.T) {
// testCustomDomainStorage is the [tlsconfig.CustomDomainStorage] for tests.
type testCustomDomainStorage struct {
onCertificateData func(ctx context.Context, certName string) (cert, key []byte, err error)
onCertificateData func(
ctx context.Context,
certName agd.CertificateName,
) (cert, key []byte, err error)
}
// type check
@@ -478,7 +481,7 @@ var _ tlsconfig.CustomDomainStorage = (*testCustomDomainStorage)(nil)
// *testCustomDomainStorage
func (s *testCustomDomainStorage) CertificateData(
ctx context.Context,
certName string,
certName agd.CertificateName,
) (cert, key []byte, err error) {
return s.onCertificateData(ctx, certName)
}
@@ -573,7 +576,10 @@ func TestCustomDomainDB_Refresh(t *testing.T) {
t.Parallel()
strg := &testCustomDomainStorage{
onCertificateData: func(ctx context.Context, certName string) (cert, key []byte, err error) {
onCertificateData: func(
ctx context.Context,
certName agd.CertificateName,
) (cert, key []byte, err error) {
assert.Equal(t, testCertName, certName)
certDER, rsaKey := newCertAndKey(t, 1)
@@ -662,8 +668,8 @@ func TestCustomDomainDB_Refresh(t *testing.T) {
// newCertAndKeyPaths is a helper that returns paths for the certificate and
// the key using the test's temporary directory.
func newCertAndKeyPaths(cacheDir string) (certPath, keyPath string) {
return filepath.Join(cacheDir, testCertName+tlsconfig.CustomDomainCertExt),
filepath.Join(cacheDir, testCertName+tlsconfig.CustomDomainKeyExt)
return filepath.Join(cacheDir, string(testCertName)+tlsconfig.CustomDomainCertExt),
filepath.Join(cacheDir, string(testCertName)+tlsconfig.CustomDomainKeyExt)
}
func TestCustomDomainDB_Refresh_retry(t *testing.T) {
@@ -693,7 +699,7 @@ func TestCustomDomainDB_Refresh_retry(t *testing.T) {
strg := &testCustomDomainStorage{
onCertificateData: func(
ctx context.Context,
certName string,
certName agd.CertificateName,
) (cert, key []byte, err error) {
if !shouldCall {
panic(testutil.UnexpectedCall(ctx, certName))
@@ -793,7 +799,7 @@ func TestCustomDomainDB_Refresh_present(t *testing.T) {
Storage: &testCustomDomainStorage{
onCertificateData: func(
ctx context.Context,
certName string,
certName agd.CertificateName,
) (cert, key []byte, err error) {
assert.Equal(t, testCertName, certName)

View File

@@ -16,10 +16,10 @@ import (
// customDomainIndex optimizes the search for custom-domain data.
type customDomainIndex struct {
// changed is the set of names of certificates that need updating.
changed *container.SortedSliceSet[string]
changed *container.SortedSliceSet[agd.CertificateName]
// retries contains the data for retrying refreshes of custom-domain data.
retries map[string]*customDomainRetry
retries map[agd.CertificateName]*customDomainRetry
// data is the mapping of custom domain or wildcard suffix to records that
// relate to it. The key must be a valid domain name, not a wildcard.
@@ -30,9 +30,9 @@ type customDomainIndex struct {
// newCustomDomainIndex returns a new properly initialized *customDomainIndex.
func newCustomDomainIndex() (idx *customDomainIndex) {
return &customDomainIndex{
changed: container.NewSortedSliceSet[string](),
changed: container.NewSortedSliceSet[agd.CertificateName](),
retries: map[agd.CertificateName]*customDomainRetry{},
data: map[string][]*customDomainIndexItem{},
retries: map[string]*customDomainRetry{},
}
}
@@ -52,7 +52,7 @@ type customDomainIndexItem struct {
// certName is the unique name for fetching the actual certificate data. It
// must not be empty.
certName string
certName agd.CertificateName
// domain is the original domain name or wildcard from which the domain
// pattern has been derived. It must be a valid domain name or wildcard.
@@ -205,7 +205,7 @@ func (idx *customDomainIndex) match(
func (idx *customDomainIndex) remove(
ctx context.Context,
l *slog.Logger,
certName string,
certName agd.CertificateName,
profID agd.ProfileID,
domains []string,
) {

View File

@@ -3,6 +3,7 @@ package tlsconfig
import (
"context"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/golibs/errors"
)
@@ -12,7 +13,7 @@ type CustomDomainStorage interface {
// CertificateData returns the certificate data for the name. If err is
// nil, cert and key must not be nil. If the certificate could not be
// found, err must contain [ErrCertificateNotFound].
CertificateData(ctx context.Context, name string) (cert, key []byte, err error)
CertificateData(ctx context.Context, name agd.CertificateName) (cert, key []byte, err error)
}
// ErrCertificateNotFound is returned (optionally wrapped) by
@@ -31,7 +32,7 @@ var _ CustomDomainStorage = EmptyCustomDomainStorage{}
// EmptyCustomDomainStorage
func (EmptyCustomDomainStorage) CertificateData(
_ context.Context,
_ string,
_ agd.CertificateName,
) (_, _ []byte, _ error) {
return nil, nil, nil
}

View File

@@ -91,7 +91,7 @@ type DefaultManager struct {
errColl errcoll.Interface
metrics ManagerMetrics
tickDB TicketDB
certStorage *certStorage
idx *certIndex
original *tls.Config
clones []*tls.Config
clonesWithMetrics []*tls.Config
@@ -110,12 +110,12 @@ func NewDefaultManager(c *DefaultManagerConfig) (m *DefaultManager, err error) {
}
m = &DefaultManager{
mu: &sync.Mutex{},
logger: c.Logger,
errColl: c.ErrColl,
metrics: c.Metrics,
tickDB: c.TicketDB,
certStorage: &certStorage{},
mu: &sync.Mutex{},
logger: c.Logger,
errColl: c.ErrColl,
metrics: c.Metrics,
tickDB: c.TicketDB,
idx: &certIndex{},
}
m.original = &tls.Config{
@@ -147,7 +147,7 @@ func (m *DefaultManager) Add(
m.mu.Lock()
defer m.mu.Unlock()
if m.certStorage.contains(cp) {
if m.idx.contains(cp) {
m.logger.InfoContext(
ctx,
"skipping already added certificate",
@@ -164,7 +164,7 @@ func (m *DefaultManager) Add(
return fmt.Errorf("adding certificate: %w", err)
}
m.certStorage.add(cert, cp)
m.idx.add(cert, cp)
m.logger.InfoContext(
ctx,
@@ -214,11 +214,11 @@ func (m *DefaultManager) getCertificate(chi *tls.ClientHelloInfo) (c *tls.Certif
m.mu.Lock()
defer m.mu.Unlock()
if m.certStorage.count() == 0 {
if m.idx.count() == 0 {
return nil, errors.Error("no certificates")
}
return m.certStorage.certFor(chi)
return m.idx.certFor(chi)
}
// CloneWithMetrics implements the [Manager] interface for *DefaultManager.
@@ -240,7 +240,7 @@ func (m *DefaultManager) CloneWithMetrics(
proto,
srvName,
deviceDomains,
m.certStorage.stored(),
m.idx.stored(),
)
m.clonesWithMetrics = append(m.clonesWithMetrics, clone)
@@ -266,7 +266,7 @@ func (m *DefaultManager) Refresh(ctx context.Context) (err error) {
defer m.mu.Unlock()
var errs []error
m.certStorage.rangeFn(func(_ *tls.Certificate, cp *certPaths) (cont bool) {
m.idx.rangeFn(func(_ *tls.Certificate, cp *certPaths) (cont bool) {
cert, loadErr := m.load(ctx, cp)
if err != nil {
errs = append(errs, loadErr)
@@ -275,7 +275,7 @@ func (m *DefaultManager) Refresh(ctx context.Context) (err error) {
}
msg, lvl := "refreshed certificate", slog.LevelInfo
if !m.certStorage.update(cp, cert) {
if !m.idx.update(cp, cert) {
msg, lvl = "certificate did not refresh", slog.LevelWarn
}
@@ -289,7 +289,7 @@ func (m *DefaultManager) Refresh(ctx context.Context) (err error) {
return fmt.Errorf("refreshing tls certificates: %w", err)
}
m.logger.InfoContext(ctx, "refresh successful", "num_configs", m.certStorage.count())
m.logger.InfoContext(ctx, "refresh successful", "num_configs", m.idx.count())
return nil
}
@@ -311,7 +311,7 @@ func (m *DefaultManager) Remove(
m.mu.Lock()
defer m.mu.Unlock()
m.certStorage.remove(cp)
m.idx.remove(cp)
m.logger.InfoContext(
ctx,
@@ -372,7 +372,7 @@ func (m *DefaultManager) RotateTickets(ctx context.Context) (err error) {
m.logger.InfoContext(
ctx,
"ticket rotation successful",
"num_configs", m.certStorage.count(),
"num_configs", m.idx.count(),
"num_tickets", len(tickets),
"num_clones", len(m.clones),
"num_clones_with_metrics", len(m.clonesWithMetrics),

View File

@@ -118,6 +118,8 @@ func (s *server) localAddr() (addr net.Addr) {
//
// TODO(a.garipov): Improve error handling.
func (s *server) serve(ctx context.Context, baseLogger *slog.Logger) {
defer slogutil.RecoverAndLog(ctx, s.logger)
tcpListener, err := net.ListenTCP("tcp", net.TCPAddrFromAddrPort(s.initialAddr))
if err != nil {
s.logger.ErrorContext(ctx, "listening tcp", slogutil.KeyError, err)

View File

@@ -49,4 +49,4 @@ readonly go count_flags shuffle_flags timeout_flags
--benchmem \
--benchtime='1s' \
--run='^$' \
./...
work

View File

@@ -21,31 +21,7 @@ set -e -f -u
go="${GO:-go}"
readonly go
# TODO(a.garipov): Add the ability to generate things separately.
(
cd ./internal/geoip/
"$go" run ./country_generate.go
)
(
cd ./internal/geoip/
"$go" run ./asntops_generate.go
)
(
cd ./internal/ecscache/
"$go" run ./ecsblocklist_generate.go
# Force format code, because it's not possible to make an accurate
# template for a long list of strings with different lengths.
gofumpt -l -w ./ecsblocklist.go
)
(
cd ./internal/profiledb/internal/filecachepb/
protoc --go_opt=paths=source_relative --go_out=. ./filecache.proto
)
(
backendpb() (
cd ./internal/backendpb/
protoc \
--go-grpc_opt=Mdns.proto=./backendpb \
@@ -56,3 +32,56 @@ readonly go
--go_out=. \
./dns.proto
)
ecscache() (
cd ./internal/ecscache/
"$go" run ./ecsblocklist_generate.go
# Force format code, because it's not possible to make an accurate
# template for a long list of strings with different lengths.
gofumpt -l -w ./ecsblocklist.go
)
filecachepb() (
cd ./internal/profiledb/internal/filecachepb/
protoc --go_opt=paths=source_relative --go_out=. ./filecache.proto
)
geoip_asntops() (
cd ./internal/geoip/
"$go" run ./asntops_generate.go
)
geoip_country() (
cd ./internal/geoip/
"$go" run ./country_generate.go
)
if [ -z "${ONLY:-}" ]; then
backendpb
ecscache
filecachepb
geoip_asntops
geoip_country
else
padded=" ${ONLY} "
if [ "${padded##* backendpb *}" = '' ]; then
backendpb
fi
if [ "${padded##* ecscache *}" = '' ]; then
ecscache
fi
if [ "${padded##* filecachepb *}" = '' ]; then
filecachepb
fi
if [ "${padded##* geoip_asntops *}" = '' ]; then
geoip_asntops
fi
if [ "${padded##* geoip_country *}" = '' ]; then
geoip_country
fi
fi

View File

@@ -158,10 +158,6 @@ underscores() {
# Checks
# TODO(a.garipov): Remove the dnsserver stuff once it is separated.
dnssrvmod='github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/...'
readonly dnssrvmod
run_linter -e blocklist_imports
run_linter -e method_const
@@ -170,9 +166,9 @@ run_linter -e underscores
run_linter -e gofumpt --extra -e -l .
run_linter "${GO:-go}" vet ./... "$dnssrvmod"
run_linter "${GO:-go}" vet work
run_linter govulncheck ./... "$dnssrvmod"
run_linter govulncheck work
# NOTE: For AdGuard DNS, ignore the generated protobuf files.
run_linter gocyclo --ignore '\.pb\.go$' --over 10 .
@@ -180,9 +176,9 @@ run_linter gocyclo --ignore '\.pb\.go$' --over 10 .
# NOTE: For AdGuard DNS, ignore the generated protobuf files.
run_linter gocognit --ignore '\.pb\.go$' --over 10 .
run_linter ineffassign ./... "$dnssrvmod"
run_linter ineffassign work
run_linter unparam ./... "$dnssrvmod"
run_linter unparam work
find_with_ignore \
-type 'f' \
@@ -197,13 +193,13 @@ find_with_ignore \
')' \
-exec 'misspell' '--error' '{}' '+'
run_linter nilness ./... "$dnssrvmod"
run_linter nilness work
# TODO(a.garipov): Remove the grep crutch once golang/go#60509 is fixed.
#
# TODO(a.garipov): Add a filtering function to run_linter.
fieldalignment_output="$(
fieldalignment ./... "$dnssrvmod" 2>&1 \
fieldalignment work 2>&1 \
| grep -e '\.pb\.go' -v \
|| :
)"
@@ -217,7 +213,7 @@ fi
# TODO(a.garipov): Remove the grep crutch once golang/go#61574 is fixed.
shadow_output="$(
shadow --strict ./... "$dnssrvmod" 2>&1 \
shadow --strict work 2>&1 \
| grep -e '\.pb\.go' -v \
|| :
)"
@@ -229,9 +225,9 @@ if [ "$shadow_output" != '' ]; then
exit 1
fi
run_linter gosec --exclude-generated --quiet ./... "$dnssrvmod"
run_linter gosec --exclude-generated --quiet work
run_linter errcheck ./... "$dnssrvmod"
run_linter errcheck work
staticcheck_matrix='
darwin: GOOS=darwin
@@ -240,4 +236,4 @@ linux: GOOS=linux
readonly staticcheck_matrix
printf '%s' "$staticcheck_matrix" \
| run_linter staticcheck --matrix ./... "$dnssrvmod"
| run_linter staticcheck --matrix work

View File

@@ -45,7 +45,6 @@ timeout_flags="${TIMEOUT_FLAGS:---timeout=120s}"
readonly count_flags cover_flags go shuffle_flags timeout_flags
go_test() {
# TODO(a.garipov): Remove the dnsserver stuff once it is separated.
"$go" test \
"$count_flags" \
"$cover_flags" \
@@ -54,8 +53,7 @@ go_test() {
"$timeout_flags" \
"$v_flags" \
"$x_flags" \
./... \
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/... \
work \
;
}

View File

@@ -3,7 +3,7 @@
# This comment is used to simplify checking local copies of the script. Bump
# this number every time a significant change is made to this script.
#
# AdGuard-Project-Version: 9
# AdGuard-Project-Version: 10
verbose="${VERBOSE:-0}"
readonly verbose
@@ -74,6 +74,8 @@ trailing_whitespace() {
done
}
# TODO(a.garipov): Consider using jq for JSON validation.
run_linter -e trailing_newlines
run_linter -e trailing_whitespace