Sync v2.15.0

This commit is contained in:
Andrey Meshkov
2025-07-25 11:49:13 +03:00
parent 920466d5ca
commit c1ed7b15fd
134 changed files with 6705 additions and 3459 deletions

View File

@@ -7,6 +7,38 @@ 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-3018 / Build 1033
- The environment variables `DNSCHECK_KV_TTL`, `DNSCHECK_KV_TYPE` have been added.
- The property `check.kv.ttl` has been removed. Use `DNSCHECK_KV_TTL` environment variable instead.
- The property `check.kv.type` has been removed. Use `DNSCHECK_KV_TYPE` environment variable instead.
- The environment variable `RATELIMIT_ALLOWLIST_TYPE` have been added.
- The property `ratelimit.allowlist.type` has been removed. Use `RATELIMIT_ALLOWLIST_TYPE` environment variable instead.
## AGDNS-2983 / Build 1020
- The environment variable `NODE_NAME` has been added.
- The property `check.node_name` has been removed. Use `NODE_NAME` environment variable instead.
## AGDNS-2864 / Build 1014
- The environment variables `CUSTOM_DOMAINS_API_KEY`, `CUSTOM_DOMAINS_CACHE_PATH`, `CUSTOM_DOMAINS_ENABLED`, `CUSTOM_DOMAINS_REFRESH_INTERVAL`, and `CUSTOM_DOMAINS_URL` have been added.
## AGDNS-2984 / Build 1013
- The environment variable `REDIS_ADDR` has been changed to `REDIS_HOST`.
- The default value of environment variable `REDIS_IDLE_TIMEOUT` has been changed from `30s` to `5m`.
- The default value of environment variable `REDIS_MAX_ACTIVE` has been changed from `10` to `100`.
- The default value of environment variable `REDIS_MAX_IDLE` has been changed from `3` to `100`.
## AGDNS-2864 / Build 1000
- The environment variables `SESSION_TICKET_API_KEY`, `SESSION_TICKET_CACHE_PATH`, `SESSION_TICKET_INDEX_NAME`, `SESSION_TICKET_REFRESH_INTERVAL`, `SESSION_TICKET_TYPE`, `SESSION_TICKET_URL` have been added.

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.3
GOTOOLCHAIN = go1.24.5
RACE = 0
REVISION = $${REVISION:-$$(git rev-parse --short HEAD)}
VERSION = 0

View File

@@ -44,9 +44,6 @@ ratelimit:
- '127.0.0.1/24'
# Time between two updates of allow list.
refresh_interval: 1h
# Defines where the rate limiting settings are received from. Allowed
# values are "backend" and "consul".
type: 'consul'
# Configuration for the stream connection limiting.
connection_limit:
@@ -174,20 +171,11 @@ geoip:
# DNS checking configuration.
check:
# Domains to use for DNS checking.
kv:
# Defines the type of remote key-value storage. Allowed values are
# "backend", "cache", "consul", and "redis".
type: 'cache'
# For how long to keep the information about the client.
ttl: 30s
# Domains to use for DNS checking.
domains:
- dnscheck.adguard-dns.com
- dnscheck.adguard.com
# Location of this node.
node_location: 'ams'
# Name of this node.
node_name: 'eu-1.dns.example.com'
# IPs to respond with.
ipv4:
- 1.2.3.4

View File

@@ -138,10 +138,6 @@ The `ratelimit` object has the following properties:
**Example:** `30s`.
- <a href="#ratelimit-allowlist-type" id="ratelimit-allowlist-type" name="ratelimit-allowlist-type">`type`</a>: Defines where the rate limit settings are received from. Allowed values are `backend` and `consul`.
**Example:** `consul`.
For example, if `backoff_period` is `1m`, `backoff_count` is `10`, `ipv4-count` is `5`, and `ipv4-interval` is `1s`, a client (meaning all IP addresses within the subnet defined by `ipv4-subnet_key_len`) that made 15 requests in one second or 6 requests (one above `rps`) every second for 10 seconds within one minute, the client is blocked for `backoff_duration`.
### <a href="#ratelimit-connection_limit" id="ratelimit-connection_limit" name="ratelimit-connection_limit">Stream connection limit</a>
@@ -378,24 +374,6 @@ The `geoip` object has the following properties:
The `check` object has the following properties:
- <a href="#check_kv" id="check_kv" name="check_kv">`kv`</a>: Remote key-value storage settings. It has the following properties:
- <a href="#check-kv-type" id="check-kv-type" name="check-kv-type">`type`</a>: Type of the remote KV storage. Allowed values are `backend`, `cache`, `consul`, and `redis`.
**Example:** `consul`.
- <a href="#check-kv-ttl" id="check-kv-ttl" name="check-kv-ttl">`ttl`</a>: For how long to keep the information about a single user in remote KV, as a human-readable duration.
For `backend`, the TTL must be greater than `0s`.
For `cache`, the TTL is not used.
For `consul`, the TTL must be between `10s` and `1d`. Note that the actual TTL can be up to twice as long.
For `redis`, the TTL must be greater than or equal to `1ms`.
**Example:** `30s`.
- <a href="#check-domains" id="check-domains" name="check-domains">`domains`</a>: The domain suffixes to which random IDs are prepended using a hyphen.
**Property example:**
@@ -410,10 +388,6 @@ The `check` object has the following properties:
**Example:** `ams`.
- <a href="#check-node_name" id="check-node_name" name="check-node_name">`node_name`</a>: The name of this server node.
**Example:** `eu-1.dns.example.com`.
- <a href="#check-ipv4" id="check-ipv4" name="check-ipv4">`ipv4` and `ipv6`</a>: Arrays of IPv4 or IPv6 addresses with which to respond to `A` and `AAAA` queries correspondingly. Generally, those should be the IP addresses of the AdGuard DNS [main HTTP API][http-dnscheck] for the DNS server check feature to work properly. In a development setup, that means the localhost addresses.
**Property examples:**

View File

@@ -31,6 +31,7 @@ example.com. 17597 IN A 93.184.216.34
;; ADDITIONAL SECTION:
client-ip.adguard-dns.com. 10 CH TXT "127.0.0.1"
server-ip.adguard-dns.com. 10 CH TXT "94.140.14.14"
node-name.adguard-dns.com. 10 CH TXT "lon-1"
resp.res-type.adguard-dns.com. 10 CH TXT "normal"
;; Query time: 26 msec
@@ -59,6 +60,14 @@ In the `ADDITIONAL SECTION`, the following debug information is returned:
server-ip.adguard-dns.com. 10 CH TXT "127.0.0.1"
```
- <a href="#additional-node-name" id="additional-node-name" name="additional-node-name">`additional-node-name`</a>: The name of this server node. The full name is `node-name.adguard-dns.com`.
**Example:**
```none
node-name.adguard-dns.com. 10 CH TXT "lon-1"
```
- <a href="#additional-device-id" id="additional-device-id" name="additional-device-id">`device-id`</a>: The ID of the device as detected by the server, if any. The full name is `device-id.adguard-dns.com`.
**Example:**

View File

@@ -103,16 +103,20 @@ Supported IDs:
- `allowlist`
- `billstat`
- `custom_domain_db`
- `filters/hashprefix/adult_blocking`
- `filters/hashprefix/newly_registered_domains`
- `filters/hashprefix/safe_browsing`
- `filters/storage`
- `geoip`
- `profiledb`
- `profiledb_full`
- `profiledb`
- `rulestat`
- `ticket_rotator`
- `tlsconfig`
- `websvc`
As well as IDs under `plugin/`, if any.
The special ID `*`, when used alone, causes all available refresh tasks to be performed. Note that it performs full profile DB refresh. Use with caution.

View File

@@ -16,7 +16,14 @@ 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)
- [`CUSTOM_DOMAINS_API_KEY`](#CUSTOM_DOMAINS_API_KEY)
- [`CUSTOM_DOMAINS_CACHE_PATH`](#CUSTOM_DOMAINS_CACHE_PATH)
- [`CUSTOM_DOMAINS_ENABLED`](#CUSTOM_DOMAINS_ENABLED)
- [`CUSTOM_DOMAINS_REFRESH_INTERVAL`](#CUSTOM_DOMAINS_REFRESH_INTERVAL)
- [`CUSTOM_DOMAINS_URL`](#CUSTOM_DOMAINS_URL)
- [`DNSCHECK_CACHE_KV_SIZE`](#DNSCHECK_CACHE_KV_SIZE)
- [`DNSCHECK_KV_TTL`](#DNSCHECK_KV_TTL)
- [`DNSCHECK_KV_TYPE`](#DNSCHECK_KV_TYPE)
- [`DNSCHECK_REMOTEKV_API_KEY`](#DNSCHECK_REMOTEKV_API_KEY)
- [`DNSCHECK_REMOTEKV_URL`](#DNSCHECK_REMOTEKV_URL)
- [`FILTER_CACHE_PATH`](#FILTER_CACHE_PATH)
@@ -32,16 +39,22 @@ AdGuard DNS uses [environment variables][wiki-env] to store some of the more sen
- [`METRICS_NAMESPACE`](#METRICS_NAMESPACE)
- [`NEW_REG_DOMAINS_ENABLED`](#NEW_REG_DOMAINS_ENABLED)
- [`NEW_REG_DOMAINS_URL`](#NEW_REG_DOMAINS_URL)
- [`NODE_NAME`](#NODE_NAME)
- [`PROFILES_API_KEY`](#PROFILES_API_KEY)
- [`PROFILES_CACHE_PATH`](#PROFILES_CACHE_PATH)
- [`PROFILES_URL`](#PROFILES_URL)
- [`REDIS_ADDR`](#REDIS_ADDR)
- [`REDIS_DB`](#REDIS_DB)
- [`REDIS_HOST`](#REDIS_HOST)
- [`REDIS_KEY_PREFIX`](#REDIS_KEY_PREFIX)
- [`REDIS_MAX_ACTIVE`](#REDIS_MAX_ACTIVE)
- [`REDIS_MAX_CONN_LIFETIME`](#REDIS_MAX_CONN_LIFETIME)
- [`REDIS_MAX_IDLE`](#REDIS_MAX_IDLE)
- [`REDIS_NETWORK`](#REDIS_NETWORK)
- [`REDIS_IDLE_TIMEOUT`](#REDIS_IDLE_TIMEOUT)
- [`REDIS_PORT`](#REDIS_PORT)
- [`REDIS_WAIT`](#REDIS_WAIT)
- [`QUERYLOG_PATH`](#QUERYLOG_PATH)
- [`RATELIMIT_ALLOWLIST_TYPE`](#RATELIMIT_ALLOWLIST_TYPE)
- [`RULESTAT_URL`](#RULESTAT_URL)
- [`SAFE_BROWSING_ENABLED`](#SAFE_BROWSING_ENABLED)
- [`SAFE_BROWSING_URL`](#SAFE_BROWSING_URL)
@@ -148,17 +161,59 @@ The HTTP(S) URL of the session API of the Consul instance used as a key-value da
**Default:** **Unset.**
**Example:** `http://localhost:8500/v1/session/create`
## <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].
**Default:** No default value, the variable is required if `CUSTOM_DOMAINS_ENABLED` is set to `1`.
## <a href="#CUSTOM_DOMAINS_CACHE_PATH" id="CUSTOM_DOMAINS_CACHE_PATH" name="CUSTOM_DOMAINS_CACHE_PATH">`CUSTOM_DOMAINS_CACHE_PATH`</a>
The path to directory for storing the downloaded certificate and private-key data.
**Default:** No default value, a valid directory path is required if `CUSTOM_DOMAINS_ENABLED` is set to `1`.
## <a href="#CUSTOM_DOMAINS_ENABLED" id="CUSTOM_DOMAINS_ENABLED" name="CUSTOM_DOMAINS_ENABLED">`CUSTOM_DOMAINS_ENABLED`</a>
When set to `1`, enable the custom-domains feature. When set to `0`, disable it.
**Default:** `1`.
## <a href="#CUSTOM_DOMAINS_REFRESH_INTERVAL" id="CUSTOM_DOMAINS_REFRESH_INTERVAL" name="CUSTOM_DOMAINS_REFRESH_INTERVAL">`CUSTOM_DOMAINS_REFRESH_INTERVAL`</a>
The interval that defines how often to query the backend for the custom-domain data, as a human-readable duration.
**Default:** No default value, a positive value is required if `CUSTOM_DOMAINS_ENABLED` is set to `1`.
**Example:** `1m`
## <a href="#CUSTOM_DOMAINS_URL" id="CUSTOM_DOMAINS_URL" name="CUSTOM_DOMAINS_URL">`CUSTOM_DOMAINS_URL`</a>
The URL of the gRPC(S) API for the custom-domain data.
**Default:** No default value, the variable is required if `CUSTOM_DOMAINS_ENABLED` is set to `1`.
## <a href="#DNSCHECK_CACHE_KV_SIZE" id="DNSCHECK_CACHE_KV_SIZE" name="DNSCHECK_CACHE_KV_SIZE">`DNSCHECK_CACHE_KV_SIZE`</a>
The maximum number of the local cache key-value database entries for the DNS server checking.
**Default:** No default value, a positive value is required if the [type][conf-dnscheck-type] of the database is set to `cache`.
**Default:** No default value, a positive value is required if `DNSCHECK_KV_TYPE` is set to `cache`.
**Example:** `1000`
[conf-dnscheck-type]: configuration.md#check-kv-type
## <a href="#DNSCHECK_KV_TTL" id="DNSCHECK_KV_TTL" name="DNSCHECK_KV_TTL">`DNSCHECK_KV_TTL`</a>
For how long to keep the information about a single user in remote KV, as a human-readable duration.
**Default:** **Unset.**
**Example:** `1m`
## <a href="#DNSCHECK_KV_TYPE" id="DNSCHECK_KV_TYPE" name="DNSCHECK_KV_TYPE">`DNSCHECK_KV_TYPE`</a>
Type of the remote KV storage. Allowed values are `backend`, `cache`, `consul`, and `redis`.
**Default:** **Unset.**
## <a href="#DNSCHECK_REMOTEKV_API_KEY" id="DNSCHECK_REMOTEKV_API_KEY" name="DNSCHECK_REMOTEKV_API_KEY">`DNSCHECK_REMOTEKV_API_KEY`</a>
@@ -269,6 +324,15 @@ The HTTP(S) URL of source list of rules for newly registered domains safe browsi
**Default:** No default value, the variable is required if `NEW_REG_DOMAINS_ENABLED` is set to `1`.
## <a href="#NODE_NAME" id="NODE_NAME" name="NODE_NAME">`NODE_NAME`</a>
The name of this server node. Used in [debug DNS API][debug-dns-api] and [DNS checking][http-dnscheck].
[debug-dns-api]: debugdns.md#additional-node-name
[http-dnscheck]: http.md#dnscheck-test
**Default:** No default value, the variable is **required.**
## <a href="#PROFILES_API_KEY" id="PROFILES_API_KEY" name="PROFILES_API_KEY">`PROFILES_API_KEY`</a>
The API key to use when authenticating queries to the profiles API, if any. The API key should be valid as defined by [RFC 6750].
@@ -313,13 +377,17 @@ The base backend URL for profiles API. Supports gRPC(S) (`grpc://` and `grpcs://
[ext-profiles]: externalhttp.md#backend-profiles
## <a href="#REDIS_ADDR" id="REDIS_ADDR" name="REDIS_ADDR">`REDIS_ADDR`</a>
## <a href="#REDIS_DB" id="REDIS_DB" name="REDIS_DB">`REDIS_DB`</a>
The index of Redis database to use.
**Default:** `0`.
## <a href="#REDIS_HOST" id="REDIS_HOST" name="REDIS_HOST">`REDIS_HOST`</a>
Redis server address. Can be an IP address or a hostname.
**Default:** No default value, the variable is required if the [type][conf-check-kv-type] of remote KV storage for DNS server checking is `redis` in the configuration file.
[conf-check-kv-type]: configuration.md#check-kv-type
**Default:** `localhost`, the variable is required if `DNSCHECK_KV_TYPE` is set to `redis`.
## <a href="#REDIS_KEY_PREFIX" id="REDIS_KEY_PREFIX" name="REDIS_KEY_PREFIX">`REDIS_KEY_PREFIX`</a>
@@ -331,19 +399,37 @@ The prefix for Redis keys.
The maximum number of active Redis connections.
**Default:** `10`.
**Default:** `100`.
## <a href="#REDIS_MAX_CONN_LIFETIME" id="REDIS_MAX_CONN_LIFETIME" name="REDIS_MAX_CONN_LIFETIME">`REDIS_MAX_CONN_LIFETIME`</a>
The maximum total duration of connections in a pool.
**Default:** `0s`, which means that the lifetime is not limited.
## <a href="#REDIS_MAX_IDLE" id="REDIS_MAX_IDLE" name="REDIS_MAX_IDLE">`REDIS_MAX_IDLE`</a>
The maximum number of idle Redis connections.
**Default:** `3`.
**Default:** `100`.
## <a href="#REDIS_NETWORK" id="REDIS_NETWORK" name="REDIS_NETWORK">`REDIS_NETWORK`</a>
Kind of IP protocol version to use:
- `ip` means both;
- `ip4` means IPv4 only;
- `ip6` means IPv6 only.
All other values are invalid.
**Default:** `ip4`.
## <a href="#REDIS_IDLE_TIMEOUT" id="REDIS_IDLE_TIMEOUT" name="REDIS_IDLE_TIMEOUT">`REDIS_IDLE_TIMEOUT`</a>
How long until idle Redis connections are closed, as a human-readable duration.
**Default:** `30s`.
**Default:** `5m`.
## <a href="#REDIS_PORT" id="REDIS_PORT" name="REDIS_PORT">`REDIS_PORT`</a>
@@ -351,12 +437,26 @@ Redis server port.
**Default:** `6379`.
## <a href="#REDIS_WAIT" id="REDIS_WAIT" name="REDIS_WAIT">`REDIS_WAIT`</a>
It selects if the pool must wait for a connection once the `REDIS_MAX_ACTIVE` limit is reached.
**Default:** `1`, which means to wait.
## <a href="#QUERYLOG_PATH" id="QUERYLOG_PATH" name="QUERYLOG_PATH">`QUERYLOG_PATH`</a>
The path to the file into which the query log is going to be written.
**Default:** `./querylog.jsonl`.
## <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`.
**Default:** **Unset.**
**Example:** `consul`.
## <a href="#RULESTAT_URL" id="RULESTAT_URL" name="RULESTAT_URL">`RULESTAT_URL`</a>
The HTTP(S) URL to send filtering rule list statistics to. If empty or unset, the collection of filtering rule statistics is disabled. See the [external HTTP API requirements section][ext-rulestat] on the expected format of the response.

View File

@@ -34,10 +34,10 @@ This service is disabled when all server groups have property [`profiles_enabled
This is the service to which the [`DNSCHECK_REMOTEKV_URL`][env-dnscheck_remotekv_url] environment variable points. Supports gRPC(s) URLs. The service must correspond to `./internal/backendpb/dns.proto`.
This service is only enabled when the `check.kv` object has the [`type`][conf-check-kv-type] property set to `backend`.
This service is only enabled when the [`DNSCHECK_KV_TYPE`][env-dnscheck_kv_type] environment variable is set to `backend`.
[env-dnscheck_remotekv_url]: environment.md#DNSCHECK_REMOTEKV_URL
[conf-check-kv-type]: configuration.md#check-kv-type
[env-dnscheck_kv_type]: environment.md#DNSCHECK_KV_TYPE
## <a href="#backend-profiles" id="backend-profiles" name="backend-profiles">Backend profiles service</a>

64
go.mod
View File

@@ -1,17 +1,18 @@
module github.com/AdguardTeam/AdGuardDNS
go 1.24.3
go 1.24.5
require (
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.0.0-20240607112746-5690301129fe
github.com/AdguardTeam/golibs v0.32.11
// NOTE: Do not change the pseudoversion.
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.0.0-00010101000000-000000000000
github.com/AdguardTeam/golibs v0.32.15
github.com/AdguardTeam/urlfilter v0.20.0
github.com/ameshkov/dnscrypt/v2 v2.4.0
github.com/axiomhq/hyperloglog v0.2.5
github.com/bluele/gcache v0.0.2
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
github.com/caarlos0/env/v7 v7.1.0
github.com/getsentry/sentry-go v0.33.0
github.com/getsentry/sentry-go v0.34.0
github.com/gomodule/redigo v1.9.2
github.com/google/renameio/v2 v2.0.0
github.com/miekg/dns v1.1.66
@@ -22,25 +23,26 @@ require (
github.com/prometheus/common v0.64.0
github.com/quic-go/quic-go v0.52.0
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.38.0
golang.org/x/net v0.40.0
golang.org/x/sys v0.33.0
golang.org/x/time v0.11.0
google.golang.org/grpc v1.72.2
golang.org/x/crypto v0.39.0
golang.org/x/net v0.41.0
golang.org/x/sys v0.34.0
golang.org/x/time v0.12.0
google.golang.org/grpc v1.73.0
google.golang.org/protobuf v1.36.6
gopkg.in/yaml.v2 v2.4.0
)
require (
cloud.google.com/go v0.121.2 // indirect
cloud.google.com/go/ai v0.12.0 // indirect
cloud.google.com/go/auth v0.16.1 // indirect
cloud.google.com/go v0.121.3 // indirect
cloud.google.com/go/ai v0.12.1 // indirect
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.7.0 // indirect
cloud.google.com/go/longrunning v0.6.7 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/caarlos0/env/v11 v11.3.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@@ -53,7 +55,7 @@ require (
github.com/golangci/misspell v0.7.0 // indirect
github.com/google/generative-ai-go v0.20.1 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
@@ -71,36 +73,36 @@ require (
github.com/quic-go/qpack v0.5.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/securego/gosec/v2 v2.22.4 // indirect
github.com/securego/gosec/v2 v2.22.5 // 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.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect
go.opentelemetry.io/otel/trace v1.36.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.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.5.2 // indirect
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b // indirect
golang.org/x/exp/typeparams v0.0.0-20250531010427-b6e5de432a8b // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/telemetry v0.0.0-20250603144755-9a9ac2102d0e // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.org/x/vuln v1.1.4 // indirect
google.golang.org/api v0.236.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/api v0.241.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // 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/sh/v3 v3.11.0 // indirect
mvdan.cc/sh/v3 v3.12.0 // indirect
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
)

126
go.sum
View File

@@ -1,17 +1,17 @@
cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg=
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go/ai v0.12.0 h1:i9k0U14BhejPY+yKTm9VTCjRAA3PwYvf4s/zhSkHof0=
cloud.google.com/go/ai v0.12.0/go.mod h1:SEbNRRerz779yMT0qjDYG245m96WO8Flieiv+/fU9GQ=
cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
cloud.google.com/go v0.121.3 h1:84RD+hQXNdY5Sw/MWVAx5O9Aui/rd5VQ9HEcdN19afo=
cloud.google.com/go v0.121.3/go.mod h1:6vWF3nJWRrEUv26mMB3FEIU/o1MQNVPG1iHdisa2SJc=
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.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
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.32.11 h1:75EquS8SWvzsM3JFJY0359ZBw66jDjAegteHzh9nSw8=
github.com/AdguardTeam/golibs v0.32.11/go.mod h1:LXr0gqqZuVpt+L+bP3Nnr0/CecLmm3rxkdgyyW5JXXM=
github.com/AdguardTeam/golibs v0.32.15 h1:arDRDWiZCH3g5Onr8AqMnOHhaOppNoBpgC3DNhmeDeA=
github.com/AdguardTeam/golibs v0.32.15/go.mod h1:G9CzUOzx87J+2u+eClJrrwWD7lMbROvuUnT8uvDUzIA=
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
@@ -28,6 +28,8 @@ github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4=
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
@@ -42,8 +44,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/getsentry/sentry-go v0.33.0 h1:YWyDii0KGVov3xOaamOnF0mjOrqSjBqwv48UEzn7QFg=
github.com/getsentry/sentry-go v0.33.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/getsentry/sentry-go v0.34.0 h1:1FCHBVp8TfSc8L10zqSwXUZNiOSF+10qw4czjarTiY4=
github.com/getsentry/sentry-go v0.34.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -70,8 +72,8 @@ github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
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/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4=
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18=
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
@@ -144,8 +146,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/securego/gosec/v2 v2.22.4 h1:21VdNGcKicFSv6rUDBc0cEtEl7lWyCKZxKIm0iwvrIM=
github.com/securego/gosec/v2 v2.22.4/go.mod h1:ww5Yie7KJ3AH8XZQTletkW5zOmIse6FACs/Ys8VR3qE=
github.com/securego/gosec/v2 v2.22.5 h1:ySws9uwOeE42DsG54v2moaJfh7r08Ev7SAYJuoMDfRA=
github.com/securego/gosec/v2 v2.22.5/go.mod h1:AWfgrFsVewk5LKobsPWlygCHt8K91boVPyL6GUZG5NY=
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=
@@ -167,20 +169,20 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
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.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
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.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=
@@ -189,64 +191,64 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA=
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/exp/typeparams v0.0.0-20250531010427-b6e5de432a8b h1:BYXmVbbiQZNvDQnJIC37pAGLxabO8CGWgWEZrCtjXbk=
golang.org/x/exp/typeparams v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b h1:KdrhdYPDUvJTvrDK9gdjfFd6JTk8vA1WJoldYSi0kHo=
golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b/go.mod h1:LKZHyeOpPuZcMgxeHjJp4p5yvxrCX1xDvH10zYHhjjQ=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
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.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20250603144755-9a9ac2102d0e h1:Rux3cpb+5sgvNBpSMHqlgFcZUZnkNuo6WDW6Z/zcAus=
golang.org/x/telemetry v0.0.0-20250603144755-9a9ac2102d0e/go.mod h1:QNvpSH4vItB4zw8JazOv6Ba3fs1TorwPx9cCU6qTIdE=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ=
golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
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.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0=
google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4=
google.golang.org/api v0.241.0 h1:QKwqWQlkc6O895LchPEDUSYr22Xp3NCxpQRiWTB6avE=
google.golang.org/api v0.241.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
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.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
@@ -264,7 +266,7 @@ 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/sh/v3 v3.11.0 h1:q5h+XMDRfUGUedCqFFsjoFjrhwf2Mvtt1rkMvVz0blw=
mvdan.cc/sh/v3 v3.11.0/go.mod h1:LRM+1NjoYCzuq/WZ6y44x14YNAI0NK7FLPeQSaFagGg=
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=
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE=

View File

@@ -1,4 +1,4 @@
go 1.24.3
go 1.24.5
use (
.

View File

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,7 @@ import (
// Profile is the profile access manager interface.
type Profile interface {
// Config returns the profile access configuration, excluding the
// [ProfileConfig.Metrics] property.
// Config returns the profile access configuration.
Config() (conf *ProfileConfig)
// IsBlocked returns true if the req should be blocked. req must not be
@@ -51,10 +50,6 @@ func (EmptyProfile) IsBlocked(
// NOTE: Do not change fields of this structure without incrementing
// [internal/profiledb/internal.FileCacheVersion].
type ProfileConfig struct {
// Metrics is used for the collection of the profile access engine
// statistics. It must not be nil.
Metrics ProfileMetrics
// AllowedNets is slice of CIDRs to be allowed.
AllowedNets []netip.Prefix
@@ -87,24 +82,35 @@ type DefaultProfile struct {
blocklistDomainRules []string
}
// NewDefaultProfile creates a new *DefaultProfile. conf is assumed to be
// defaultProfileConfig is the configuration for the default access for
// profiles.
type defaultProfileConfig struct {
// conf is the configuration to use for the access manager. It must not be
// nil and must be valid.
conf *ProfileConfig
// metrics is used for the collection of the profile access engine
// statistics. It must not be nil.
metrics ProfileMetrics
}
// newDefaultProfile creates a new *DefaultProfile. conf is assumed to be
// valid. mtrc must not be nil.
func NewDefaultProfile(conf *ProfileConfig) (p *DefaultProfile) {
func newDefaultProfile(c *defaultProfileConfig) (p *DefaultProfile) {
return &DefaultProfile{
allowedNets: conf.AllowedNets,
blockedNets: conf.BlockedNets,
allowedASN: conf.AllowedASN,
blockedASN: conf.BlockedASN,
blocklistDomainRules: conf.BlocklistDomainRules,
blockedHostsEng: newBlockedHostEngine(conf.Metrics, conf.BlocklistDomainRules),
allowedNets: c.conf.AllowedNets,
blockedNets: c.conf.BlockedNets,
allowedASN: c.conf.AllowedASN,
blockedASN: c.conf.BlockedASN,
blocklistDomainRules: c.conf.BlocklistDomainRules,
blockedHostsEng: newBlockedHostEngine(c.metrics, c.conf.BlocklistDomainRules),
}
}
// type check
var _ Profile = (*DefaultProfile)(nil)
// Config implements the [Profile] interface for *DefaultProfile. It excludes
// the Metrics property.
// Config implements the [Profile] interface for *DefaultProfile.
func (p *DefaultProfile) Config() (conf *ProfileConfig) {
return &ProfileConfig{
AllowedNets: slices.Clone(p.allowedNets),
@@ -157,3 +163,28 @@ func matchASNs(asns []geoip.ASN, l *geoip.Location) (ok bool) {
func (p *DefaultProfile) isBlockedByHostsEng(ctx context.Context, req *dns.Msg) (blocked bool) {
return p.blockedHostsEng.isBlocked(ctx, req)
}
// ProfileConstructor creates default access managers for profiles.
//
// TODO(a.garipov): Add global standard rules for profile access managers here
// as well.
type ProfileConstructor struct {
metrics ProfileMetrics
}
// NewProfileConstructor returns a properly initialized *ProfileConstructor.
// mtrc must not be nil.
func NewProfileConstructor(mtrc ProfileMetrics) (c *ProfileConstructor) {
return &ProfileConstructor{
metrics: mtrc,
}
}
// New creates a new access manager for a profile based on the configuration.
// conf must not be nil and must be valid.
func (c *ProfileConstructor) New(conf *ProfileConfig) (p *DefaultProfile) {
return newDefaultProfile(&defaultProfileConfig{
conf: conf,
metrics: c.metrics,
})
}

View File

@@ -14,7 +14,6 @@ import (
func TestDefaultProfile_Config(t *testing.T) {
conf := &access.ProfileConfig{
Metrics: testAccessMtrc,
AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
BlockedNets: []netip.Prefix{netip.MustParsePrefix("2.2.2.0/24")},
AllowedASN: []geoip.ASN{1},
@@ -22,7 +21,8 @@ func TestDefaultProfile_Config(t *testing.T) {
BlocklistDomainRules: []string{"block.test"},
}
a := access.NewDefaultProfile(conf)
cons := access.NewProfileConstructor(testAccessMtrc)
a := cons.New(conf)
got := a.Config()
assert.Equal(t, conf.AllowedNets, got.AllowedNets)
assert.Equal(t, conf.BlockedNets, got.BlockedNets)
@@ -35,7 +35,6 @@ func TestDefaultProfile_IsBlocked(t *testing.T) {
passAddrPort := netip.MustParseAddrPort("3.3.3.3:3333")
conf := &access.ProfileConfig{
Metrics: testAccessMtrc,
AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.1/32")},
BlockedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
AllowedASN: []geoip.ASN{1},
@@ -49,7 +48,8 @@ func TestDefaultProfile_IsBlocked(t *testing.T) {
},
}
a := access.NewDefaultProfile(conf)
cons := access.NewProfileConstructor(testAccessMtrc)
a := cons.New(conf)
testCases := []struct {
loc *geoip.Location
@@ -171,7 +171,6 @@ func TestDefaultProfile_IsBlocked(t *testing.T) {
func TestDefaultProfile_IsBlocked_prefixAllowlist(t *testing.T) {
conf := &access.ProfileConfig{
Metrics: testAccessMtrc,
AllowedNets: []netip.Prefix{
netip.MustParsePrefix("2.2.2.0/24"),
netip.MustParsePrefix("3.3.0.0/16"),
@@ -182,7 +181,8 @@ func TestDefaultProfile_IsBlocked_prefixAllowlist(t *testing.T) {
BlocklistDomainRules: nil,
}
a := access.NewDefaultProfile(conf)
cons := access.NewProfileConstructor(testAccessMtrc)
a := cons.New(conf)
testCases := []struct {
want assert.BoolAssertionFunc
@@ -225,7 +225,6 @@ func BenchmarkDefaultProfile_IsBlocked(b *testing.B) {
passAddrPort := netip.MustParseAddrPort("3.3.3.3:3333")
conf := &access.ProfileConfig{
Metrics: testAccessMtrc,
AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.1/32")},
BlockedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
AllowedASN: []geoip.ASN{1},
@@ -239,7 +238,8 @@ func BenchmarkDefaultProfile_IsBlocked(b *testing.B) {
},
}
a := access.NewDefaultProfile(conf)
cons := access.NewProfileConstructor(testAccessMtrc)
a := cons.New(conf)
ctx := testutil.ContextWithTimeout(b, testTimeout)

View File

@@ -47,6 +47,8 @@ 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
// Enabled shows if this certificate is enabled.

View File

@@ -4,7 +4,7 @@ import (
"encoding/base64"
"fmt"
"github.com/AdguardTeam/AdGuardDNS/internal/agdrand"
"github.com/AdguardTeam/golibs/mathutil/randutil"
)
// RequestIDLen is the length of a [RequestID] in bytes. A RequestID is
@@ -19,7 +19,7 @@ type RequestID [RequestIDLen]byte
// requestIDRand is used to create [RequestID]s.
//
// TODO(a.garipov): Consider making a struct instead of using one global source.
var requestIDRand = agdrand.NewReader(agdrand.MustNewSeed())
var requestIDRand = randutil.NewReader(randutil.MustNewSeed())
// NewRequestID returns a new pseudorandom RequestID. Prefer this to manual
// conversion from other string types.

View File

@@ -1,76 +0,0 @@
// Package agdrand contains utilities for random numbers.
//
// TODO(a.garipov): Move to golibs.
package agdrand
import (
cryptorand "crypto/rand"
"math/rand/v2"
"sync"
)
// Reader is a ChaCha8-based cryptographically strong random number reader.
// It's safe for concurrent use.
type Reader struct {
// mu protects reader.
mu *sync.Mutex
reader *rand.ChaCha8
}
// NewReader returns a new properly initialized *Reader seeded with the given
// seed.
func NewReader(seed [32]byte) (r *Reader) {
return &Reader{
mu: &sync.Mutex{},
reader: rand.NewChaCha8(seed),
}
}
// Read generates len(p) random bytes and writes them into p. It always returns
// len(p) and a nil error. It's safe for concurrent use.
func (r *Reader) Read(p []byte) (n int, err error) {
r.mu.Lock()
defer r.mu.Unlock()
return r.reader.Read(p)
}
// LockedSource is an implementation of [rand.Source] that is concurrency-safe.
type LockedSource struct {
// mu protects src.
mu *sync.Mutex
src rand.Source
}
// NewLockedSource returns new properly initialized *LockedSource.
func NewLockedSource(src rand.Source) (s *LockedSource) {
return &LockedSource{
mu: &sync.Mutex{},
src: src,
}
}
// type check
var _ rand.Source = (*LockedSource)(nil)
// Uint64 implements the [rand.Source] interface for *LockedSource.
func (s *LockedSource) Uint64() (r uint64) {
s.mu.Lock()
defer s.mu.Unlock()
return s.src.Uint64()
}
// MustNewSeed returns new 32 byte seed for pseudorandom generators. Panics on
// errors.
func MustNewSeed() (seed [32]byte) {
_, err := cryptorand.Read(seed[:])
if err != nil {
// Don't wrap the error, because it's informative enough as is.
panic(err)
}
return seed
}

View File

@@ -1,112 +0,0 @@
package agdrand_test
import (
"math/rand/v2"
"sync"
"testing"
"github.com/AdguardTeam/AdGuardDNS/internal/agdrand"
"github.com/stretchr/testify/require"
)
// routinesLimit is the number of goroutines for tests.
const routinesLimit = 512
func TestReader_race(t *testing.T) {
t.Parallel()
const length = 128
reader := agdrand.NewReader(agdrand.MustNewSeed())
wg := &sync.WaitGroup{}
wg.Add(routinesLimit)
startCh := make(chan struct{})
for range routinesLimit {
go func() {
defer wg.Done()
<-startCh
for range 1_000 {
buf := make([]byte, length)
_, _ = reader.Read(buf)
}
}()
}
close(startCh)
wg.Wait()
}
func TestLockedSource_race(t *testing.T) {
t.Parallel()
src := agdrand.NewLockedSource(rand.NewPCG(0, 0))
wg := &sync.WaitGroup{}
wg.Add(routinesLimit)
startCh := make(chan struct{})
for range routinesLimit {
go func() {
defer wg.Done()
<-startCh
for range 1_000 {
_ = src.Uint64()
}
}()
}
close(startCh)
wg.Wait()
}
// testSeed is a seed for tests.
var testSeed = [32]byte{}
func BenchmarkReader_Read(b *testing.B) {
const length = 16
reader := agdrand.NewReader(testSeed)
var n int
var err error
b.ReportAllocs()
buf := make([]byte, length)
for b.Loop() {
n, err = reader.Read(buf)
}
require.Equal(b, length, n)
require.NoError(b, err)
// Most recent results:
//
// goos: darwin
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/agdrand
// cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
// BenchmarkReader_Read-12 38364720 28.48 ns/op 0 B/op 0 allocs/op
}
func BenchmarkLockedSource_Uint64(b *testing.B) {
src := agdrand.NewLockedSource(rand.NewChaCha8(testSeed))
b.ReportAllocs()
for range b.N {
_ = src.Uint64()
}
// Most recent results:
//
// goos: darwin
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/agdrand
// cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
// BenchmarkLockedSource_Uint64-12 59585797 18.13 ns/op 0 B/op 0 allocs/op
}

View File

@@ -0,0 +1,47 @@
package agdtime
import (
"time"
"github.com/AdguardTeam/golibs/timeutil"
)
// ExponentialSchedule is a [timeutil.Schedule] that exponentially increases the
// time until the next event until it reaches the maximum.
//
// TODO(a.garipov): Consider moving to golibs.
type ExponentialSchedule struct {
current time.Duration
max time.Duration
base uint64
}
// NewExponentialSchedule returns a new properly initialized
// *ExponentialSchedule.
func NewExponentialSchedule(initial, max time.Duration, base uint64) (s *ExponentialSchedule) {
return &ExponentialSchedule{
current: initial,
max: max,
base: base,
}
}
// type check
var _ timeutil.Schedule = (*ExponentialSchedule)(nil)
// UntilNext implements the [timeutil.Schedule] interface for
// *ExponentialSchedule.
func (s *ExponentialSchedule) UntilNext(_ time.Time) (d time.Duration) {
d = s.current
// A negative s.current means that the previous call has overflown
// time.Duration, which means it's above max.
if d >= s.max || d < 0 {
return s.max
}
// #nosec G115 -- The overflow is processed above.
s.current = s.current * time.Duration(s.base)
return d
}

View File

@@ -0,0 +1,28 @@
package agdtime_test
import (
"fmt"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
)
func ExampleExponentialSchedule() {
s := agdtime.NewExponentialSchedule(1*time.Second, 1*time.Minute, 2)
for range 10 {
fmt.Println(s.UntilNext(time.Time{}))
}
// Output:
// 1s
// 2s
// 4s
// 8s
// 16s
// 32s
// 1m0s
// 1m0s
// 1m0s
// 1m0s
}

View File

@@ -4,6 +4,7 @@ import (
"net/netip"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/c2h5oh/datasize"
@@ -44,6 +45,9 @@ var (
TestPendingExpire = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
)
// TestRespSzEst is a response-size estimate for tests.
const TestRespSzEst datasize.ByteSize = 1 * datasize.KB
// TestBind includes any IPv4 address.
//
// TODO(a.garipov): Add to golibs/netutil.
@@ -52,5 +56,6 @@ var TestBind = netip.MustParsePrefix("0.0.0.0/0")
// TestLogger is the common logger for tests.
var TestLogger = slogutil.NewDiscardLogger()
// TestRespSzEst is a response-size estimate for tests.
const TestRespSzEst datasize.ByteSize = 1 * datasize.KB
// TestProfileAccessConstructor is the common constructor of profile access
// managers for tests
var TestProfileAccessConstructor = access.NewProfileConstructor(access.EmptyProfileMetrics{})

View File

@@ -15,8 +15,6 @@ import (
// [tlsconfig.CustomDomainStorage] interface that uses the business-logic
// backend as the custom-domain certificate storage. It is safe for concurrent
// use.
//
// TODO(a.garipov): Use.
type CustomDomainStorage struct {
logger *slog.Logger
client CustomDomainServiceClient
@@ -100,7 +98,5 @@ func (s *CustomDomainStorage) CertificateData(
)
}
// TODO(a.garipov): Consider validating certificate and private-key date.
return resp.Certificate, resp.PrivateKey, nil
}

View File

@@ -2044,6 +2044,50 @@ func (x *AuthenticationFailedError) GetMessage() string {
return ""
}
type NotFoundError struct {
state protoimpl.MessageState `protogen:"open.v1"`
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *NotFoundError) Reset() {
*x = NotFoundError{}
mi := &file_dns_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *NotFoundError) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NotFoundError) ProtoMessage() {}
func (x *NotFoundError) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[30]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NotFoundError.ProtoReflect.Descriptor instead.
func (*NotFoundError) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{30}
}
func (x *NotFoundError) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
type RateLimitSettings struct {
state protoimpl.MessageState `protogen:"open.v1"`
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
@@ -2055,7 +2099,7 @@ type RateLimitSettings struct {
func (x *RateLimitSettings) Reset() {
*x = RateLimitSettings{}
mi := &file_dns_proto_msgTypes[30]
mi := &file_dns_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2067,7 +2111,7 @@ func (x *RateLimitSettings) String() string {
func (*RateLimitSettings) ProtoMessage() {}
func (x *RateLimitSettings) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[30]
mi := &file_dns_proto_msgTypes[31]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2080,7 +2124,7 @@ func (x *RateLimitSettings) ProtoReflect() protoreflect.Message {
// Deprecated: Use RateLimitSettings.ProtoReflect.Descriptor instead.
func (*RateLimitSettings) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{30}
return file_dns_proto_rawDescGZIP(), []int{31}
}
func (x *RateLimitSettings) GetEnabled() bool {
@@ -2113,7 +2157,7 @@ type RemoteKVGetRequest struct {
func (x *RemoteKVGetRequest) Reset() {
*x = RemoteKVGetRequest{}
mi := &file_dns_proto_msgTypes[31]
mi := &file_dns_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2125,7 +2169,7 @@ func (x *RemoteKVGetRequest) String() string {
func (*RemoteKVGetRequest) ProtoMessage() {}
func (x *RemoteKVGetRequest) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[31]
mi := &file_dns_proto_msgTypes[32]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2138,7 +2182,7 @@ func (x *RemoteKVGetRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RemoteKVGetRequest.ProtoReflect.Descriptor instead.
func (*RemoteKVGetRequest) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{31}
return file_dns_proto_rawDescGZIP(), []int{32}
}
func (x *RemoteKVGetRequest) GetKey() string {
@@ -2161,7 +2205,7 @@ type RemoteKVGetResponse struct {
func (x *RemoteKVGetResponse) Reset() {
*x = RemoteKVGetResponse{}
mi := &file_dns_proto_msgTypes[32]
mi := &file_dns_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2173,7 +2217,7 @@ func (x *RemoteKVGetResponse) String() string {
func (*RemoteKVGetResponse) ProtoMessage() {}
func (x *RemoteKVGetResponse) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[32]
mi := &file_dns_proto_msgTypes[33]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2186,7 +2230,7 @@ func (x *RemoteKVGetResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use RemoteKVGetResponse.ProtoReflect.Descriptor instead.
func (*RemoteKVGetResponse) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{32}
return file_dns_proto_rawDescGZIP(), []int{33}
}
func (x *RemoteKVGetResponse) GetValue() isRemoteKVGetResponse_Value {
@@ -2241,7 +2285,7 @@ type RemoteKVSetRequest struct {
func (x *RemoteKVSetRequest) Reset() {
*x = RemoteKVSetRequest{}
mi := &file_dns_proto_msgTypes[33]
mi := &file_dns_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2253,7 +2297,7 @@ func (x *RemoteKVSetRequest) String() string {
func (*RemoteKVSetRequest) ProtoMessage() {}
func (x *RemoteKVSetRequest) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[33]
mi := &file_dns_proto_msgTypes[34]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2266,7 +2310,7 @@ func (x *RemoteKVSetRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use RemoteKVSetRequest.ProtoReflect.Descriptor instead.
func (*RemoteKVSetRequest) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{33}
return file_dns_proto_rawDescGZIP(), []int{34}
}
func (x *RemoteKVSetRequest) GetKey() string {
@@ -2298,7 +2342,7 @@ type RemoteKVSetResponse struct {
func (x *RemoteKVSetResponse) Reset() {
*x = RemoteKVSetResponse{}
mi := &file_dns_proto_msgTypes[34]
mi := &file_dns_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2310,7 +2354,7 @@ func (x *RemoteKVSetResponse) String() string {
func (*RemoteKVSetResponse) ProtoMessage() {}
func (x *RemoteKVSetResponse) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[34]
mi := &file_dns_proto_msgTypes[35]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2323,7 +2367,7 @@ func (x *RemoteKVSetResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use RemoteKVSetResponse.ProtoReflect.Descriptor instead.
func (*RemoteKVSetResponse) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{34}
return file_dns_proto_rawDescGZIP(), []int{35}
}
type CustomDomainCertificateRequest struct {
@@ -2335,7 +2379,7 @@ type CustomDomainCertificateRequest struct {
func (x *CustomDomainCertificateRequest) Reset() {
*x = CustomDomainCertificateRequest{}
mi := &file_dns_proto_msgTypes[35]
mi := &file_dns_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2347,7 +2391,7 @@ func (x *CustomDomainCertificateRequest) String() string {
func (*CustomDomainCertificateRequest) ProtoMessage() {}
func (x *CustomDomainCertificateRequest) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[35]
mi := &file_dns_proto_msgTypes[36]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2360,7 +2404,7 @@ func (x *CustomDomainCertificateRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use CustomDomainCertificateRequest.ProtoReflect.Descriptor instead.
func (*CustomDomainCertificateRequest) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{35}
return file_dns_proto_rawDescGZIP(), []int{36}
}
func (x *CustomDomainCertificateRequest) GetCertName() string {
@@ -2380,7 +2424,7 @@ type CustomDomainCertificateResponse struct {
func (x *CustomDomainCertificateResponse) Reset() {
*x = CustomDomainCertificateResponse{}
mi := &file_dns_proto_msgTypes[36]
mi := &file_dns_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2392,7 +2436,7 @@ func (x *CustomDomainCertificateResponse) String() string {
func (*CustomDomainCertificateResponse) ProtoMessage() {}
func (x *CustomDomainCertificateResponse) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[36]
mi := &file_dns_proto_msgTypes[37]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2405,7 +2449,7 @@ func (x *CustomDomainCertificateResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use CustomDomainCertificateResponse.ProtoReflect.Descriptor instead.
func (*CustomDomainCertificateResponse) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{36}
return file_dns_proto_rawDescGZIP(), []int{37}
}
func (x *CustomDomainCertificateResponse) GetCertificate() []byte {
@@ -2430,7 +2474,7 @@ type SessionTicketRequest struct {
func (x *SessionTicketRequest) Reset() {
*x = SessionTicketRequest{}
mi := &file_dns_proto_msgTypes[37]
mi := &file_dns_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2442,7 +2486,7 @@ func (x *SessionTicketRequest) String() string {
func (*SessionTicketRequest) ProtoMessage() {}
func (x *SessionTicketRequest) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[37]
mi := &file_dns_proto_msgTypes[38]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2455,7 +2499,7 @@ func (x *SessionTicketRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use SessionTicketRequest.ProtoReflect.Descriptor instead.
func (*SessionTicketRequest) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{37}
return file_dns_proto_rawDescGZIP(), []int{38}
}
type SessionTicketResponse struct {
@@ -2467,7 +2511,7 @@ type SessionTicketResponse struct {
func (x *SessionTicketResponse) Reset() {
*x = SessionTicketResponse{}
mi := &file_dns_proto_msgTypes[38]
mi := &file_dns_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2479,7 +2523,7 @@ func (x *SessionTicketResponse) String() string {
func (*SessionTicketResponse) ProtoMessage() {}
func (x *SessionTicketResponse) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[38]
mi := &file_dns_proto_msgTypes[39]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2492,7 +2536,7 @@ func (x *SessionTicketResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use SessionTicketResponse.ProtoReflect.Descriptor instead.
func (*SessionTicketResponse) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{38}
return file_dns_proto_rawDescGZIP(), []int{39}
}
func (x *SessionTicketResponse) GetTickets() []*SessionTicket {
@@ -2512,7 +2556,7 @@ type SessionTicket struct {
func (x *SessionTicket) Reset() {
*x = SessionTicket{}
mi := &file_dns_proto_msgTypes[39]
mi := &file_dns_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2524,7 +2568,7 @@ func (x *SessionTicket) String() string {
func (*SessionTicket) ProtoMessage() {}
func (x *SessionTicket) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[39]
mi := &file_dns_proto_msgTypes[40]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2537,7 +2581,7 @@ func (x *SessionTicket) ProtoReflect() protoreflect.Message {
// Deprecated: Use SessionTicket.ProtoReflect.Descriptor instead.
func (*SessionTicket) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{39}
return file_dns_proto_rawDescGZIP(), []int{40}
}
func (x *SessionTicket) GetName() string {
@@ -2563,7 +2607,7 @@ type DeviceSettingsChange_Deleted struct {
func (x *DeviceSettingsChange_Deleted) Reset() {
*x = DeviceSettingsChange_Deleted{}
mi := &file_dns_proto_msgTypes[40]
mi := &file_dns_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2575,7 +2619,7 @@ func (x *DeviceSettingsChange_Deleted) String() string {
func (*DeviceSettingsChange_Deleted) ProtoMessage() {}
func (x *DeviceSettingsChange_Deleted) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[40]
mi := &file_dns_proto_msgTypes[41]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2607,7 +2651,7 @@ type DeviceSettingsChange_Upserted struct {
func (x *DeviceSettingsChange_Upserted) Reset() {
*x = DeviceSettingsChange_Upserted{}
mi := &file_dns_proto_msgTypes[41]
mi := &file_dns_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2619,7 +2663,7 @@ func (x *DeviceSettingsChange_Upserted) String() string {
func (*DeviceSettingsChange_Upserted) ProtoMessage() {}
func (x *DeviceSettingsChange_Upserted) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[41]
mi := &file_dns_proto_msgTypes[42]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2652,7 +2696,7 @@ type CustomDomain_Pending struct {
func (x *CustomDomain_Pending) Reset() {
*x = CustomDomain_Pending{}
mi := &file_dns_proto_msgTypes[42]
mi := &file_dns_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2664,7 +2708,7 @@ func (x *CustomDomain_Pending) String() string {
func (*CustomDomain_Pending) ProtoMessage() {}
func (x *CustomDomain_Pending) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[42]
mi := &file_dns_proto_msgTypes[43]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2706,7 +2750,7 @@ type CustomDomain_Current struct {
func (x *CustomDomain_Current) Reset() {
*x = CustomDomain_Current{}
mi := &file_dns_proto_msgTypes[43]
mi := &file_dns_proto_msgTypes[44]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -2718,7 +2762,7 @@ func (x *CustomDomain_Current) String() string {
func (*CustomDomain_Current) ProtoMessage() {}
func (x *CustomDomain_Current) ProtoReflect() protoreflect.Message {
mi := &file_dns_proto_msgTypes[43]
mi := &file_dns_proto_msgTypes[44]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2916,6 +2960,8 @@ const file_dns_proto_rawDesc = "" +
"\x0fBadRequestError\x12\x18\n" +
"\amessage\x18\x01 \x01(\tR\amessage\"5\n" +
"\x19AuthenticationFailedError\x12\x18\n" +
"\amessage\x18\x01 \x01(\tR\amessage\")\n" +
"\rNotFoundError\x12\x18\n" +
"\amessage\x18\x01 \x01(\tR\amessage\"l\n" +
"\x11RateLimitSettings\x12\x18\n" +
"\aenabled\x18\x01 \x01(\bR\aenabled\x12\x10\n" +
@@ -2989,7 +3035,7 @@ func file_dns_proto_rawDescGZIP() []byte {
}
var file_dns_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_dns_proto_msgTypes = make([]protoimpl.MessageInfo, 44)
var file_dns_proto_msgTypes = make([]protoimpl.MessageInfo, 45)
var file_dns_proto_goTypes = []any{
(DeviceType)(0), // 0: DeviceType
(*RateLimitSettingsRequest)(nil), // 1: RateLimitSettingsRequest
@@ -3022,46 +3068,47 @@ var file_dns_proto_goTypes = []any{
(*DeviceQuotaExceededError)(nil), // 28: DeviceQuotaExceededError
(*BadRequestError)(nil), // 29: BadRequestError
(*AuthenticationFailedError)(nil), // 30: AuthenticationFailedError
(*RateLimitSettings)(nil), // 31: RateLimitSettings
(*RemoteKVGetRequest)(nil), // 32: RemoteKVGetRequest
(*RemoteKVGetResponse)(nil), // 33: RemoteKVGetResponse
(*RemoteKVSetRequest)(nil), // 34: RemoteKVSetRequest
(*RemoteKVSetResponse)(nil), // 35: RemoteKVSetResponse
(*CustomDomainCertificateRequest)(nil), // 36: CustomDomainCertificateRequest
(*CustomDomainCertificateResponse)(nil), // 37: CustomDomainCertificateResponse
(*SessionTicketRequest)(nil), // 38: SessionTicketRequest
(*SessionTicketResponse)(nil), // 39: SessionTicketResponse
(*SessionTicket)(nil), // 40: SessionTicket
(*DeviceSettingsChange_Deleted)(nil), // 41: DeviceSettingsChange.Deleted
(*DeviceSettingsChange_Upserted)(nil), // 42: DeviceSettingsChange.Upserted
(*CustomDomain_Pending)(nil), // 43: CustomDomain.Pending
(*CustomDomain_Current)(nil), // 44: CustomDomain.Current
(*timestamppb.Timestamp)(nil), // 45: google.protobuf.Timestamp
(*durationpb.Duration)(nil), // 46: google.protobuf.Duration
(*emptypb.Empty)(nil), // 47: google.protobuf.Empty
(*NotFoundError)(nil), // 31: NotFoundError
(*RateLimitSettings)(nil), // 32: RateLimitSettings
(*RemoteKVGetRequest)(nil), // 33: RemoteKVGetRequest
(*RemoteKVGetResponse)(nil), // 34: RemoteKVGetResponse
(*RemoteKVSetRequest)(nil), // 35: RemoteKVSetRequest
(*RemoteKVSetResponse)(nil), // 36: RemoteKVSetResponse
(*CustomDomainCertificateRequest)(nil), // 37: CustomDomainCertificateRequest
(*CustomDomainCertificateResponse)(nil), // 38: CustomDomainCertificateResponse
(*SessionTicketRequest)(nil), // 39: SessionTicketRequest
(*SessionTicketResponse)(nil), // 40: SessionTicketResponse
(*SessionTicket)(nil), // 41: SessionTicket
(*DeviceSettingsChange_Deleted)(nil), // 42: DeviceSettingsChange.Deleted
(*DeviceSettingsChange_Upserted)(nil), // 43: DeviceSettingsChange.Upserted
(*CustomDomain_Pending)(nil), // 44: CustomDomain.Pending
(*CustomDomain_Current)(nil), // 45: CustomDomain.Current
(*timestamppb.Timestamp)(nil), // 46: google.protobuf.Timestamp
(*durationpb.Duration)(nil), // 47: google.protobuf.Duration
(*emptypb.Empty)(nil), // 48: google.protobuf.Empty
}
var file_dns_proto_depIdxs = []int32{
23, // 0: RateLimitSettingsResponse.allowed_subnets:type_name -> CidrRange
22, // 1: GlobalAccessSettingsResponse.standard:type_name -> AccessSettings
45, // 2: DNSProfilesRequest.sync_time:type_name -> google.protobuf.Timestamp
46, // 2: DNSProfilesRequest.sync_time:type_name -> google.protobuf.Timestamp
10, // 3: DNSProfile.safe_browsing:type_name -> SafeBrowsingSettings
12, // 4: DNSProfile.parental:type_name -> ParentalSettings
16, // 5: DNSProfile.rule_lists:type_name -> RuleListsSettings
11, // 6: DNSProfile.devices:type_name -> DeviceSettings
46, // 7: DNSProfile.filtered_response_ttl:type_name -> google.protobuf.Duration
47, // 7: DNSProfile.filtered_response_ttl:type_name -> google.protobuf.Duration
17, // 8: DNSProfile.blocking_mode_custom_ip:type_name -> BlockingModeCustomIP
18, // 9: DNSProfile.blocking_mode_nxdomain:type_name -> BlockingModeNXDOMAIN
19, // 10: DNSProfile.blocking_mode_null_ip:type_name -> BlockingModeNullIP
20, // 11: DNSProfile.blocking_mode_refused:type_name -> BlockingModeREFUSED
22, // 12: DNSProfile.access:type_name -> AccessSettings
31, // 13: DNSProfile.rate_limit:type_name -> RateLimitSettings
32, // 13: DNSProfile.rate_limit:type_name -> RateLimitSettings
8, // 14: DNSProfile.custom_domain:type_name -> CustomDomainSettings
7, // 15: DNSProfile.device_changes:type_name -> DeviceSettingsChange
41, // 16: DeviceSettingsChange.deleted:type_name -> DeviceSettingsChange.Deleted
42, // 17: DeviceSettingsChange.upserted:type_name -> DeviceSettingsChange.Upserted
42, // 16: DeviceSettingsChange.deleted:type_name -> DeviceSettingsChange.Deleted
43, // 17: DeviceSettingsChange.upserted:type_name -> DeviceSettingsChange.Upserted
9, // 18: CustomDomainSettings.domains:type_name -> CustomDomain
43, // 19: CustomDomain.pending:type_name -> CustomDomain.Pending
44, // 20: CustomDomain.current:type_name -> CustomDomain.Current
44, // 19: CustomDomain.pending:type_name -> CustomDomain.Pending
45, // 20: CustomDomain.current:type_name -> CustomDomain.Current
24, // 21: DeviceSettings.authentication:type_name -> AuthenticationSettings
13, // 22: ParentalSettings.schedule:type_name -> ScheduleSettings
14, // 23: ScheduleSettings.weeklyRange:type_name -> WeeklyRange
@@ -3072,40 +3119,40 @@ var file_dns_proto_depIdxs = []int32{
15, // 28: WeeklyRange.fri:type_name -> DayRange
15, // 29: WeeklyRange.sat:type_name -> DayRange
15, // 30: WeeklyRange.sun:type_name -> DayRange
46, // 31: DayRange.start:type_name -> google.protobuf.Duration
46, // 32: DayRange.end:type_name -> google.protobuf.Duration
45, // 33: DeviceBillingStat.last_activity_time:type_name -> google.protobuf.Timestamp
47, // 31: DayRange.start:type_name -> google.protobuf.Duration
47, // 32: DayRange.end:type_name -> google.protobuf.Duration
46, // 33: DeviceBillingStat.last_activity_time:type_name -> google.protobuf.Timestamp
23, // 34: AccessSettings.allowlist_cidr:type_name -> CidrRange
23, // 35: AccessSettings.blocklist_cidr:type_name -> CidrRange
0, // 36: CreateDeviceRequest.device_type:type_name -> DeviceType
11, // 37: CreateDeviceResponse.device:type_name -> DeviceSettings
46, // 38: RateLimitedError.retry_delay:type_name -> google.protobuf.Duration
47, // 38: RateLimitedError.retry_delay:type_name -> google.protobuf.Duration
23, // 39: RateLimitSettings.client_cidr:type_name -> CidrRange
47, // 40: RemoteKVGetResponse.empty:type_name -> google.protobuf.Empty
46, // 41: RemoteKVSetRequest.ttl:type_name -> google.protobuf.Duration
40, // 42: SessionTicketResponse.tickets:type_name -> SessionTicket
48, // 40: RemoteKVGetResponse.empty:type_name -> google.protobuf.Empty
47, // 41: RemoteKVSetRequest.ttl:type_name -> google.protobuf.Duration
41, // 42: SessionTicketResponse.tickets:type_name -> SessionTicket
11, // 43: DeviceSettingsChange.Upserted.device:type_name -> DeviceSettings
45, // 44: CustomDomain.Pending.expire:type_name -> google.protobuf.Timestamp
45, // 45: CustomDomain.Current.not_before:type_name -> google.protobuf.Timestamp
45, // 46: CustomDomain.Current.not_after:type_name -> google.protobuf.Timestamp
46, // 44: CustomDomain.Pending.expire:type_name -> google.protobuf.Timestamp
46, // 45: CustomDomain.Current.not_before:type_name -> google.protobuf.Timestamp
46, // 46: CustomDomain.Current.not_after:type_name -> google.protobuf.Timestamp
5, // 47: DNSService.getDNSProfiles:input_type -> DNSProfilesRequest
21, // 48: DNSService.saveDevicesBillingStat:input_type -> DeviceBillingStat
25, // 49: DNSService.createDeviceByHumanId:input_type -> CreateDeviceRequest
1, // 50: RateLimitService.getRateLimitSettings:input_type -> RateLimitSettingsRequest
3, // 51: RateLimitService.getGlobalAccessSettings:input_type -> GlobalAccessSettingsRequest
32, // 52: RemoteKVService.get:input_type -> RemoteKVGetRequest
34, // 53: RemoteKVService.set:input_type -> RemoteKVSetRequest
36, // 54: CustomDomainService.getCustomDomainCertificate:input_type -> CustomDomainCertificateRequest
38, // 55: SessionTicketService.getSessionTickets:input_type -> SessionTicketRequest
33, // 52: RemoteKVService.get:input_type -> RemoteKVGetRequest
35, // 53: RemoteKVService.set:input_type -> RemoteKVSetRequest
37, // 54: CustomDomainService.getCustomDomainCertificate:input_type -> CustomDomainCertificateRequest
39, // 55: SessionTicketService.getSessionTickets:input_type -> SessionTicketRequest
6, // 56: DNSService.getDNSProfiles:output_type -> DNSProfile
47, // 57: DNSService.saveDevicesBillingStat:output_type -> google.protobuf.Empty
48, // 57: DNSService.saveDevicesBillingStat:output_type -> google.protobuf.Empty
26, // 58: DNSService.createDeviceByHumanId:output_type -> CreateDeviceResponse
2, // 59: RateLimitService.getRateLimitSettings:output_type -> RateLimitSettingsResponse
4, // 60: RateLimitService.getGlobalAccessSettings:output_type -> GlobalAccessSettingsResponse
33, // 61: RemoteKVService.get:output_type -> RemoteKVGetResponse
35, // 62: RemoteKVService.set:output_type -> RemoteKVSetResponse
37, // 63: CustomDomainService.getCustomDomainCertificate:output_type -> CustomDomainCertificateResponse
39, // 64: SessionTicketService.getSessionTickets:output_type -> SessionTicketResponse
34, // 61: RemoteKVService.get:output_type -> RemoteKVGetResponse
36, // 62: RemoteKVService.set:output_type -> RemoteKVSetResponse
38, // 63: CustomDomainService.getCustomDomainCertificate:output_type -> CustomDomainCertificateResponse
40, // 64: SessionTicketService.getSessionTickets:output_type -> SessionTicketResponse
56, // [56:65] is the sub-list for method output_type
47, // [47:56] is the sub-list for method input_type
47, // [47:47] is the sub-list for extension type_name
@@ -3135,7 +3182,7 @@ func file_dns_proto_init() {
file_dns_proto_msgTypes[23].OneofWrappers = []any{
(*AuthenticationSettings_PasswordHashBcrypt)(nil),
}
file_dns_proto_msgTypes[32].OneofWrappers = []any{
file_dns_proto_msgTypes[33].OneofWrappers = []any{
(*RemoteKVGetResponse_Data)(nil),
(*RemoteKVGetResponse_Empty)(nil),
}
@@ -3145,7 +3192,7 @@ func file_dns_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_dns_proto_rawDesc), len(file_dns_proto_rawDesc)),
NumEnums: 1,
NumMessages: 44,
NumMessages: 45,
NumExtensions: 0,
NumServices: 5,
},

View File

@@ -85,7 +85,8 @@ service CustomDomainService {
This method may return the following errors:
- AuthenticationFailedError: If the authentication failed.
- BadRequestError: If the request is invalid: cert_name is empty or no certificate found.
- BadRequestError: If the request is invalid: cert_name is empty.
- NotFoundError: If the certificate could not be found.
*/
rpc getCustomDomainCertificate(CustomDomainCertificateRequest) returns (CustomDomainCertificateResponse);
}
@@ -341,6 +342,10 @@ message AuthenticationFailedError {
string message = 1;
}
message NotFoundError {
string message = 1;
}
message RateLimitSettings {
bool enabled = 1;
uint32 rps = 2;

View File

@@ -554,7 +554,8 @@ type CustomDomainServiceClient interface {
//
// This method may return the following errors:
// - AuthenticationFailedError: If the authentication failed.
// - BadRequestError: If the request is invalid: cert_name is empty or no certificate found.
// - BadRequestError: If the request is invalid: cert_name is empty.
// - NotFoundError: If the certificate could not be found.
GetCustomDomainCertificate(ctx context.Context, in *CustomDomainCertificateRequest, opts ...grpc.CallOption) (*CustomDomainCertificateResponse, error)
}
@@ -584,7 +585,8 @@ type CustomDomainServiceServer interface {
//
// This method may return the following errors:
// - AuthenticationFailedError: If the authentication failed.
// - BadRequestError: If the request is invalid: cert_name is empty or no certificate found.
// - BadRequestError: If the request is invalid: cert_name is empty.
// - NotFoundError: If the certificate could not be found.
GetCustomDomainCertificate(context.Context, *CustomDomainCertificateRequest) (*CustomDomainCertificateResponse, error)
mustEmbedUnimplementedCustomDomainServiceServer()
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
@@ -17,6 +18,8 @@ import (
// [GRPCMetrics.IncrementErrorCount].
//
// TODO(e.burkov): Move error types to this package.
//
// TODO(a.garipov): Separate metrics reporting from error fixing.
func fixGRPCError(ctx context.Context, mtrc GRPCMetrics, err error) (res error) {
metricsType := GRPCErrOther
defer func() { mtrc.IncrementErrorCount(ctx, metricsType) }()
@@ -56,6 +59,15 @@ func fixGRPCError(ctx context.Context, mtrc GRPCMetrics, err error) (res error)
return &profiledb.DeviceQuotaExceededError{
Message: structErr.Message,
}
case *NotFoundError:
metricsType = GRPCErrNotFound
// This error can currently only be returned from the certificate
// API, so return the error that it expects.
//
// TODO(a.garipov): Fix this and don't assume that this error is
// only returned there.
return fmt.Errorf("%s: %w", structErr.Message, tlsconfig.ErrCertificateNotFound)
case *RateLimitedError:
metricsType = GRPCErrRateLimit

View File

@@ -19,6 +19,7 @@ const (
GRPCErrAuthentication GRPCError = "auth"
GRPCErrBadRequest GRPCError = "bad_req"
GRPCErrDeviceQuota GRPCError = "dev_quota"
GRPCErrNotFound GRPCError = "not_found"
GRPCErrOther GRPCError = "other"
GRPCErrRateLimit GRPCError = "rate_limit"
GRPCErrTimeout GRPCError = "timeout"
@@ -150,6 +151,9 @@ type TicketStorageMetrics interface {
err error,
)
// SetTicketsState sets the state number code of the session tickets.
SetTicketsState(ctx context.Context, num float64)
// ObserveUpdate sets the duration of the session ticket update operation.
ObserveUpdate(ctx context.Context, dur time.Duration, err error)
}
@@ -171,6 +175,10 @@ func (EmptyTicketStorageMetrics) SetTicketStatus(
) {
}
// SetTicketsState implements the [TicketStorageMetrics] interface for
// EmptyTicketStorageMetrics.
func (EmptyTicketStorageMetrics) SetTicketsState(_ context.Context, _ float64) {}
// ObserveUpdate implements the [TicketStorageMetrics] interface for
// EmptyTicketStorageMetrics.
func (EmptyTicketStorageMetrics) ObserveUpdate(_ context.Context, _ time.Duration, _ error) {}

View File

@@ -81,19 +81,19 @@ func (x *SafeBrowsingSettings) toInternal() (c *filter.ConfigSafeBrowsing) {
}
// toInternal converts protobuf access settings to an internal structure. If x
// is nil, toInternal returns [access.EmptyProfile].
// is nil, toInternal returns [access.EmptyProfile]. all arguments must not be
// nil.
func (x *AccessSettings) toInternal(
ctx context.Context,
errColl errcoll.Interface,
mtrc access.ProfileMetrics,
logger *slog.Logger,
errColl errcoll.Interface,
cons *access.ProfileConstructor,
) (a access.Profile) {
if x == nil || !x.Enabled {
return access.EmptyProfile{}
}
return access.NewDefaultProfile(&access.ProfileConfig{
Metrics: mtrc,
return cons.New(&access.ProfileConfig{
AllowedNets: cidrRangeToInternal(ctx, errColl, logger, x.AllowlistCidr),
BlockedNets: cidrRangeToInternal(ctx, errColl, logger, x.BlocklistCidr),
AllowedASN: asnToInternal(x.AllowlistAsn),

View File

@@ -14,10 +14,10 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/custom"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/AdguardTeam/golibs/netutil"
"github.com/c2h5oh/datasize"
"google.golang.org/grpc"
@@ -35,6 +35,14 @@ type ProfileStorageConfig struct {
// BaseCustomLogger is the base logger used for the custom filters.
BaseCustomLogger *slog.Logger
// Endpoint is the backend API URL. The scheme should be either "grpc" or
// "grpcs". It must not be nil.
Endpoint *url.URL
// ProfileAccessConstructor is used to create access managers for profiles.
// It must not be nil.
ProfileAccessConstructor *access.ProfileConstructor
// BindSet is the subnet set created from DNS servers listening addresses.
// It must not be nil.
BindSet netutil.SubnetSet
@@ -43,10 +51,6 @@ type ProfileStorageConfig struct {
// non-critical errors. It must not be nil.
ErrColl errcoll.Interface
// ProfileMetrics is used for the collection of the profile access engine
// statistics. It must not be nil.
ProfileMetrics access.ProfileMetrics
// GRPCMetrics is used for the collection of the protobuf communication
// statistics.
GRPCMetrics GRPCMetrics
@@ -54,10 +58,6 @@ type ProfileStorageConfig struct {
// Metrics is used for the collection of the profiles storage statistics.
Metrics ProfileDBMetrics
// Endpoint is the backend API URL. The scheme should be either "grpc" or
// "grpcs". It must not be nil.
Endpoint *url.URL
// APIKey is the API key used for authentication, if any. If empty, no
// authentication is performed.
APIKey string
@@ -77,10 +77,10 @@ type ProfileStorageConfig struct {
type ProfileStorage struct {
logger *slog.Logger
baseCustomLogger *slog.Logger
profAccessCons *access.ProfileConstructor
bindSet netutil.SubnetSet
errColl errcoll.Interface
client DNSServiceClient
profileMetrics access.ProfileMetrics
grpcMetrics GRPCMetrics
metrics ProfileDBMetrics
apiKey string
@@ -100,10 +100,10 @@ func NewProfileStorage(c *ProfileStorageConfig) (s *ProfileStorage, err error) {
return &ProfileStorage{
logger: c.Logger,
baseCustomLogger: c.BaseCustomLogger,
profAccessCons: c.ProfileAccessConstructor,
bindSet: c.BindSet,
errColl: c.ErrColl,
client: NewDNSServiceClient(client),
profileMetrics: c.ProfileMetrics,
grpcMetrics: c.GRPCMetrics,
metrics: c.Metrics,
apiKey: c.APIKey,
@@ -313,7 +313,7 @@ func (s *ProfileStorage) newProfile(
RuleList: p.RuleLists.toInternal(ctx, s.errColl, s.logger),
SafeBrowsing: p.SafeBrowsing.toInternal(),
},
Access: p.Access.toInternal(ctx, s.errColl, s.profileMetrics, s.logger),
Access: p.Access.toInternal(ctx, s.logger, s.errColl, s.profAccessCons),
BlockingMode: m,
Ratelimiter: p.RateLimit.toInternal(ctx, s.errColl, s.logger, s.respSzEst),
AccountID: accID,

View File

@@ -38,9 +38,9 @@ func TestProfileStorage_NewProfile(t *testing.T) {
profileStorage := &ProfileStorage{
logger: TestLogger,
baseCustomLogger: TestLogger,
profAccessCons: TestProfileAccessConstructor,
bindSet: TestBind,
errColl: agdtest.NewErrorCollector(),
profileMetrics: access.EmptyProfileMetrics{},
grpcMetrics: EmptyGRPCMetrics{},
metrics: EmptyProfileDBMetrics{},
respSzEst: TestRespSzEst,
@@ -73,9 +73,9 @@ func TestProfileStorage_NewProfile(t *testing.T) {
storage := &ProfileStorage{
logger: TestLogger,
baseCustomLogger: TestLogger,
profAccessCons: TestProfileAccessConstructor,
bindSet: TestBind,
errColl: savingErrColl,
profileMetrics: access.EmptyProfileMetrics{},
grpcMetrics: EmptyGRPCMetrics{},
metrics: EmptyProfileDBMetrics{},
respSzEst: TestRespSzEst,
@@ -113,9 +113,9 @@ func TestProfileStorage_NewProfile(t *testing.T) {
storage := &ProfileStorage{
logger: TestLogger,
baseCustomLogger: TestLogger,
profAccessCons: TestProfileAccessConstructor,
bindSet: bindSet,
errColl: savingErrColl,
profileMetrics: access.EmptyProfileMetrics{},
grpcMetrics: EmptyGRPCMetrics{},
metrics: EmptyProfileDBMetrics{},
respSzEst: TestRespSzEst,
@@ -551,8 +551,7 @@ func newProfile(tb testing.TB) (p *agd.Profile) {
IPv6: []netip.Addr{netip.MustParseAddr("1234::cdef")},
}
wantAccess := access.NewDefaultProfile(&access.ProfileConfig{
Metrics: access.EmptyProfileMetrics{},
wantAccess := TestProfileAccessConstructor.New(&access.ProfileConfig{
AllowedNets: []netip.Prefix{netip.MustParsePrefix("1.1.1.0/24")},
BlockedNets: []netip.Prefix{netip.MustParsePrefix("2.2.2.0/24")},
AllowedASN: []geoip.ASN{1},
@@ -722,9 +721,9 @@ func BenchmarkProfileStorage_NewProfile(b *testing.B) {
profileStorage := &ProfileStorage{
logger: TestLogger,
baseCustomLogger: TestLogger,
profAccessCons: TestProfileAccessConstructor,
bindSet: TestBind,
errColl: agdtest.NewErrorCollector(),
profileMetrics: access.EmptyProfileMetrics{},
grpcMetrics: EmptyGRPCMetrics{},
metrics: EmptyProfileDBMetrics{},
respSzEst: TestRespSzEst,
@@ -743,9 +742,9 @@ func BenchmarkProfileStorage_NewProfile(b *testing.B) {
// Most recent results:
//
// goos: darwin
// goarch: arm64
// goos: linux
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/backendpb
// cpu: Apple M1 Pro
// BenchmarkProfileStorage_NewProfile-8 69070 16238 ns/op 3864 B/op 75 allocs/op
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkProfileStorage_NewProfile-16 98218 18425 ns/op 4008 B/op 76 allocs/op
}

View File

@@ -70,13 +70,14 @@ func TestProfileStorage_CreateAutoDevice(t *testing.T) {
endpoint := runLocalGRPCServer(t, grpcSrv)
s, err := backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{
BindSet: backendpb.TestBind,
ErrColl: agdtest.NewErrorCollector(),
Logger: backendpb.TestLogger,
BaseCustomLogger: backendpb.TestLogger,
GRPCMetrics: backendpb.EmptyGRPCMetrics{},
Metrics: backendpb.EmptyProfileDBMetrics{},
Endpoint: endpoint,
Logger: backendpb.TestLogger,
BaseCustomLogger: backendpb.TestLogger,
Endpoint: endpoint,
ProfileAccessConstructor: backendpb.TestProfileAccessConstructor,
BindSet: backendpb.TestBind,
ErrColl: agdtest.NewErrorCollector(),
GRPCMetrics: backendpb.EmptyGRPCMetrics{},
Metrics: backendpb.EmptyProfileDBMetrics{},
})
require.NoError(t, err)
@@ -140,17 +141,18 @@ func BenchmarkProfileStorage_Profiles(b *testing.B) {
require.NoError(b, err)
s, err := backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{
BindSet: netip.MustParsePrefix("0.0.0.0/0"),
ErrColl: agdtest.NewErrorCollector(),
Logger: backendpb.TestLogger,
BaseCustomLogger: backendpb.TestLogger,
GRPCMetrics: backendpb.EmptyGRPCMetrics{},
Metrics: backendpb.EmptyProfileDBMetrics{},
Endpoint: &url.URL{
Scheme: "grpc",
Host: l.Addr().String(),
},
MaxProfilesSize: 1 * datasize.MB,
ProfileAccessConstructor: backendpb.TestProfileAccessConstructor,
BindSet: netip.MustParsePrefix("0.0.0.0/0"),
ErrColl: agdtest.NewErrorCollector(),
GRPCMetrics: backendpb.EmptyGRPCMetrics{},
Metrics: backendpb.EmptyProfileDBMetrics{},
MaxProfilesSize: 1 * datasize.MB,
})
require.NoError(b, err)
@@ -187,5 +189,5 @@ func BenchmarkProfileStorage_Profiles(b *testing.B) {
// goarch: amd64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/backendpb
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkProfileStorage_Profiles-16 3982 322718 ns/op 21769 B/op 388 allocs/op
// BenchmarkProfileStorage_Profiles-16 6260 177333 ns/op 22001 B/op 388 allocs/op
}

View File

@@ -2,9 +2,13 @@ package backendpb
import (
"context"
"crypto/sha256"
"encoding/binary"
"fmt"
"log/slog"
"maps"
"net/url"
"slices"
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
"github.com/AdguardTeam/golibs/timeutil"
@@ -91,8 +95,52 @@ func (ts *TicketStorage) Tickets(
}
tickets, err = ts.ticketsToInternal(ctx, resp.GetTickets())
ts.logger.DebugContext(ctx, "loaded session tickets", "count", len(tickets))
if err != nil {
return tickets, fmt.Errorf("converting: %w", err)
}
return tickets, err
ts.metrics.SetTicketsState(ctx, calcTicketsHash(tickets))
return tickets, nil
}
// calcTicketsHash calculates a hash of the tickets and returns a part of it as
// a float64 number. Returns 0 if there are no tickets.
func calcTicketsHash(tickets tlsconfig.NamedTickets) (num float64) {
if len(tickets) == 0 {
return 0
}
// Start a new SHA256 hash sum.
h := sha256.New()
// Add each ticket's data to the hash sum. The errors are ignored, because
// [hash.Hash] never returns an error.
// NOTE: Sorted by name, as strings, so "ticket_10" goes before "ticket_2".
for _, name := range slices.Sorted(maps.Keys(tickets)) {
// NOTE: Name first, data second, with no separators between them.
_, _ = h.Write([]byte(name))
data := tickets[name]
_, _ = h.Write(data[:])
}
hashData := h.Sum(nil)
// Now, the bytes that will become our uint64 and then float64.
//
// NOTE: Java will have to use a long signed integer here and below, but
// since we only use 48 bits, there should be no signedness issues.
intData := make([]byte, 8)
// Copy the first six bytes to the least significant bytes of the integer
// data to prevent signedness issues.
copy(intData[2:8], hashData[0:6])
// Since we only use 48 bits, the integer should fit into a float64 (aka
// double in Java) with no issues.
num = float64(binary.BigEndian.Uint64(intData))
return num
}

View File

@@ -0,0 +1,41 @@
package backendpb
import (
"testing"
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
"github.com/stretchr/testify/assert"
)
func TestTicketStorage_CalcTicketsHash(t *testing.T) {
t.Parallel()
testCases := []struct {
tickets tlsconfig.NamedTickets
name string
want float64
}{{
tickets: tlsconfig.NamedTickets{
"foo": tlsconfig.SessionTicket{1, 2, 3, 4},
"bar": tlsconfig.SessionTicket{5, 6, 7, 8},
},
name: "data",
want: 2.5599110696847e+14,
}, {
tickets: tlsconfig.NamedTickets{"foo": tlsconfig.SessionTicket{}},
name: "no_data",
want: 1.76700443131662e+14,
}, {
tickets: tlsconfig.NamedTickets{},
name: "no_tickets",
want: 0,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.want, calcTicketsHash(tc.tickets))
})
}
}

View File

@@ -70,7 +70,7 @@ func TestTicketStorage_Tickets(t *testing.T) {
const badTicketName tlsconfig.SessionTicketName = "test/ticket"
badTicketData := []byte{1, 2, 3, 4}
const wantErrMsg = `loaded session ticket: ` +
const wantErrMsg = `converting: loaded session ticket: ` +
`at index 1: str: at index 4: bad rune '/'` + "\n" +
`ticket: length: out of range: must be no less than 32, got 4`

View File

@@ -0,0 +1,41 @@
//go:build linux
package bindtodevice
import (
"net"
"testing"
"github.com/stretchr/testify/require"
)
// newTestChanListener is a helper for creating a *chanListener for tests.
func newTestChanListener(tb testing.TB, conns chan net.Conn) (l *chanListener) {
tb.Helper()
l = newChanListener(EmptyMetrics{}, conns, testSubnetIPv4, testLAddr)
require.NotNil(tb, l)
return l
}
// newTestChanPacketConn is a helper for creating a *chanPacketConn for tests.
func newTestChanPacketConn(
tb testing.TB,
sessions chan *packetSession,
writeReqs chan *packetConnWriteReq,
) (c *chanPacketConn) {
tb.Helper()
c = newChanPacketConn(
EmptyMetrics{},
sessions,
testSubnetIPv4,
writeReqs,
"",
testLAddr,
)
require.NotNil(tb, c)
return c
}

View File

@@ -3,12 +3,10 @@
package bindtodevice
import (
"context"
"net"
"net/netip"
"sync"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/prometheus/client_golang/prometheus"
)
// chanListener is a [net.Listener] that returns data sent to it through a
@@ -18,23 +16,29 @@ import (
// module dnsserver to make the bind-to-device logic work in DNS-over-TCP.
type chanListener struct {
// mu protects conns (against closure) and isClosed.
mu *sync.Mutex
conns chan net.Conn
connsGauge prometheus.Gauge
laddr net.Addr
subnet netip.Prefix
isClosed bool
mu *sync.Mutex
conns chan net.Conn
metrics Metrics
laddr net.Addr
subnet netip.Prefix
isClosed bool
}
// newChanListener returns a new properly initialized *chanListener.
func newChanListener(conns chan net.Conn, subnet netip.Prefix, laddr net.Addr) (l *chanListener) {
// newChanListener returns a new properly initialized *chanListener. mtrc must
// not be nil.
func newChanListener(
mtrc Metrics,
conns chan net.Conn,
subnet netip.Prefix,
laddr net.Addr,
) (l *chanListener) {
return &chanListener{
mu: &sync.Mutex{},
conns: conns,
connsGauge: metrics.BindToDeviceTCPConnsChanSize.WithLabelValues(subnet.String()),
laddr: laddr,
subnet: subnet,
isClosed: false,
mu: &sync.Mutex{},
conns: conns,
metrics: mtrc,
laddr: laddr,
subnet: subnet,
isClosed: false,
}
}
@@ -72,7 +76,7 @@ func (l *chanListener) Close() (err error) {
// send is a helper method to send a conn to the listener's channel. ok is
// false if the listener is closed.
func (l *chanListener) send(conn net.Conn) (ok bool) {
func (l *chanListener) send(ctx context.Context, conn net.Conn) (ok bool) {
l.mu.Lock()
defer l.mu.Unlock()
@@ -82,7 +86,7 @@ func (l *chanListener) send(conn net.Conn) (ok bool) {
l.conns <- conn
l.connsGauge.Set(float64(len(l.conns)))
l.metrics.SetTCPConnsChanSize(ctx, l.subnet, uint(len(l.conns)))
return true
}

View File

@@ -12,7 +12,7 @@ import (
func TestChanListener_Accept(t *testing.T) {
conns := make(chan net.Conn, 1)
l := newChanListener(conns, testSubnetIPv4, testLAddr)
l := newTestChanListener(t, conns)
// A simple way to have a distinct net.Conn without actually implementing
// the entire interface.
@@ -32,14 +32,14 @@ func TestChanListener_Accept(t *testing.T) {
}
func TestChanListener_Addr(t *testing.T) {
l := newChanListener(nil, testSubnetIPv4, testLAddr)
l := newTestChanListener(t, nil)
got := l.Addr()
assert.Equal(t, testLAddr, got)
}
func TestChanListener_Close(t *testing.T) {
conns := make(chan net.Conn)
l := newChanListener(conns, testSubnetIPv4, testLAddr)
l := newTestChanListener(t, conns)
err := l.Close()
assert.NoError(t, err)

View File

@@ -3,6 +3,7 @@
package bindtodevice
import (
"context"
"fmt"
"net"
"net/netip"
@@ -11,8 +12,6 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/prometheus/client_golang/prometheus"
)
// chanPacketConn is a [netext.SessionPacketConn] that returns data sent to it
@@ -28,25 +27,27 @@ type chanPacketConn struct {
writeRequests chan *packetConnWriteReq
sessionsGauge prometheus.Gauge
writeRequestsGauge prometheus.Gauge
metrics Metrics
// deadlineMu protects readDeadline and writeDeadline.
deadlineMu *sync.RWMutex
readDeadline time.Time
writeDeadline time.Time
laddr net.Addr
subnet netip.Prefix
isClosed bool
laddr net.Addr
subnet netip.Prefix
ifaceName string
isClosed bool
}
// newChanPacketConn returns a new properly initialized *chanPacketConn.
// newChanPacketConn returns a new properly initialized *chanPacketConn. mtrc
// must not be nil.
func newChanPacketConn(
mtrc Metrics,
sessions chan *packetSession,
subnet netip.Prefix,
writeRequests chan *packetConnWriteReq,
writeRequestsGauge prometheus.Gauge,
ifaceName string,
laddr net.Addr,
) (c *chanPacketConn) {
return &chanPacketConn{
@@ -54,15 +55,14 @@ func newChanPacketConn(
sessions: sessions,
writeRequests: writeRequests,
sessionsGauge: metrics.BindToDeviceUDPSessionsChanSize.WithLabelValues(
subnet.String(),
),
writeRequestsGauge: writeRequestsGauge,
metrics: mtrc,
deadlineMu: &sync.RWMutex{},
laddr: laddr,
subnet: subnet,
ifaceName: ifaceName,
}
}
@@ -281,7 +281,8 @@ func (c *chanPacketConn) writeToSession(
return 0, wrapConnError(tnChanPConn, fnName, c.laddr, err)
}
c.writeRequestsGauge.Set(float64(len(c.writeRequests)))
// TODO(s.chzhen): Pass context.
c.metrics.SetUDPWriteRequestsChanSize(context.TODO(), c.ifaceName, uint(len(c.writeRequests)))
r, err := receiveWithTimer(resp, timerCh)
if err != nil {
@@ -325,7 +326,7 @@ func sendWithTimer[T any](ch chan<- T, v T, timerCh <-chan time.Time) (err error
// send is a helper method to send a session to the packet connection's channel.
// ok is false if the listener is closed.
func (c *chanPacketConn) send(sess *packetSession) (ok bool) {
func (c *chanPacketConn) send(ctx context.Context, sess *packetSession) (ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
@@ -335,7 +336,7 @@ func (c *chanPacketConn) send(sess *packetSession) (ok bool) {
c.sessions <- sess
c.sessionsGauge.Set(float64(len(c.sessions)))
c.metrics.SetUDPSessionsChanSize(ctx, c.subnet, uint(len(c.sessions)))
return true
}

View File

@@ -8,14 +8,13 @@ import (
"time"
"github.com/AdguardTeam/golibs/testutil"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestChanPacketConn_Close(t *testing.T) {
sessions := make(chan *packetSession)
c := newChanPacketConn(sessions, testSubnetIPv4, nil, nil, testLAddr)
c := newTestChanPacketConn(t, sessions, nil)
err := c.Close()
assert.NoError(t, err)
@@ -24,14 +23,14 @@ func TestChanPacketConn_Close(t *testing.T) {
}
func TestChanPacketConn_LocalAddr(t *testing.T) {
c := newChanPacketConn(nil, testSubnetIPv4, nil, nil, testLAddr)
c := newTestChanPacketConn(t, nil, nil)
got := c.LocalAddr()
assert.Equal(t, testLAddr, got)
}
func TestChanPacketConn_ReadFromSession(t *testing.T) {
sessions := make(chan *packetSession, 1)
c := newChanPacketConn(sessions, testSubnetIPv4, nil, nil, testLAddr)
c := newTestChanPacketConn(t, sessions, nil)
body := []byte("hello")
bodyLen := len(body)
@@ -81,8 +80,7 @@ func TestChanPacketConn_WriteToSession(t *testing.T) {
sessions := make(chan *packetSession, 1)
writes := make(chan *packetConnWriteReq, 1)
gauge := prometheus.NewGauge(prometheus.GaugeOpts{})
c := newChanPacketConn(sessions, testSubnetIPv4, writes, gauge, testLAddr)
c := newTestChanPacketConn(t, sessions, writes)
body := []byte("hello")
bodyLen := len(body)
@@ -151,7 +149,7 @@ func checkWriteReqAndRespond(
}
func TestChanPacketConn_deadlines(t *testing.T) {
c := newChanPacketConn(nil, testSubnetIPv4, nil, nil, testLAddr)
c := newTestChanPacketConn(t, nil, nil)
deadline := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
testCases := []struct {

View File

@@ -10,29 +10,26 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/syncutil"
"github.com/prometheus/client_golang/prometheus"
)
// interfaceListener contains information about a single interface listener.
type interfaceListener struct {
logger *slog.Logger
conns *connIndex
listenConf *net.ListenConfig
bodyPool *syncutil.Pool[[]byte]
oobPool *syncutil.Pool[[]byte]
writeRequests chan *packetConnWriteReq
done chan unit
errColl errcoll.Interface
writeRequestsGauge prometheus.Gauge
writeDurationHist prometheus.Observer
ifaceName string
port uint16
logger *slog.Logger
conns *connIndex
listenConf *net.ListenConfig
bodyPool *syncutil.Pool[[]byte]
oobPool *syncutil.Pool[[]byte]
writeRequests chan *packetConnWriteReq
done chan unit
errColl errcoll.Interface
metrics Metrics
ifaceName string
port uint16
}
// listenTCP runs the TCP listening loop. It is intended to be used as a
@@ -80,14 +77,14 @@ func (l *interfaceListener) processConn(ctx context.Context, logger *slog.Logger
laddr := netutil.NetAddrToAddrPort(conn.LocalAddr())
raddr := conn.RemoteAddr()
if lsnr := l.conns.listener(laddr.Addr()); lsnr != nil {
if !lsnr.send(conn) {
if !lsnr.send(ctx, conn) {
optslog.Debug2(ctx, logger, "channel is closed", "raddr", raddr, "laddr", laddr)
}
return
}
metrics.BindToDeviceUnknownTCPRequestsTotal.Inc()
l.metrics.IncrementUnknownTCPRequests(ctx)
optslog.Debug2(ctx, logger, "no stream channel", "raddr", raddr, "laddr", laddr)
@@ -171,14 +168,14 @@ func (l *interfaceListener) readUDP(
laddr := sess.laddr.AddrPort().Addr()
chanPacketConn := l.conns.packetConn(laddr)
if chanPacketConn == nil {
metrics.BindToDeviceUnknownUDPRequestsTotal.Inc()
l.metrics.IncrementUnknownUDPRequests(ctx)
optslog.Debug2(ctx, logger, "no packet channel", "raddr", sess.raddr, "laddr", laddr)
return nil
}
if !chanPacketConn.send(sess) {
if !chanPacketConn.send(ctx, sess) {
optslog.Debug1(ctx, logger, "channel is closed", "laddr", laddr)
}
@@ -201,14 +198,14 @@ func (l *interfaceListener) writeUDPResponses(
return
case req := <-l.writeRequests:
l.writeUDP(c, req)
l.writeUDP(ctx, c, req)
}
}
}
// writeUDP handles a single write operation and writes a response to
// req.respCh.
func (l *interfaceListener) writeUDP(c *net.UDPConn, req *packetConnWriteReq) {
func (l *interfaceListener) writeUDP(ctx context.Context, c *net.UDPConn, req *packetConnWriteReq) {
resp := &packetConnWriteResp{}
resp.err = c.SetWriteDeadline(req.deadline)
if resp.err != nil {
@@ -217,7 +214,7 @@ func (l *interfaceListener) writeUDP(c *net.UDPConn, req *packetConnWriteReq) {
return
}
l.writeToUDPConn(c, req, resp)
l.writeToUDPConn(ctx, c, req, resp)
resetDeadlineErr := c.SetWriteDeadline(time.Time{})
resp.err = errors.WithDeferred(resp.err, resetDeadlineErr)
@@ -228,12 +225,15 @@ func (l *interfaceListener) writeUDP(c *net.UDPConn, req *packetConnWriteReq) {
// writeToUDPConn writes to c, depending on what kind of session req contains,
// and sets resp.written and resp.err accordingly.
func (l *interfaceListener) writeToUDPConn(
ctx context.Context,
c *net.UDPConn,
req *packetConnWriteReq,
resp *packetConnWriteResp,
) {
start := time.Now()
defer func() { l.writeDurationHist.Observe(time.Since(start).Seconds()) }()
defer func() {
l.metrics.ObserveUDPWriteDuration(ctx, l.ifaceName, time.Since(start))
}()
s := req.session
if s == nil {

View File

@@ -12,8 +12,8 @@ import (
)
func TestListenConfig(t *testing.T) {
pc := newChanPacketConn(nil, testSubnetIPv4, nil, nil, testLAddr)
lsnr := newChanListener(nil, testSubnetIPv4, testLAddr)
pc := newTestChanPacketConn(t, nil, nil)
lsnr := newTestChanListener(t, nil)
addr := &agdnet.PrefixNetAddr{
Prefix: testSubnetIPv4,
Net: "",

View File

@@ -20,6 +20,9 @@ type ManagerConfig struct {
// errors.
ErrColl errcoll.Interface
// Metrics collects bindtodevice-related statistics. It must not be nil.
Metrics Metrics
// ChannelBufferSize is the size of the buffers of the channels used to
// dispatch TCP connections and UDP sessions.
ChannelBufferSize int

View File

@@ -15,7 +15,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/service"
"github.com/AdguardTeam/golibs/syncutil"
@@ -29,6 +28,7 @@ type Manager struct {
closeOnce *sync.Once
ifaceListeners map[ID]*interfaceListener
errColl errcoll.Interface
metrics Metrics
done chan unit
chanBufSize int
}
@@ -41,6 +41,7 @@ func NewManager(c *ManagerConfig) (m *Manager) {
closeOnce: &sync.Once{},
ifaceListeners: map[ID]*interfaceListener{},
errColl: c.ErrColl,
metrics: c.Metrics,
done: make(chan unit),
chanBufSize: c.ChannelBufferSize,
}
@@ -106,18 +107,17 @@ func (m *Manager) newInterfaceListener(
port uint16,
) (l *interfaceListener) {
return &interfaceListener{
logger: m.logger.With("iface", ifaceName, "port", port),
conns: &connIndex{},
listenConf: newListenConfig(ifaceName, ctrlConf),
bodyPool: syncutil.NewSlicePool[byte](bodySize),
oobPool: syncutil.NewSlicePool[byte](netext.IPDstOOBSize),
writeRequests: make(chan *packetConnWriteReq, m.chanBufSize),
done: m.done,
errColl: m.errColl,
writeRequestsGauge: metrics.BindToDeviceUDPWriteRequestsChanSize.WithLabelValues(ifaceName),
writeDurationHist: metrics.BindToDeviceUDPWriteDurationSeconds.WithLabelValues(ifaceName),
ifaceName: ifaceName,
port: port,
logger: m.logger.With("iface", ifaceName, "port", port),
conns: &connIndex{},
listenConf: newListenConfig(ifaceName, ctrlConf),
bodyPool: syncutil.NewSlicePool[byte](bodySize),
oobPool: syncutil.NewSlicePool[byte](netext.IPDstOOBSize),
writeRequests: make(chan *packetConnWriteReq, m.chanBufSize),
done: m.done,
errColl: m.errColl,
metrics: m.metrics,
ifaceName: ifaceName,
port: port,
}
}
@@ -147,7 +147,7 @@ func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c *ListenConfig, err
}
lsnrCh := make(chan net.Conn, m.chanBufSize)
lsnr := newChanListener(lsnrCh, subnet, &agdnet.PrefixNetAddr{
lsnr := newChanListener(m.metrics, lsnrCh, subnet, &agdnet.PrefixNetAddr{
Prefix: subnet,
Net: "tcp",
Port: l.port,
@@ -160,10 +160,11 @@ func (m *Manager) ListenConfig(id ID, subnet netip.Prefix) (c *ListenConfig, err
sessCh := make(chan *packetSession, m.chanBufSize)
pConn := newChanPacketConn(
m.metrics,
sessCh,
subnet,
l.writeRequests,
l.writeRequestsGauge,
l.ifaceName,
&agdnet.PrefixNetAddr{
Prefix: subnet,
Net: "udp",

View File

@@ -3,15 +3,17 @@
package bindtodevice_test
import (
"cmp"
"net"
"net/netip"
"testing"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/servicetest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -47,18 +49,36 @@ func (iface *fakeInterface) Subnets() (subnets []netip.Prefix, err error) {
return iface.OnSubnets()
}
func TestManager_Add(t *testing.T) {
m := bindtodevice.NewManager(&bindtodevice.ManagerConfig{
Logger: slogutil.NewDiscardLogger(),
InterfaceStorage: &fakeInterfaceStorage{
// newTestManager is a helper for creating a [bindtodevice.Manager] for tests.
// c may be nil, and all zero-value fields in c are replaced with test defaults.
func newTestManager(tb testing.TB, c *bindtodevice.ManagerConfig) (m *bindtodevice.Manager) {
tb.Helper()
c = cmp.Or(c, &bindtodevice.ManagerConfig{})
c.Logger = cmp.Or(c.Logger, slogutil.NewDiscardLogger())
c.InterfaceStorage = cmp.Or[bindtodevice.InterfaceStorage](
c.InterfaceStorage,
&fakeInterfaceStorage{
OnInterfaceByName: func(_ string) (iface bindtodevice.NetInterface, err error) {
return nil, nil
},
},
ErrColl: agdtest.NewErrorCollector(),
ChannelBufferSize: 1,
})
require.NotNil(t, m)
)
c.ErrColl = cmp.Or[errcoll.Interface](c.ErrColl, agdtest.NewErrorCollector())
c.Metrics = cmp.Or[bindtodevice.Metrics](c.Metrics, bindtodevice.EmptyMetrics{})
c.ChannelBufferSize = cmp.Or(c.ChannelBufferSize, 1)
m = bindtodevice.NewManager(c)
require.NotNil(tb, m)
return m
}
func TestManager_Add(t *testing.T) {
m := newTestManager(t, nil)
// Don't use a table, since the results of these subtests depend on each
// other.
@@ -91,17 +111,13 @@ func TestManager_ListenConfig(t *testing.T) {
},
}
m := bindtodevice.NewManager(&bindtodevice.ManagerConfig{
Logger: slogutil.NewDiscardLogger(),
m := newTestManager(t, &bindtodevice.ManagerConfig{
InterfaceStorage: &fakeInterfaceStorage{
OnInterfaceByName: func(_ string) (iface bindtodevice.NetInterface, err error) {
return ifaceWithSubnet, nil
},
},
ErrColl: agdtest.NewErrorCollector(),
ChannelBufferSize: 1,
})
require.NotNil(t, m)
err := m.Add(testID1, testIfaceName, testPort1, nil)
require.NoError(t, err)
@@ -140,17 +156,13 @@ func TestManager_ListenConfig(t *testing.T) {
},
}
noSubnetMgr := bindtodevice.NewManager(&bindtodevice.ManagerConfig{
Logger: slogutil.NewDiscardLogger(),
noSubnetMgr := newTestManager(t, &bindtodevice.ManagerConfig{
InterfaceStorage: &fakeInterfaceStorage{
OnInterfaceByName: func(_ string) (iface bindtodevice.NetInterface, err error) {
return ifaceWithoutSubnet, nil
},
},
ErrColl: agdtest.NewErrorCollector(),
ChannelBufferSize: 1,
})
require.NotNil(t, noSubnetMgr)
subTestErr := noSubnetMgr.Add(testID1, testIfaceName, testPort1, nil)
require.NoError(t, subTestErr)
@@ -169,17 +181,13 @@ func TestManager_ListenConfig(t *testing.T) {
},
}
narrowSubnetMgr := bindtodevice.NewManager(&bindtodevice.ManagerConfig{
Logger: slogutil.NewDiscardLogger(),
narrowSubnetMgr := newTestManager(t, &bindtodevice.ManagerConfig{
InterfaceStorage: &fakeInterfaceStorage{
OnInterfaceByName: func(_ string) (iface bindtodevice.NetInterface, err error) {
return ifaceWithNarrowerSubnet, nil
},
},
ErrColl: agdtest.NewErrorCollector(),
ChannelBufferSize: 1,
})
require.NotNil(t, narrowSubnetMgr)
subTestErr := narrowSubnetMgr.Add(testID1, testIfaceName, testPort1, nil)
require.NoError(t, subTestErr)
@@ -202,13 +210,7 @@ func TestManager(t *testing.T) {
ifaceName := iface.Name
m := bindtodevice.NewManager(&bindtodevice.ManagerConfig{
Logger: slogutil.NewDiscardLogger(),
InterfaceStorage: bindtodevice.DefaultInterfaceStorage{},
ErrColl: agdtest.NewErrorCollector(),
ChannelBufferSize: 1,
})
require.NotNil(t, m)
m := newTestManager(t, nil)
// TODO(a.garipov): Add support for zero port.
err := m.Add(testID1, ifaceName, testPort1, nil)
@@ -226,11 +228,7 @@ func TestManager(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, lc)
err = m.Start(testutil.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, func() (err error) {
return m.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
})
servicetest.RequireRun(t, m, testTimeout)
t.Run("tcp", func(t *testing.T) {
bindtodevice.SubtestListenControlTCP(t, lc, ifaceName, ifaceNet)

View File

@@ -0,0 +1,62 @@
package bindtodevice
import (
"context"
"net/netip"
"time"
)
// Metrics is an interface for collecting bindtodevice-related statistics.
type Metrics interface {
// IncrementUnknownTCPRequests increments the counter for TCP requests to
// unknown local address.
IncrementUnknownTCPRequests(ctx context.Context)
// IncrementUnknownUDPRequests increments the counter for UDP requests to
// unknown local address.
IncrementUnknownUDPRequests(ctx context.Context)
// SetTCPConnsChanSize sets the current number of TCP connections in the
// channel by subnet.
SetTCPConnsChanSize(ctx context.Context, subnet netip.Prefix, n uint)
// SetUDPConnsChanSize sets the current number of UDP connections in the
// channel by subnet.
SetUDPSessionsChanSize(ctx context.Context, subnet netip.Prefix, n uint)
// SetUDPWriteRequestsChanSize sets the current number of UDP write requests
// in the channel by interface name.
SetUDPWriteRequestsChanSize(ctx context.Context, name string, n uint)
// ObserveUDPWriteDuration observes the duration of a UDP write operation by
// interface name.
ObserveUDPWriteDuration(ctx context.Context, name string, dur time.Duration)
}
// EmptyMetrics is the implementation of the [Metrics] interface that does
// nothing.
type EmptyMetrics struct{}
// type check
var _ Metrics = EmptyMetrics{}
// IncrementUnknownTCPRequests implements the [Metrics] interface for
// EmptyMetrics.
func (EmptyMetrics) IncrementUnknownTCPRequests(_ context.Context) {}
// IncrementUnknownUDPRequests implements the [Metrics] interface for
// EmptyMetrics.
func (EmptyMetrics) IncrementUnknownUDPRequests(_ context.Context) {}
// SetTCPConnsChanSize implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) SetTCPConnsChanSize(_ context.Context, _ netip.Prefix, _ uint) {}
// SetUDPSessionsChanSize implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) SetUDPSessionsChanSize(_ context.Context, _ netip.Prefix, _ uint) {}
// SetUDPWriteRequestsChanSize implements the [Metrics] interface for
// EmptyMetrics.
func (EmptyMetrics) SetUDPWriteRequestsChanSize(_ context.Context, _ string, _ uint) {}
// ObserveUDPWriteDuration implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) ObserveUDPWriteDuration(_ context.Context, _ string, _ time.Duration) {}

View File

@@ -16,7 +16,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
"github.com/AdguardTeam/AdGuardDNS/internal/agdrand"
"github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
@@ -46,6 +45,7 @@ import (
"github.com/AdguardTeam/golibs/contextutil"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/mathutil/randutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/osutil"
@@ -57,15 +57,16 @@ import (
// Constants that define debug identifiers for the debug HTTP service.
const (
debugIDAllowlist = "allowlist"
debugIDBillStat = "billstat"
debugIDGeoIP = "geoip"
debugIDProfileDB = "profiledb"
debugIDProfileDBFull = "profiledb_full"
debugIDRuleStat = "rulestat"
debugIDTicketRotator = "ticket_rotator"
debugIDTLSConfig = "tlsconfig"
debugIDWebSvc = "websvc"
debugIDAllowlist = "allowlist"
debugIDBillStat = "billstat"
debugIDCustomDomainDB = "custom_domain_db"
debugIDGeoIP = "geoip"
debugIDProfileDB = "profiledb"
debugIDProfileDBFull = "profiledb_full"
debugIDRuleStat = "rulestat"
debugIDTLSConfig = "tlsconfig"
debugIDTicketRotator = "ticket_rotator"
debugIDWebSvc = "websvc"
// debugIDPrefixPlugin is the prefix for plugin debug identifiers.
debugIDPrefixPlugin = "plugin/"
@@ -100,38 +101,41 @@ type builder struct {
// The fields below are initialized later by calling the builder's methods.
// Keep them sorted.
access *access.Global
adultBlocking *hashprefix.Filter
adultBlockingHashes *hashprefix.Storage
backendGRPCMtrc *metrics.BackendGRPC
billStat billstat.Recorder
bindSet netutil.SubnetSet
btdManager *bindtodevice.Manager
cloner *dnsmsg.Cloner
connLimit *connlimiter.Limiter
controlConf *netext.ControlConfig
customDomainDB profiledb.CustomDomainDB
dnsCheck dnscheck.Interface
dnsDB dnsdb.Interface
dnsSvc *dnssvc.Service
filterMtrc filter.Metrics
filterStorage *filterstorage.Default
filteringGroups map[agd.FilteringGroupID]*agd.FilteringGroup
fwdHandler *forward.Handler
geoIP *geoip.File
hashMatcher *hashprefix.Matcher
messages *dnsmsg.Constructor
newRegDomains *hashprefix.Filter
newRegDomainsHashes *hashprefix.Storage
profileDB profiledb.Interface
queryLog querylog.Interface
rateLimit *ratelimit.Backoff
ruleStat rulestat.Interface
safeBrowsing *hashprefix.Filter
safeBrowsingHashes *hashprefix.Storage
sdeConf *dnsmsg.StructuredDNSErrorsConfig
tlsManager *tlsconfig.DefaultManager
webSvc *websvc.Service
access *access.Global
adultBlocking *hashprefix.Filter
adultBlockingHashes *hashprefix.Storage
backendGRPCMtrc backendpb.GRPCMetrics
billStat billstat.Recorder
bindSet netutil.SubnetSet
btdManager *bindtodevice.Manager
cloner *dnsmsg.Cloner
connLimit *connlimiter.Limiter
controlConf *netext.ControlConfig
customDomainDB *tlsconfig.CustomDomainDB
dnsCheck dnscheck.Interface
dnsDB dnsdb.Interface
dnsSvc *dnssvc.Service
dnsSvcCustomDomainDB dnssvc.CustomDomainDB
filterMtrc filter.Metrics
filterStorage *filterstorage.Default
filteringGroups map[agd.FilteringGroupID]*agd.FilteringGroup
fwdHandler *forward.Handler
geoIP *geoip.File
hashMatcher *hashprefix.Matcher
messages *dnsmsg.Constructor
newRegDomains *hashprefix.Filter
newRegDomainsHashes *hashprefix.Storage
profDBCustomDomainDB profiledb.CustomDomainDB
profileDB profiledb.Interface
queryLog querylog.Interface
rateLimit *ratelimit.Backoff
ruleStat rulestat.Interface
safeBrowsing *hashprefix.Filter
safeBrowsingHashes *hashprefix.Storage
sdeConf *dnsmsg.StructuredDNSErrorsConfig
tlsManager *tlsconfig.DefaultManager
webSvc *websvc.Service
webSvcCertValidator websvc.CertificateValidator
// The fields below are initialized later, just like with the fields above,
// but are placed in this order for alignment optimization.
@@ -375,12 +379,15 @@ func (b *builder) initAdultBlocking(
Schedule: timeutil.NewConstSchedule(refrIvl),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
// TODO(a.garipov, e.burkov): Consider using different context for child
// routines.
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting refresher: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
matchers[filter.AdultBlockingTXTSuffix] = b.adultBlockingHashes
@@ -474,12 +481,12 @@ func (b *builder) initNewRegDomains(
Schedule: timeutil.NewConstSchedule(refrIvl),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting refresher: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
b.debugRefrs[prefix] = b.newRegDomains
@@ -560,12 +567,12 @@ func (b *builder) initSafeBrowsing(
Schedule: timeutil.NewConstSchedule(refrIvl),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting refresher: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
matchers[filter.GeneralTXTSuffix] = b.safeBrowsingHashes
@@ -659,12 +666,12 @@ func (b *builder) initFilterStorage(ctx context.Context) (err error) {
Schedule: timeutil.NewConstSchedule(refrIvl),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting default filter storage update: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
b.debugRefrs[filter.StoragePrefix] = b.filterStorage
@@ -691,7 +698,7 @@ func (b *builder) newSafeSearchConfig(
ID: id,
// TODO(a.garipov): Consider adding a separate parameter here.
MaxSize: fltConf.MaxSize,
// TODO(a.garipov): Consider making this configurable.
// TODO(a.garipov): Consider making configurable.
ResultCacheTTL: 1 * time.Hour,
// TODO(a.garipov): Consider adding a separate parameter here.
RefreshTimeout: time.Duration(fltConf.RefreshTimeout),
@@ -736,9 +743,14 @@ func (b *builder) initAccess(ctx context.Context) (err error) {
func (b *builder) initBindToDevice(ctx context.Context) (err error) {
c := b.conf
mtrc, err := metrics.NewBindToDevice(b.mtrcNamespace, b.promRegisterer)
if err != nil {
return fmt.Errorf("registering bindtodevice metrics: %w", err)
}
var btdCtrlConf *bindtodevice.ControlConfig
btdCtrlConf, b.controlConf = c.Network.toInternal()
b.btdManager, err = c.InterfaceListeners.toInternal(b.baseLogger, b.errColl, btdCtrlConf)
b.btdManager, err = c.InterfaceListeners.toInternal(b.baseLogger, b.errColl, mtrc, btdCtrlConf)
if err != nil {
return fmt.Errorf("converting interface listeners: %w", err)
}
@@ -793,7 +805,7 @@ func (b *builder) initQueryLog(ctx context.Context) (err error) {
Logger: b.baseLogger.With(slogutil.KeyPrefix, "querylog"),
Path: b.env.QueryLogPath,
Metrics: mtrc,
RandSeed: agdrand.MustNewSeed(),
RandSeed: randutil.MustNewSeed(),
})
b.logger.DebugContext(ctx, "initialized file-based query log")
@@ -854,7 +866,7 @@ func (b *builder) initMsgConstructor(ctx context.Context) (err error) {
//
// [builder.initGRPCMetrics] must be called before this method.
func (b *builder) initTLSManager(ctx context.Context) (err error) {
mtrc, err := metrics.NewTLSConfig(b.mtrcNamespace, b.promRegisterer)
mtrc, err := metrics.NewTLSConfigManager(b.mtrcNamespace, b.promRegisterer)
if err != nil {
return fmt.Errorf("registering tls metrics: %w", err)
}
@@ -941,25 +953,65 @@ func (b *builder) newTicketDB(ctx context.Context) (db tlsconfig.TicketDB, err e
// initCustomDomainDB initializes the database for the custom domains.
//
// [builder.initTLSManager] must be called before this method.
func (b *builder) initCustomDomainDB(ctx context.Context) {
if !b.profilesEnabled {
b.customDomainDB = profiledb.EmptyCustomDomainDB{}
func (b *builder) initCustomDomainDB(ctx context.Context) (err error) {
if !bool(b.env.CustomDomainsEnabled) || !b.profilesEnabled {
b.logger.WarnContext(ctx, "custom domains are disabled")
return
b.dnsSvcCustomDomainDB = dnssvc.EmptyCustomDomainDB{}
b.profDBCustomDomainDB = profiledb.EmptyCustomDomainDB{}
b.webSvcCertValidator = websvc.RejectCertificateValidator{}
return nil
}
b.customDomainDB = tlsconfig.NewCustomDomainDB(&tlsconfig.CustomDomainDBConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "custom_domain_db"),
Clock: timeutil.SystemClock{},
ErrColl: b.errColl,
// TODO(a.garipov): Init and set the gRPC storage.
Storage: tlsconfig.EmptyCustomDomainStorage{},
// TODO(a.garipov): Set the TLS manager.
strgMtrc, err := metrics.NewBackendCustomDomainStorage(b.mtrcNamespace, b.promRegisterer)
if err != nil {
return fmt.Errorf("registering custom domain storage metrics: %w", err)
}
strg, err := backendpb.NewCustomDomainStorage(&backendpb.CustomDomainStorageConfig{
Endpoint: &b.env.CustomDomainsURL.URL,
Logger: b.baseLogger.With(slogutil.KeyPrefix, "custom_domain_storage"),
Clock: timeutil.SystemClock{},
GRPCMetrics: b.backendGRPCMtrc,
Metrics: strgMtrc,
APIKey: b.env.CustomDomainsAPIKey,
})
if err != nil {
return fmt.Errorf("custom domain storage: %w", err)
}
// TODO(a.garipov): Add a refresher and refresh initially.
mtrc, err := metrics.NewCustomDomainDB(b.mtrcNamespace, b.promRegisterer)
if err != nil {
return fmt.Errorf("registering custom domain database metrics: %w", err)
}
b.logger.DebugContext(ctx, "initialized custom domain db")
b.customDomainDB, err = tlsconfig.NewCustomDomainDB(&tlsconfig.CustomDomainDBConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "custom_domain_db"),
Clock: timeutil.SystemClock{},
ErrColl: b.errColl,
Manager: b.tlsManager,
Metrics: mtrc,
Storage: strg,
CacheDirPath: b.env.CustomDomainsCachePath,
InitialRetryIvl: time.Duration(b.env.CustomDomainsRefreshIvl),
// TODO(a.garipov): Consider making configurable.
MaxRetryIvl: 1 * timeutil.Day,
})
if err != nil {
return fmt.Errorf("custom domain db: %w", err)
}
b.dnsSvcCustomDomainDB = b.customDomainDB
b.profDBCustomDomainDB = b.customDomainDB
b.webSvcCertValidator = b.customDomainDB
// NOTE: The initial refresh and thus full initialization is done in
// [builder.refreshCustomDomainDB].
b.logger.DebugContext(ctx, "prepared custom domain db")
return nil
}
// initServerGroups initializes the server groups.
@@ -1010,12 +1062,12 @@ func (b *builder) initTicketRotator(ctx context.Context) (err error) {
Schedule: timeutil.NewConstSchedule(time.Duration(b.env.SessionTicketRefreshIvl)),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting ticket rotator refresh: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
b.debugRefrs[debugIDTicketRotator] = tickRot
@@ -1068,12 +1120,12 @@ func (b *builder) setServerGroupProperties(ctx context.Context) {
func (b *builder) startBindToDevice(ctx context.Context) (err error) {
// Start the bind-to-device manager here, now that no further calls to
// b.btdManager.ListenConfig are required.
err = b.btdManager.Start(ctx)
err = b.btdManager.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting bindtodevice manager: %w", err)
}
b.sigHdlr.Add(b.btdManager)
b.sigHdlr.AddService(b.btdManager)
b.logger.DebugContext(ctx, "started bindtodevice manager")
@@ -1086,12 +1138,18 @@ const defaultTimeout = 30 * time.Second
// initGRPCMetrics initializes the gRPC metrics if necessary.
func (b *builder) initGRPCMetrics(ctx context.Context) (err error) {
b.backendGRPCMtrc = b.plugins.GRPCMetrics()
switch {
case b.backendGRPCMtrc != nil:
b.logger.DebugContext(ctx, "initialized grpc metrics from plugin")
return nil
case
b.profilesEnabled,
b.env.SessionTicketType == sessionTicketRemote,
b.conf.Check.KV.Type == kvModeBackend,
b.conf.RateLimit.Allowlist.Type == rlAllowlistTypeBackend:
b.env.DNSCheckKVType == kvModeBackend,
b.env.RateLimitAllowlistType == rlAllowlistTypeBackend:
// Go on.
default:
// Don't initialize the metrics if no protobuf backend is used.
@@ -1147,12 +1205,12 @@ func (b *builder) initBillStat(ctx context.Context) (err error) {
Schedule: timeutil.NewConstSchedule(refrIvl),
RefreshOnShutdown: true,
})
err = refr.Start(ctx)
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting billstat recorder refresher: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
b.debugRefrs[debugIDBillStat] = billStat
@@ -1179,11 +1237,13 @@ func (b *builder) newBillStatUploader() (s billstat.Uploader, err error) {
})
}
// initProfileDB initializes the profile database if necessary.
//
// [builder.initGRPCMetrics] must be called before this method. It also adds
// initProfileDB initializes the profile database if necessary. It also adds
// the refreshers with ID [debugIDProfileDB], [debugIDProfileDBFull] to the
// debug refreshers.
//
// The following methods must be called before this one:
// - [builder.initCustomDomainDB]
// - [builder.initGRPCMetrics]
func (b *builder) initProfileDB(ctx context.Context) (err error) {
if !b.profilesEnabled {
b.profileDB = &profiledb.Disabled{}
@@ -1202,6 +1262,8 @@ func (b *builder) initProfileDB(ctx context.Context) (err error) {
return fmt.Errorf("registering profile access engine metrics: %w", err)
}
profAccessCons := access.NewProfileConstructor(profileMtrc)
backendProfileDBMtrc, err := metrics.NewBackendProfileDB(b.mtrcNamespace, b.promRegisterer)
if err != nil {
return fmt.Errorf("registering backend grpc profile metrics: %w", err)
@@ -1210,17 +1272,17 @@ func (b *builder) initProfileDB(ctx context.Context) (err error) {
respSzEst := b.conf.RateLimit.ResponseSizeEstimate
customLogger := b.baseLogger.With(slogutil.KeyPrefix, "filters/"+string(filter.IDCustom))
strg, err := backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{
BindSet: b.bindSet,
ErrColl: b.errColl,
Logger: b.baseLogger.With(slogutil.KeyPrefix, "profilestorage"),
BaseCustomLogger: customLogger,
ProfileMetrics: profileMtrc,
GRPCMetrics: b.backendGRPCMtrc,
Metrics: backendProfileDBMtrc,
Endpoint: apiURL,
APIKey: b.env.ProfilesAPIKey,
ResponseSizeEstimate: respSzEst,
MaxProfilesSize: b.env.ProfilesMaxRespSize,
Logger: b.baseLogger.With(slogutil.KeyPrefix, "profilestorage"),
BaseCustomLogger: customLogger,
Endpoint: apiURL,
ProfileAccessConstructor: profAccessCons,
BindSet: b.bindSet,
ErrColl: b.errColl,
GRPCMetrics: b.backendGRPCMtrc,
Metrics: backendProfileDBMtrc,
APIKey: b.env.ProfilesAPIKey,
ResponseSizeEstimate: respSzEst,
MaxProfilesSize: b.env.ProfilesMaxRespSize,
})
if err != nil {
return fmt.Errorf("creating profile storage: %w", err)
@@ -1234,18 +1296,19 @@ func (b *builder) initProfileDB(ctx context.Context) (err error) {
c := b.conf.Backend
timeout := time.Duration(c.Timeout)
profDB, err := profiledb.New(&profiledb.Config{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "profiledb"),
BaseCustomLogger: customLogger,
Clock: timeutil.SystemClock{},
CustomDomainDB: b.customDomainDB,
ErrColl: b.errColl,
ProfileMetrics: profileMtrc,
Metrics: profDBMtrc,
Storage: strg,
CacheFilePath: b.env.ProfilesCachePath,
FullSyncIvl: time.Duration(c.FullRefreshIvl),
FullSyncRetryIvl: time.Duration(c.FullRefreshRetryIvl),
ResponseSizeEstimate: respSzEst,
Logger: b.baseLogger.With(slogutil.KeyPrefix, "profiledb"),
BaseCustomLogger: customLogger,
ProfileAccessConstructor: profAccessCons,
Clock: timeutil.SystemClock{},
CustomDomainDB: b.profDBCustomDomainDB,
ErrColl: b.errColl,
ProfileMetrics: profileMtrc,
Metrics: profDBMtrc,
Storage: strg,
CacheFilePath: b.env.ProfilesCachePath,
FullSyncIvl: time.Duration(c.FullRefreshIvl),
FullSyncRetryIvl: time.Duration(c.FullRefreshRetryIvl),
ResponseSizeEstimate: respSzEst,
})
if err != nil {
return fmt.Errorf("creating default profile database: %w", err)
@@ -1274,12 +1337,12 @@ func (b *builder) initProfileDB(ctx context.Context) (err error) {
Schedule: sched,
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting default profile database refresher: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
b.debugRefrs[debugIDProfileDB] = profDB
@@ -1291,6 +1354,41 @@ func (b *builder) initProfileDB(ctx context.Context) (err error) {
return nil
}
// refreshCustomDomainDB performs the initial refresh of the custom-domain
// database.
//
// [builder.initProfileDB] must be called before this method.
func (b *builder) refreshCustomDomainDB(ctx context.Context) (err error) {
if !bool(b.env.CustomDomainsEnabled) || !b.profilesEnabled {
return nil
}
err = b.customDomainDB.Refresh(ctx)
if err != nil {
return fmt.Errorf("custom domain db: initial refresh: %w", err)
}
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
// TODO(a.garipov): Consider making configurable.
ContextConstructor: contextutil.NewTimeoutConstructor(defaultTimeout),
Refresher: b.customDomainDB,
Schedule: timeutil.NewConstSchedule(time.Duration(b.env.CustomDomainsRefreshIvl)),
})
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting custom domain db refresher: %w", err)
}
b.sigHdlr.AddService(refr)
b.debugRefrs[debugIDCustomDomainDB] = b.customDomainDB
b.logger.DebugContext(ctx, "initialized custom domain db")
return nil
}
// initDNSCheck initializes the DNS checker.
//
// [builder.initGRPCMetrics] and [builder.initMsgConstructor] must be called
@@ -1306,6 +1404,7 @@ func (b *builder) initDNSCheck(ctx context.Context) (err error) {
c := b.conf.Check
checkConf, err := c.toInternal(
ctx,
b.baseLogger,
b.env,
b.messages,
@@ -1365,12 +1464,12 @@ func (b *builder) initRuleStat(ctx context.Context) (err error) {
Schedule: timeutil.NewConstSchedule(10 * time.Minute),
RefreshOnShutdown: true,
})
err = refr.Start(ctx)
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting rulestat refresher: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
b.debugRefrs[debugIDRuleStat] = ruleStat
@@ -1390,7 +1489,7 @@ func (b *builder) initRateLimiter(ctx context.Context) (err error) {
allowSubnets := netutil.UnembedPrefixes(c.Allowlist.List)
allowlist := ratelimit.NewDynamicAllowlist(allowSubnets, nil)
typ := b.conf.RateLimit.Allowlist.Type
typ := b.env.RateLimitAllowlistType
mtrc, err := metrics.NewAllowlist(b.mtrcNamespace, b.promRegisterer, typ)
if err != nil {
return fmt.Errorf("ratelimit metrics: %w", err)
@@ -1434,12 +1533,12 @@ func (b *builder) initRateLimiter(ctx context.Context) (err error) {
Schedule: timeutil.NewConstSchedule(time.Duration(c.Allowlist.RefreshIvl)),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting allowlist refresher: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
err = b.initConnLimit(ctx, c.ConnectionLimit)
if err != nil {
@@ -1472,8 +1571,11 @@ func (b *builder) initConnLimit(ctx context.Context, conf *connLimitConfig) (err
}
// initWeb initializes the web service, starts it, and registers it in the
// signal handler. [builder.initDNSCheck] and [builder.initProfileDB] must be
// called before this method.
// signal handler.
//
// The following methods must be called before this one:
// - [builder.initDNSCheck]
// - [builder.initProfileDB]
func (b *builder) initWeb(ctx context.Context) (err error) {
webSvcMtrc, err := metrics.NewWebSvc(b.mtrcNamespace, b.promRegisterer)
if err != nil {
@@ -1494,14 +1596,7 @@ func (b *builder) initWeb(ctx context.Context) (err error) {
return fmt.Errorf("converting web configuration: %w", err)
}
if b.profilesEnabled {
// If profiles are enabled, the custom-domain database must also
// implement [websvc.CertificateValidator], as these are parts of the
// same feature.
webConf.CertificateValidator = b.customDomainDB.(websvc.CertificateValidator)
} else {
webConf.CertificateValidator = websvc.RejectCertificateValidator{}
}
webConf.CertificateValidator = b.webSvcCertValidator
b.webSvc = websvc.New(webConf)
@@ -1518,18 +1613,18 @@ func (b *builder) initWeb(ctx context.Context) (err error) {
Schedule: timeutil.NewConstSchedule(5 * time.Minute),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting websvc refresher: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
// The web service is considered critical, so its Start method panics
// instead of returning an error.
_ = b.webSvc.Start(ctx)
_ = b.webSvc.Start(context.WithoutCancel(ctx))
b.sigHdlr.Add(b.webSvc)
b.sigHdlr.AddService(b.webSvc)
b.debugRefrs[debugIDWebSvc] = b.webSvc
@@ -1559,12 +1654,12 @@ func (b *builder) waitGeoIP(ctx context.Context) (err error) {
Schedule: timeutil.NewConstSchedule(time.Duration(b.conf.GeoIP.RefreshIvl)),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
err = refr.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("starting geoip refresher: %w", err)
}
b.sigHdlr.Add(refr)
b.sigHdlr.AddService(refr)
b.debugRefrs[debugIDGeoIP] = b.geoIP
@@ -1577,6 +1672,7 @@ func (b *builder) waitGeoIP(ctx context.Context) (err error) {
// - [builder.initAccess]
// - [builder.initBillStat]
// - [builder.initBindToDevice]
// - [builder.initCustomDomainDB]
// - [builder.initDNSDB]
// - [builder.initFilterStorage]
// - [builder.initFilteringGroups]
@@ -1601,32 +1697,35 @@ func (b *builder) initDNS(ctx context.Context) (err error) {
b.fwdHandler = forward.NewHandler(b.conf.Upstream.toInternal(b.baseLogger, mtrcListener))
dnsHdlrsConf := &dnssvc.HandlersConfig{
BaseLogger: b.baseLogger,
Cache: b.conf.Cache.toInternal(),
Cloner: b.cloner,
HumanIDParser: agd.NewHumanIDParser(),
Messages: b.messages,
PluginRegistry: b.plugins,
StructuredErrors: b.sdeConf,
AccessManager: b.access,
BillStat: b.billStat,
CacheManager: b.cacheManager,
DNSCheck: b.dnsCheck,
DNSDB: b.dnsDB,
ErrColl: b.errColl,
FilterStorage: b.filterStorage,
GeoIP: b.geoIP,
Handler: b.fwdHandler,
HashMatcher: b.hashMatcher,
ProfileDB: b.profileDB,
PrometheusRegisterer: b.promRegisterer,
QueryLog: b.queryLog,
RateLimit: b.rateLimit,
RuleStat: b.ruleStat,
MetricsNamespace: b.mtrcNamespace,
FilteringGroups: b.filteringGroups,
ServerGroups: b.serverGroups,
EDEEnabled: b.conf.Filters.EDEEnabled,
BaseLogger: b.baseLogger,
Cache: b.conf.Cache.toInternal(),
Cloner: b.cloner,
HumanIDParser: agd.NewHumanIDParser(),
MainMiddlewareMetrics: b.plugins.MainMiddlewareMetrics(),
Messages: b.messages,
PostInitialMiddleware: b.plugins.PostInitialMiddleware(),
StructuredErrors: b.sdeConf,
AccessManager: b.access,
BillStat: b.billStat,
CacheManager: b.cacheManager,
CustomDomainDB: b.dnsSvcCustomDomainDB,
DNSCheck: b.dnsCheck,
DNSDB: b.dnsDB,
ErrColl: b.errColl,
FilterStorage: b.filterStorage,
GeoIP: b.geoIP,
Handler: b.fwdHandler,
HashMatcher: b.hashMatcher,
ProfileDB: b.profileDB,
PrometheusRegisterer: b.promRegisterer,
QueryLog: b.queryLog,
RateLimit: b.rateLimit,
RuleStat: b.ruleStat,
MetricsNamespace: b.mtrcNamespace,
NodeName: b.env.NodeName,
FilteringGroups: b.filteringGroups,
ServerGroups: b.serverGroups,
EDEEnabled: b.conf.Filters.EDEEnabled,
}
dnsHdlrs, err := dnssvc.NewHandlers(ctx, dnsHdlrsConf)
@@ -1679,12 +1778,12 @@ func (b *builder) performConnCheck(ctx context.Context) (err error) {
// [builder.initDNS] must be called before this method.
func (b *builder) initHealthCheck(ctx context.Context) (err error) {
upd := newUpstreamHealthcheck(b.baseLogger, b.fwdHandler, b.conf.Upstream, b.errColl)
err = upd.Start(ctx)
err = upd.Start(context.WithoutCancel(ctx))
if err != nil {
return fmt.Errorf("initializing healthcheck: %w", err)
}
b.sigHdlr.Add(upd)
b.sigHdlr.AddService(upd)
b.logger.DebugContext(ctx, "initialized healthcheck")
@@ -1704,14 +1803,14 @@ func (b *builder) initPluginRefreshers() {
func (b *builder) initPluginServices(ctx context.Context) (err error) {
var errs []error
for id, svc := range b.plugins.Services() {
err = svc.Start(ctx)
err = svc.Start(context.WithoutCancel(ctx))
if err != nil {
errs = append(errs, fmt.Errorf("starting plugin service %q: %w", id, err))
continue
}
b.sigHdlr.Add(svc)
b.sigHdlr.AddService(svc)
}
return errors.Join(errs...)
@@ -1725,9 +1824,9 @@ func (b *builder) initPluginServices(ctx context.Context) (err error) {
func (b *builder) mustStartDNS(ctx context.Context) {
// The DNS service is considered critical, so its Start method panics
// instead of returning an error.
_ = b.dnsSvc.Start(ctx)
_ = b.dnsSvc.Start(context.WithoutCancel(ctx))
b.sigHdlr.Add(b.dnsSvc)
b.sigHdlr.AddService(b.dnsSvc)
b.logger.DebugContext(ctx, "started dns")
}
@@ -1754,9 +1853,9 @@ func (b *builder) mustInitDebugSvc(ctx context.Context) {
// The debug HTTP service is considered critical, so its Start method panics
// instead of returning an error.
_ = debugSvc.Start(ctx)
_ = debugSvc.Start(context.WithoutCancel(ctx))
b.sigHdlr.Add(debugSvc)
b.sigHdlr.AddService(debugSvc)
b.logger.DebugContext(
ctx,

View File

@@ -1,6 +1,7 @@
package cmd
import (
"context"
"fmt"
"log/slog"
"net/netip"
@@ -30,18 +31,12 @@ import (
// checkConfig is the DNS server checking configuration.
type checkConfig struct {
// KV is remote key-value store configuration for DNS server checking.
KV *remoteKVConfig `yaml:"kv"`
// Domains are the domain names used for DNS server checking.
Domains []string `yaml:"domains"`
// NodeLocation is the location of this server node.
NodeLocation string `yaml:"node_location"`
// NodeName is the name of this server node.
NodeName string `yaml:"node_name"`
// IPv4 is the list of IPv4 addresses to respond with for A queries to
// subdomains of Domain.
IPv4 []netip.Addr `yaml:"ipv4"`
@@ -54,6 +49,7 @@ type checkConfig struct {
// toInternal converts c to the DNS server check configuration for the DNS
// server. c must be valid.
func (c *checkConfig) toInternal(
ctx context.Context,
baseLogger *slog.Logger,
envs *environment,
messages *dnsmsg.Constructor,
@@ -67,7 +63,7 @@ func (c *checkConfig) toInternal(
return nil, fmt.Errorf("dnscheck metrics: %w", err)
}
kv, err := c.KV.newRemoteKV(envs, namespace, reg, grpcMtrc, baseLogger)
kv, err := newRemoteKV(ctx, envs, namespace, reg, grpcMtrc, baseLogger)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return nil, err
@@ -86,7 +82,7 @@ func (c *checkConfig) toInternal(
ErrColl: errColl,
Domains: domains,
NodeLocation: c.NodeLocation,
NodeName: c.NodeName,
NodeName: envs.NodeName,
IPv4: c.IPv4,
IPv6: c.IPv6,
}, nil
@@ -99,18 +95,22 @@ const maxRespSize = 1 * datasize.MB
// [remotekv.KeyNamespace].
const keyNamespaceCheck = "check"
// newRemoteKV returns a new properly initialized remote key-value storage. c
// must be valid. grpcMtrc should be registered before calling this method.
func (c *remoteKVConfig) newRemoteKV(
// newRemoteKV returns a new properly initialized remote key-value storage.
// grpcMtrc should be registered before calling this method.
func newRemoteKV(
ctx context.Context,
envs *environment,
namespace string,
reg prometheus.Registerer,
grpcMtrc backendpb.GRPCMetrics,
baseLogger *slog.Logger,
) (kv remotekv.Interface, err error) {
switch c.Type {
switch envs.DNSCheckKVType {
case kvModeBackend:
return newBackendRemoteKV(envs, namespace, reg, grpcMtrc, c.TTL)
kv, err = newBackendRemoteKV(envs, namespace, reg, grpcMtrc, envs.DNSCheckKVTTL)
if err != nil {
return nil, fmt.Errorf("initializing backend dnscheck kv: %w", err)
}
case kvModeCache:
// TODO(e.burkov): The local cache in [dnscheck.RemoteKV] becomes
// pointless with this mode.
@@ -120,19 +120,26 @@ func (c *remoteKVConfig) newRemoteKV(
}),
}), nil
case kvModeRedis:
return newRedisRemoteKV(envs, namespace, reg, baseLogger, c.TTL)
kv, err = newRedisRemoteKV(ctx, namespace, reg, baseLogger, envs.DNSCheckKVTTL)
if err != nil {
return nil, fmt.Errorf("initializing redis dnscheck kv: %w", err)
}
case kvModeConsul:
kv, err = newConsulRemoteKV(envs, c.TTL)
kv, err = newConsulRemoteKV(envs, envs.DNSCheckKVTTL)
if err != nil {
return nil, fmt.Errorf("initializing consul dnscheck kv: %w", err)
}
default:
panic(fmt.Errorf("dnscheck kv type: %w: %q", errors.ErrBadEnumValue, c.Type))
panic(fmt.Errorf(
"env DNSCHECK_KV_TYPE: %w: %q",
errors.ErrBadEnumValue,
envs.DNSCheckKVType,
))
}
return remotekv.NewKeyNamespace(&remotekv.KeyNamespaceConfig{
KV: kv,
Prefix: newRemoteKVPrefix(envs, c.Type),
Prefix: newRemoteKVPrefix(envs, envs.DNSCheckKVType),
}), nil
}
@@ -170,7 +177,7 @@ func newBackendRemoteKV(
// newRedisRemoteKV returns a new properly initialized Redis-based remote
// key-value storage.
func newRedisRemoteKV(
envs *environment,
ctx context.Context,
namespace string,
reg prometheus.Registerer,
baseLogger *slog.Logger,
@@ -181,36 +188,7 @@ func newRedisRemoteKV(
return nil, fmt.Errorf("registering redis kv metrics: %w", err)
}
var dialer *redisutil.DefaultDialer
dialer, err = redisutil.NewDefaultDialer(&redisutil.DefaultDialerConfig{
Addr: &netutil.HostPort{
Host: envs.RedisAddr,
Port: envs.RedisPort,
},
})
if err != nil {
return nil, fmt.Errorf("initializing redisutil dialer: %w", err)
}
var connTester *redisutil.RoleChecker
connTester, err = redisutil.NewRoleChecker(&redisutil.RoleCheckerConfig{
Logger: baseLogger.With(slogutil.KeyPrefix, "redis_role_checker"),
})
if err != nil {
return nil, fmt.Errorf("initializing redisutil role checker: %w", err)
}
var pool *redisutil.DefaultPool
pool, err = redisutil.NewDefaultPool(&redisutil.DefaultPoolConfig{
Logger: baseLogger.With(slogutil.KeyPrefix, "redis_pool"),
ConnectionTester: connTester,
Dialer: dialer,
Metrics: mtrc,
IdleTimeout: time.Duration(envs.RedisIdleTimeout),
MaxActive: envs.RedisMaxActive,
MaxIdle: envs.RedisMaxIdle,
Wait: true,
})
pool, err := redisutil.NewPoolFromEnvironment(ctx, baseLogger, mtrc)
if err != nil {
return nil, fmt.Errorf("initializing redisutil pool: %w", err)
}
@@ -224,8 +202,11 @@ func newRedisRemoteKV(
}
// newConsulRemoteKV returns a new properly initialized Consul-based remote
// key-value storage.
func newConsulRemoteKV(envs *environment, ttl timeutil.Duration) (kv remotekv.Interface, err error) {
// key-value storage. envs must be valid.
func newConsulRemoteKV(
envs *environment,
ttl timeutil.Duration,
) (kv remotekv.Interface, err error) {
consulKVURL := envs.ConsulDNSCheckKVURL
consulSessionURL := envs.ConsulDNSCheckSessionURL
if consulKVURL == nil || consulSessionURL == nil {
@@ -274,7 +255,6 @@ func (c *checkConfig) Validate() (err error) {
errs := []error{
validate.NotEmpty("node_location", c.NodeLocation),
validate.NotEmpty("node_name", c.NodeName),
validate.NotEmptySlice("domains", c.Domains),
}
@@ -290,8 +270,6 @@ func (c *checkConfig) Validate() (err error) {
errs = append(errs, err)
}
errs = validate.Append(errs, "kv", c.KV)
return errors.Join(errs...)
}
@@ -332,39 +310,3 @@ const (
kvModeConsul = "consul"
kvModeRedis = "redis"
)
// remoteKVConfig is remote key-value store configuration for DNS server
// checking.
type remoteKVConfig struct {
// Type defines the type of remote key-value store. Allowed values are
// [kvModeBackend], [kvModeCache], [kvModeConsul] and [kvModeRedis].
Type string `yaml:"type"`
// TTL defines, for how long to keep the information about a single client.
TTL timeutil.Duration `yaml:"ttl"`
}
// type check
var _ validate.Interface = (*remoteKVConfig)(nil)
// Validate implements the [validate.Interface] interface for *remoteKVConfig.
func (c *remoteKVConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
ttl := time.Duration(c.TTL)
switch c.Type {
case kvModeBackend:
return validate.Positive("ttl", ttl)
case kvModeCache:
return nil
case kvModeConsul:
return validate.InRange("ttl", ttl, consulkv.MinTTL, consulkv.MaxTTL)
case kvModeRedis:
return validate.NoLessThan("ttl", ttl, rediskv.MinTTL)
default:
return fmt.Errorf("type: %w: %q", errors.ErrBadEnumValue, c.Type)
}
}

View File

@@ -5,6 +5,7 @@ package cmd
import (
"context"
"os"
"os/signal"
"runtime"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
@@ -15,12 +16,13 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/sentryutil"
"golang.org/x/sys/unix"
)
// Main is the entry point of application.
func Main(plugins *plugin.Registry) {
// TODO(a.garipov, e.burkov): Consider adding timeouts for initialization.
ctx := context.Background()
ctx, stop := signal.NotifyContext(context.Background(), unix.SIGINT, unix.SIGTERM)
envs := errors.Must(parseEnvironment())
errors.Check(envs.Validate())
@@ -35,8 +37,6 @@ func Main(plugins *plugin.Registry) {
sentryutil.SetDefaultLogger(baseLogger, "")
experiment.Init(baseLogger)
// TODO(a.garipov): Consider ways of replacing a prefix and stop passing
// the main logger everywhere.
mainLogger := baseLogger.With(slogutil.KeyPrefix, "main")
@@ -71,9 +71,7 @@ func Main(plugins *plugin.Registry) {
profilesEnabled := c.isProfilesEnabled()
errors.Check(envs.validateFromValidConfig(c, profilesEnabled))
metrics.SetAdditionalInfo(c.AdditionalMetricsInfo)
errors.Check(envs.validateProfilesConf(profilesEnabled))
// Building and running the server
@@ -86,6 +84,10 @@ func Main(plugins *plugin.Registry) {
profilesEnabled: profilesEnabled,
})
errors.Check(experiment.Init(baseLogger, b.promRegisterer))
errors.Check(metrics.SetAdditionalInfo(b.promRegisterer, c.AdditionalMetricsInfo))
b.startGeoIP(ctx)
errors.Check(os.MkdirAll(envs.FilterCachePath, agd.DefaultDirPerm))
@@ -112,9 +114,7 @@ func Main(plugins *plugin.Registry) {
errors.Check(b.initTLSManager(ctx))
// TODO(a.garipov): Check the errors when the methods starts returning
// them.
b.initCustomDomainDB(ctx)
errors.Check(b.initCustomDomainDB(ctx))
errors.Check(b.initServerGroups(ctx))
@@ -126,6 +126,8 @@ func Main(plugins *plugin.Registry) {
errors.Check(b.initProfileDB(ctx))
errors.Check(b.refreshCustomDomainDB(ctx))
errors.Check(b.initDNSCheck(ctx))
errors.Check(b.initRuleStat(ctx))
@@ -151,7 +153,18 @@ func Main(plugins *plugin.Registry) {
b.mustInitDebugSvc(ctx)
// Signal that the server is started.
metrics.SetUpGauge(buildVersion, commitTime, branch, revision, runtime.Version())
errors.Check(metrics.SetUpGauge(
b.promRegisterer,
buildVersion,
branch,
commitTime,
revision,
runtime.Version(),
))
// Unregister the signal behavior for ctx.
stop()
ctx = context.WithoutCancel(ctx)
os.Exit(b.handleSignals(ctx))
}

View File

@@ -9,10 +9,13 @@ import (
"net/url"
"os"
"strings"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/remotekv/consulkv"
"github.com/AdguardTeam/AdGuardDNS/internal/remotekv/rediskv"
"github.com/AdguardTeam/AdGuardDNS/internal/version"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
@@ -36,6 +39,7 @@ type environment struct {
ConsulAllowlistURL *urlutil.URL `env:"CONSUL_ALLOWLIST_URL"`
ConsulDNSCheckKVURL *urlutil.URL `env:"CONSUL_DNSCHECK_KV_URL"`
ConsulDNSCheckSessionURL *urlutil.URL `env:"CONSUL_DNSCHECK_SESSION_URL"`
CustomDomainsURL *urlutil.URL `env:"CUSTOM_DOMAINS_URL"`
DNSCheckRemoteKVURL *urlutil.URL `env:"DNSCHECK_REMOTEKV_URL"`
FilterIndexURL *urlutil.URL `env:"FILTER_INDEX_URL,notEmpty"`
GeneralSafeSearchURL *urlutil.URL `env:"GENERAL_SAFE_SEARCH_URL"`
@@ -50,22 +54,26 @@ type environment struct {
BackendRateLimitAPIKey string `env:"BACKEND_RATELIMIT_API_KEY"`
BillStatAPIKey string `env:"BILLSTAT_API_KEY"`
ConfPath string `env:"CONFIG_PATH" envDefault:"./config.yaml"`
CustomDomainsAPIKey string `env:"CUSTOM_DOMAINS_API_KEY"`
CustomDomainsCachePath string `env:"CUSTOM_DOMAINS_CACHE_PATH"`
DNSCheckKVType string `env:"DNSCHECK_KV_TYPE"`
DNSCheckRemoteKVAPIKey string `env:"DNSCHECK_REMOTEKV_API_KEY"`
FilterCachePath string `env:"FILTER_CACHE_PATH" envDefault:"./filters/"`
GeoIPASNPath string `env:"GEOIP_ASN_PATH" envDefault:"./asn.mmdb"`
GeoIPCountryPath string `env:"GEOIP_COUNTRY_PATH" envDefault:"./country.mmdb"`
LogFormat string `env:"LOG_FORMAT" envDefault:"text"`
NodeName string `env:"NODE_NAME,notEmpty"`
ProfilesAPIKey string `env:"PROFILES_API_KEY"`
ProfilesCachePath string `env:"PROFILES_CACHE_PATH" envDefault:"./profilecache.pb"`
QueryLogPath string `env:"QUERYLOG_PATH" envDefault:"./querylog.jsonl"`
RedisAddr string `env:"REDIS_ADDR"`
RateLimitAllowlistType string `env:"RATELIMIT_ALLOWLIST_TYPE"`
RedisKeyPrefix string `env:"REDIS_KEY_PREFIX" envDefault:"agdns"`
SSLKeyLogFile string `env:"SSL_KEY_LOG_FILE"`
SentryDSN string `env:"SENTRY_DSN" envDefault:"stderr"`
SessionTicketCachePath string `env:"SESSION_TICKET_CACHE_PATH"`
SessionTicketType string `env:"SESSION_TICKET_TYPE"`
SessionTicketAPIKey string `env:"SESSION_TICKET_API_KEY"`
SessionTicketCachePath string `env:"SESSION_TICKET_CACHE_PATH"`
SessionTicketIndexName string `env:"SESSION_TICKET_INDEX_NAME"`
SessionTicketType string `env:"SESSION_TICKET_TYPE"`
// TODO(a.garipov): Consider renaming to "WEB_STATIC_PATH" or something
// similar.
@@ -75,20 +83,19 @@ type environment struct {
ProfilesMaxRespSize datasize.ByteSize `env:"PROFILES_MAX_RESP_SIZE" envDefault:"64MB"`
RedisIdleTimeout timeutil.Duration `env:"REDIS_IDLE_TIMEOUT" envDefault:"30s"`
CustomDomainsRefreshIvl timeutil.Duration `env:"CUSTOM_DOMAINS_REFRESH_INTERVAL"`
DNSCheckKVTTL timeutil.Duration `env:"DNSCHECK_KV_TTL"`
SessionTicketRefreshIvl timeutil.Duration `env:"SESSION_TICKET_REFRESH_INTERVAL"`
// TODO(a.garipov): Rename to DNSCHECK_CACHE_KV_COUNT?
DNSCheckCacheKVSize int `env:"DNSCHECK_CACHE_KV_SIZE"`
RedisMaxActive int `env:"REDIS_MAX_ACTIVE" envDefault:"10"`
RedisMaxIdle int `env:"REDIS_MAX_IDLE" envDefault:"3"`
ListenPort uint16 `env:"LISTEN_PORT" envDefault:"8181"`
RedisPort uint16 `env:"REDIS_PORT" envDefault:"6379"`
Verbosity uint8 `env:"VERBOSE" envDefault:"0"`
AdultBlockingEnabled strictBool `env:"ADULT_BLOCKING_ENABLED" envDefault:"1"`
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"`
@@ -141,7 +148,11 @@ func (envs *environment) Validate() (err error) {
errs = append(errs, fmt.Errorf("VERBOSE: %w", err))
}
errs = envs.validateCustomDomains(errs)
errs = envs.validateDNSCheck(errs)
errs = envs.validateRateLimit(errs)
errs = envs.validateSessionTickets(errs)
errs = envs.validateRateLimitURLs(errs)
return errors.Join(errs...)
}
@@ -245,8 +256,75 @@ func (envs *environment) validateWebStaticDir() (err error) {
return nil
}
// validateSessionTickets appends validation errors to the given errs if
// environment variables for session tickets contain errors.
// validateCustomDomains appends validation errors to errs if the environment
// variables for custom domains contain errors.
func (envs *environment) validateCustomDomains(errs []error) (res []error) {
res = errs
if !envs.CustomDomainsEnabled {
return res
}
res = append(res,
validate.NotEmpty("env CUSTOM_DOMAINS_CACHE_PATH", envs.CustomDomainsCachePath),
validate.Positive("env CUSTOM_DOMAINS_REFRESH_INTERVAL", envs.CustomDomainsRefreshIvl),
)
if err := validate.NotNil("env CUSTOM_DOMAINS_URL", envs.CustomDomainsURL); err != nil {
res = append(res, err)
} else if err = urlutil.ValidateGRPCURL(&envs.CustomDomainsURL.URL); err != nil {
res = append(res, fmt.Errorf("env CUSTOM_DOMAINS_URL: %w", err))
}
return res
}
// validateDNSCheck appends validation errors to errs if the environment
// variables for DNS check contain errors.
func (envs *environment) validateDNSCheck(errs []error) (res []error) {
res = errs
ttl := time.Duration(envs.DNSCheckKVTTL)
var err error
switch typ := envs.DNSCheckKVType; typ {
case kvModeBackend:
res = envs.validateBackendKV(res)
err = validate.Positive("env DNSCHECK_KV_TTL", ttl)
case kvModeCache:
res = envs.validateCache(res)
case kvModeConsul:
err = validate.InRange("env DNSCHECK_KV_TTL", ttl, consulkv.MinTTL, consulkv.MaxTTL)
case kvModeRedis:
err = validate.NoLessThan("env DNSCHECK_KV_TTL", ttl, rediskv.MinTTL)
default:
err = fmt.Errorf("env DNSCHECK_KV_TYPE: %w: %q", errors.ErrBadEnumValue, typ)
}
if err != nil {
res = append(res, err)
}
return res
}
// validateDNSCheck appends validation errors to errs if the environment
// variables for rate limit contain errors.
func (envs *environment) validateRateLimit(errs []error) (res []error) {
switch typ := envs.RateLimitAllowlistType; typ {
case rlAllowlistTypeBackend, rlAllowlistTypeConsul:
// Go on.
default:
err := fmt.Errorf("env RATELIMIT_ALLOWLIST_TYPE: %w: %q", errors.ErrBadEnumValue, typ)
return append(errs, err)
}
return errs
}
// validateSessionTickets appends validation errors to errs if the environment
// variables for session tickets contain errors.
func (envs *environment) validateSessionTickets(errs []error) (res []error) {
res = errs
@@ -287,25 +365,11 @@ func (envs *environment) validateSessionTickets(errs []error) (res []error) {
return res
}
// validateFromValidConfig returns an error if environment variables that depend
// on configuration properties contain errors. conf is expected to be valid.
func (envs *environment) validateFromValidConfig(
conf *configuration,
profilesEnabled bool,
) (err error) {
// validateProfilesConf returns an error if environment variables for profiles
// database configuration contain errors.
func (envs *environment) validateProfilesConf(profilesEnabled bool) (err error) {
var errs []error
switch typ := conf.Check.KV.Type; typ {
case kvModeBackend:
errs = envs.validateBackendKV(errs)
case kvModeCache:
errs = envs.validateCache(errs)
case kvModeRedis:
errs = envs.validateRedis(errs)
default:
// Probably consul.
}
if profilesEnabled {
errs = envs.validateProfilesURLs(errs)
@@ -319,8 +383,6 @@ func (envs *environment) validateFromValidConfig(
}
}
errs = envs.validateRateLimitURLs(conf, errs)
return errors.Join(errs...)
}
@@ -338,34 +400,6 @@ func (envs *environment) validateCache(errs []error) (res []error) {
return res
}
// validateRedis appends validation errors to the given errs if environment
// variables for Redis contain errors.
func (envs *environment) validateRedis(errs []error) (res []error) {
res = errs
if err := validate.NotEmpty("env REDIS_ADDR", envs.RedisAddr); err != nil {
// Don't wrap the error, because it's informative enough as is.
res = append(res, err)
}
if err := validate.Positive("env REDIS_IDLE_TIMEOUT", envs.RedisIdleTimeout); err != nil {
// Don't wrap the error, because it's informative enough as is.
res = append(res, err)
}
if err := validate.NotNegative("env REDIS_MAX_ACTIVE", envs.RedisMaxActive); err != nil {
// Don't wrap the error, because it's informative enough as is.
res = append(res, err)
}
if err := validate.NotNegative("env REDIS_MAX_IDLE", envs.RedisMaxIdle); err != nil {
// Don't wrap the error, because it's informative enough as is.
res = append(res, err)
}
return res
}
// validateBackendKV appends validation errors to the given errs if environment
// variables for a backend key-value store contain errors.
func (envs *environment) validateBackendKV(errs []error) (res []error) {
@@ -420,15 +454,12 @@ func (envs *environment) validateProfilesURLs(errs []error) (res []error) {
// validateRateLimitURLs appends validation errors to the given errs if rate
// limit URLs in environment variables are invalid.
func (envs *environment) validateRateLimitURLs(
conf *configuration,
errs []error,
) (withURLs []error) {
func (envs *environment) validateRateLimitURLs(errs []error) (withURLs []error) {
rlURL := envs.BackendRateLimitURL
rlEnv := "BACKEND_RATELIMIT_URL"
validateFunc := urlutil.ValidateGRPCURL
if conf.RateLimit.Allowlist.Type == rlAllowlistTypeConsul {
if envs.RateLimitAllowlistType == rlAllowlistTypeConsul {
rlURL = envs.ConsulAllowlistURL
rlEnv = "CONSUL_ALLOWLIST_URL"
validateFunc = urlutil.ValidateHTTPURL

View File

@@ -25,10 +25,12 @@ type interfaceListenersConfig struct {
}
// toInternal converts c to a possibly-nil bindtodevice.Manager. c must be
// valid.
// valid. logger, errColl, and mtrc must not be nil. If ctrlConf is nil, a
// default configuration is used.
func (c *interfaceListenersConfig) toInternal(
logger *slog.Logger,
errColl errcoll.Interface,
mtrc bindtodevice.Metrics,
ctrlConf *bindtodevice.ControlConfig,
) (m *bindtodevice.Manager, err error) {
if c == nil {
@@ -39,6 +41,7 @@ func (c *interfaceListenersConfig) toInternal(
Logger: logger.With(slogutil.KeyPrefix, "bindtodevice"),
InterfaceStorage: bindtodevice.DefaultInterfaceStorage{},
ErrColl: errColl,
Metrics: mtrc,
ChannelBufferSize: c.ChannelBufferSize,
})

View File

@@ -3,9 +3,10 @@
package plugin
import (
"github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/AdGuardDNS/internal/rulestat"
"github.com/AdguardTeam/golibs/service"
)
@@ -15,9 +16,12 @@ type Config struct {
// DNSCheck is a custom implementation of the DNSCheck service.
DNSCheck dnscheck.Interface
// GRPCMtrc is a custom implementation of the gRPC metrics.
GRPCMtrc backendpb.GRPCMetrics
// MainMwMtrc is a custom implementation of the filtering-middleware
// metrics.
MainMwMtrc metrics.MainMiddleware
MainMwMtrc dnssvc.MainMiddlewareMetrics
// PostInitMw is a custom implementation of the post-initial middleware.
PostInitMw dnsserver.Middleware
@@ -39,7 +43,8 @@ type Config struct {
// values.
type Registry struct {
dnscheck dnscheck.Interface
mainMwMtrc metrics.MainMiddleware
grpcMtrc backendpb.GRPCMetrics
mainMwMtrc dnssvc.MainMiddlewareMetrics
postInitMw dnsserver.Middleware
ruleStat rulestat.Interface
refrs map[string]service.Refresher
@@ -51,6 +56,7 @@ type Registry struct {
func NewRegistry(c *Config) (r *Registry) {
return &Registry{
dnscheck: c.DNSCheck,
grpcMtrc: c.GRPCMtrc,
mainMwMtrc: c.MainMwMtrc,
postInitMw: c.PostInitMw,
ruleStat: c.RuleStat,
@@ -68,9 +74,18 @@ func (r *Registry) DNSCheck() (dnsCk dnscheck.Interface) {
return r.dnscheck
}
// GRPCMetrics returns a custom implementation of the gRPC metrics, if any.
func (r *Registry) GRPCMetrics() (grpcMtrc backendpb.GRPCMetrics) {
if r == nil {
return nil
}
return r.grpcMtrc
}
// MainMiddlewareMetrics returns a custom implementation of the
// filtering-middleware metrics, if any.
func (r *Registry) MainMiddlewareMetrics() (mainMwMtrc metrics.MainMiddleware) {
func (r *Registry) MainMiddlewareMetrics() (mainMwMtrc dnssvc.MainMiddlewareMetrics) {
if r == nil {
return nil
}

View File

@@ -2,7 +2,6 @@ package cmd
import (
"context"
"fmt"
"log/slog"
"time"
@@ -135,10 +134,6 @@ func (c *rateLimitConfig) Validate() (err error) {
// allowListConfig is the consul allow list configuration.
type allowListConfig struct {
// Type defines where the rate limit settings are received from. Allowed
// values are [rlAllowlistTypeBackend] and [rlAllowlistTypeConsul].
Type string `yaml:"type"`
// List contains IPs and CIDRs.
List []netutil.Prefix `yaml:"list"`
@@ -161,20 +156,7 @@ func (c *allowListConfig) Validate() (err error) {
return errors.ErrNoValue
}
errs := []error{
validate.Positive("refresh_interval", c.RefreshIvl),
}
switch c.Type {
case
rlAllowlistTypeBackend,
rlAllowlistTypeConsul:
// Go on.
default:
errs = append(errs, fmt.Errorf("type: %w: %q", errors.ErrBadEnumValue, c.Type))
}
return errors.Join(errs...)
return validate.Positive("refresh_interval", c.RefreshIvl)
}
// connLimitConfig is the configuration structure for the stream-connection

View File

@@ -126,7 +126,7 @@ type tlsConfigCerts []*tlsConfigCert
func (certs tlsConfigCerts) store(ctx context.Context, tlsMgr tlsconfig.Manager) (err error) {
var errs []error
for i, c := range certs {
err = tlsMgr.Add(ctx, c.Certificate, c.Key)
err = tlsMgr.Add(ctx, c.Certificate, c.Key, false)
if err != nil {
errs = append(errs, fmt.Errorf("adding certificate at index %d: %w", i, err))
}

View File

@@ -7,8 +7,8 @@ import (
"sync/atomic"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
)
// limitConn is a wrapper for a stream connection that decreases the counter

View File

@@ -66,7 +66,12 @@ func (EmptyMetrics) DecrementActive(_ context.Context, _ *ConnMetricsData) {}
func (EmptyMetrics) ObserveLifeDuration(_ context.Context, _ *ConnMetricsData, _ time.Duration) {}
// ObserveWaitingDuration implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) ObserveWaitingDuration(_ context.Context, _ *ConnMetricsData, _ time.Duration) {}
func (EmptyMetrics) ObserveWaitingDuration(
_ context.Context,
_ *ConnMetricsData,
_ time.Duration,
) {
}
// SetStopLimit implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) SetStopLimit(_ context.Context, _ uint64) {}

View File

@@ -17,6 +17,7 @@ import (
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/fakeservice"
"github.com/AdguardTeam/golibs/testutil/servicetest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -79,14 +80,7 @@ func TestService_Start(t *testing.T) {
svc := debugsvc.New(c)
require.NotNil(t, svc)
var err error
require.NotPanics(t, func() {
err = svc.Start(testutil.ContextWithTimeout(t, testTimeout))
})
require.NoError(t, err)
testutil.CleanupAndRequireSuccess(t, func() (err error) {
return svc.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
})
servicetest.RequireRun(t, svc, testTimeout)
client := agdhttp.NewClient(&agdhttp.ClientConfig{
Timeout: testTimeout,
@@ -105,10 +99,10 @@ func TestService_Start(t *testing.T) {
// yet, check for it in periodically.
var resp *http.Response
healthCheckURL := srvURL.JoinPath(debugsvc.PathPatternHealthCheck)
require.Eventually(t, func() (ok bool) {
resp, err = client.Get(ctx, healthCheckURL)
return err == nil
require.EventuallyWithT(t, func(ct *assert.CollectT) {
var getErr error
resp, getErr = client.Get(ctx, healthCheckURL)
assert.NoError(t, getErr)
}, testTimeout, testTimeout/10)
body := readRespBody(t, resp)
@@ -116,7 +110,7 @@ func TestService_Start(t *testing.T) {
assert.Equal(t, http.StatusOK, resp.StatusCode)
// Check pprof service URL.
resp, err = client.Get(ctx, srvURL.JoinPath(httputil.PprofBasePath))
resp, err := client.Get(ctx, srvURL.JoinPath(httputil.PprofBasePath))
require.NoError(t, err)
body = readRespBody(t, resp)

View File

@@ -36,6 +36,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/mathutil/randutil"
"github.com/AdguardTeam/golibs/service"
"github.com/miekg/dns"
)
@@ -166,7 +167,7 @@ func NewHandler(conf *HandlerConfig) (h *Handler) {
src := conf.RandSource
if src == nil {
// Do not initialize through [cmp.Or], as the default value could panic.
src = rand.NewChaCha8(mustNewSeed())
src = rand.NewChaCha8(randutil.MustNewSeed())
}
hcConf := conf.Healthcheck
@@ -177,10 +178,7 @@ func NewHandler(conf *HandlerConfig) (h *Handler) {
h = &Handler{
logger: cmp.Or(conf.Logger, slog.Default()),
// #nosec G404 -- We don't need a real random, pseudorandom is enough.
rand: rand.New(&lockedSource{
mu: &sync.Mutex{},
src: src,
}),
rand: rand.New(randutil.NewLockedSource(src)),
activeUpstreamsMu: &sync.RWMutex{},
}

View File

@@ -1,41 +0,0 @@
package forward
import (
cryptorand "crypto/rand"
"math/rand/v2"
"sync"
)
// mustNewSeed returns new 32 byte seed for pseudorandom generators. Panics on
// errors.
//
// TODO(a.garipov): Remove once agdrand is merged into golibs.
func mustNewSeed() (seed [32]byte) {
_, err := cryptorand.Read(seed[:])
if err != nil {
// Don't wrap the error, because it's informative enough as is.
panic(err)
}
return seed
}
// lockedSource is an implementation of [rand.Source] that is concurrency-safe.
//
// TODO(a.garipov): Remove once agdrand is merged into golibs.
type lockedSource struct {
// mu protects src.
mu *sync.Mutex
src rand.Source
}
// type check
var _ rand.Source = (*lockedSource)(nil)
// Uint64 implements the [rand.Source] interface for *lockedSource.
func (s *lockedSource) Uint64() (r uint64) {
s.mu.Lock()
defer s.mu.Unlock()
return s.src.Uint64()
}

View File

@@ -1,9 +1,9 @@
module github.com/AdguardTeam/AdGuardDNS/internal/dnsserver
go 1.24.3
go 1.24.5
require (
github.com/AdguardTeam/golibs v0.32.11
github.com/AdguardTeam/golibs v0.32.15
github.com/ameshkov/dnscrypt/v2 v2.4.0
github.com/ameshkov/dnsstamps v1.0.3
github.com/bluele/gcache v0.0.2
@@ -14,7 +14,7 @@ require (
github.com/prometheus/client_golang v1.22.0
github.com/quic-go/quic-go v0.52.0
github.com/stretchr/testify v1.10.0
golang.org/x/net v0.40.0
golang.org/x/net v0.41.0
golang.org/x/sys v0.33.0
)
@@ -23,7 +23,7 @@ require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
github.com/onsi/gomega v1.37.0 // indirect
@@ -35,12 +35,12 @@ require (
github.com/robfig/cron/v3 v3.0.1 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.5.2 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/tools v0.34.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,4 +1,5 @@
github.com/AdguardTeam/golibs v0.32.11 h1:75EquS8SWvzsM3JFJY0359ZBw66jDjAegteHzh9nSw8=
github.com/AdguardTeam/golibs v0.32.15 h1:arDRDWiZCH3g5Onr8AqMnOHhaOppNoBpgC3DNhmeDeA=
github.com/AdguardTeam/golibs v0.32.15/go.mod h1:G9CzUOzx87J+2u+eClJrrwWD7lMbROvuUnT8uvDUzIA=
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=
@@ -14,12 +15,13 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
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/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4=
github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18=
github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
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=
@@ -62,21 +64,22 @@ 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/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -3,6 +3,7 @@ package prometheus_test
import (
"net"
"testing"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/logutil/slogutil"
@@ -10,6 +11,9 @@ import (
"github.com/stretchr/testify/require"
)
// testTimeout is the common timeout for tests.
const testTimeout = 1 * time.Second
// testLogger is the common logger for tests.
var testLogger = slogutil.NewDiscardLogger()

View File

@@ -8,7 +8,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
dnssvcprom "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/servicetest"
"github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
@@ -37,13 +37,7 @@ func TestServerMetricsListener_integration_requestLifetime(t *testing.T) {
srv := dnsserver.NewServerDNS(conf)
// Start the server.
err = srv.Start(context.Background())
require.NoError(t, err)
// Make sure the server shuts down in the end.
testutil.CleanupAndRequireSuccess(t, func() (err error) {
return srv.Shutdown(context.Background())
})
servicetest.RequireRun(t, srv, testTimeout)
// Create a test message.
req := dnsservertest.CreateMessage(testReqDomain, dns.TypeA)

View File

@@ -9,7 +9,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/cmd/plugin"
"github.com/AdguardTeam/AdGuardDNS/internal/connlimiter"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
@@ -17,7 +16,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
@@ -110,12 +108,17 @@ type HandlersConfig struct {
// profiles enabled.
HumanIDParser *agd.HumanIDParser
// MainMiddlewareMetrics is used to collect metrics for the main middleware,
// if needed.
MainMiddlewareMetrics MainMiddlewareMetrics
// Messages is the message constructor used to create blocked and other
// messages for this DNS service. It must not be nil.
Messages *dnsmsg.Constructor
// PluginRegistry is used to override configuration parameters.
PluginRegistry *plugin.Registry
// PostInitialMiddleware is the middleware to run after the initial
// middleware, if any.
PostInitialMiddleware dnsserver.Middleware
// StructuredErrors is the configuration for the experimental Structured DNS
// Errors feature in the profiles' message constructors. It must not be
@@ -131,6 +134,9 @@ type HandlersConfig struct {
// CacheManager is the global cache manager. It must not be nil.
CacheManager agdcache.Manager
// CustomDomainDB is used to match custom domains. It must not be nil.
CustomDomainDB CustomDomainDB
// DNSCheck is used by clients to check if they use AdGuard DNS. It must
// not be nil.
DNSCheck dnscheck.Interface
@@ -184,6 +190,9 @@ type HandlersConfig struct {
// valid Prometheus metric label.
MetricsNamespace string
// NodeName is the name of this server node.
NodeName string
// FilteringGroups are the DNS filtering groups. Each element must be
// non-nil.
FilteringGroups map[agd.FilteringGroupID]*agd.FilteringGroup
@@ -269,7 +278,3 @@ type ServerGroupConfig struct {
// ServerGroupName is the name of a server group.
type ServerGroupName string
// DDRConfig is the configuration for the server group's Discovery Of Designated
// Resolvers (DDR) handlers.
type DDRConfig = initial.DDRConfig

View File

@@ -48,6 +48,7 @@ func NewHandlers(ctx context.Context, c *HandlersConfig) (handlers Handlers, err
QueryLog: c.QueryLog,
Metrics: mainMwMtrc,
RuleStat: c.RuleStat,
NodeName: c.NodeName,
})
handler = mainMw.Wrap(handler)
@@ -61,7 +62,7 @@ func NewHandlers(ctx context.Context, c *HandlersConfig) (handlers Handlers, err
handler = preSvcMw.Wrap(handler)
postInitMw := c.PluginRegistry.PostInitialMiddleware()
postInitMw := c.PostInitialMiddleware
if postInitMw != nil {
handler = postInitMw.Wrap(handler)
}
@@ -149,12 +150,12 @@ func wrapPreUpstreamMw(
// newMainMiddlewareMetrics returns a filtering-middleware metrics
// implementation from the config.
func newMainMiddlewareMetrics(c *HandlersConfig) (mainMwMtrc MainMiddlewareMetrics, err error) {
mainMwMtrc = c.PluginRegistry.MainMiddlewareMetrics()
mainMwMtrc = c.MainMiddlewareMetrics
if mainMwMtrc != nil {
return mainMwMtrc, nil
}
mainMwMtrc, err = metrics.NewDefaultMainMiddleware(
mainMwMtrc, err = metrics.NewMainMiddleware(
c.BaseLogger.With(slogutil.KeyPrefix, "mainmw_metrics"),
c.MetricsNamespace,
c.PrometheusRegisterer,
@@ -168,15 +169,27 @@ func newMainMiddlewareMetrics(c *HandlersConfig) (mainMwMtrc MainMiddlewareMetri
// newHandlersForServers returns a handler map for each server group and each
// server.
func newHandlersForServers(c *HandlersConfig, handler dnsserver.Handler) (handlers Handlers, err error) {
rlMwMtrc, err := metrics.NewDefaultRatelimitMiddleware(
c.MetricsNamespace,
c.PrometheusRegisterer,
)
//
// TODO(a.garipov): Refactor.
func newHandlersForServers(
c *HandlersConfig,
handler dnsserver.Handler,
) (handlers Handlers, err error) {
initMwMtrc, err := metrics.NewInitialMiddleware(c.MetricsNamespace, c.PrometheusRegisterer)
if err != nil {
return nil, fmt.Errorf("initial middleware metrics: %w", err)
}
rlMwMtrc, err := metrics.NewRatelimitMiddleware(c.MetricsNamespace, c.PrometheusRegisterer)
if err != nil {
return nil, fmt.Errorf("ratelimit middleware metrics: %w", err)
}
dfMtrc, err := metrics.NewDeviceFinder(c.MetricsNamespace, c.PrometheusRegisterer)
if err != nil {
return nil, fmt.Errorf("device finder metrics: %w", err)
}
handlers = Handlers{}
rlMwLogger := c.BaseLogger.With(slogutil.KeyPrefix, "ratelimitmw")
@@ -191,8 +204,9 @@ func newHandlersForServers(c *HandlersConfig, handler dnsserver.Handler) (handle
}
initMw := initial.New(&initial.Config{
Logger: c.BaseLogger.With(slogutil.KeyPrefix, "initmw"),
DDR: srvGrp.DDR,
Logger: c.BaseLogger.With(slogutil.KeyPrefix, "initmw"),
Metrics: initMwMtrc,
DDR: srvGrp.DDR,
})
srvGrpHandler := initMw.Wrap(handler)
@@ -213,7 +227,7 @@ func newHandlersForServers(c *HandlersConfig, handler dnsserver.Handler) (handle
ServerInfo: srvInfo,
StructuredErrors: c.StructuredErrors,
AccessManager: c.AccessManager,
DeviceFinder: newDeviceFinder(c, srvGrp, srv),
DeviceFinder: newDeviceFinder(c, srvGrp, srv, dfMtrc),
ErrColl: c.ErrColl,
GeoIP: c.GeoIP,
Metrics: rlMwMtrc,
@@ -236,18 +250,22 @@ func newHandlersForServers(c *HandlersConfig, handler dnsserver.Handler) (handle
// newDeviceFinder returns a new agd.DeviceFinder for a server based on the
// configuration. All arguments must not be nil.
func newDeviceFinder(c *HandlersConfig, g *ServerGroupConfig, s *agd.Server) (df agd.DeviceFinder) {
func newDeviceFinder(
c *HandlersConfig,
g *ServerGroupConfig,
s *agd.Server,
mtrc DeviceFinderMetrics,
) (df agd.DeviceFinder) {
if !g.ProfilesEnabled {
return agd.EmptyDeviceFinder{}
}
return devicefinder.NewDefault(&devicefinder.Config{
HumanIDParser: c.HumanIDParser,
Logger: c.BaseLogger.With(slogutil.KeyPrefix, "devicefinder"),
Server: s,
// TODO(a.garipov): Use a real one after implementing the interface in
// package tlsconfig.
CustomDomainDB: devicefinder.EmptyCustomDomainDB{},
HumanIDParser: c.HumanIDParser,
Logger: c.BaseLogger.With(slogutil.KeyPrefix, "devicefinder"),
Server: s,
CustomDomainDB: c.CustomDomainDB,
Metrics: mtrc,
ProfileDB: c.ProfileDB,
DeviceDomains: g.DeviceDomains,
})

View File

@@ -150,17 +150,20 @@ func TestNewHandlers(t *testing.T) {
ctx := testutil.ContextWithTimeout(t, dnssvctest.Timeout)
handlers, err := dnssvc.NewHandlers(ctx, &dnssvc.HandlersConfig{
BaseLogger: testLogger,
Cloner: agdtest.NewCloner(),
Cache: tc.cacheConf,
HumanIDParser: agd.NewHumanIDParser(),
Messages: agdtest.NewConstructor(t),
PluginRegistry: nil,
StructuredErrors: agdtest.NewSDEConfig(true),
AccessManager: accessMgr,
BillStat: billStat,
BaseLogger: testLogger,
Cloner: agdtest.NewCloner(),
Cache: tc.cacheConf,
HumanIDParser: agd.NewHumanIDParser(),
MainMiddlewareMetrics: nil,
Messages: agdtest.NewConstructor(t),
PostInitialMiddleware: nil,
StructuredErrors: agdtest.NewSDEConfig(true),
AccessManager: accessMgr,
BillStat: billStat,
// TODO(a.garipov): Create a test implementation?
CacheManager: agdcache.EmptyManager{},
CacheManager: agdcache.EmptyManager{},
// TODO(a.garipov): Create a test implementation?
CustomDomainDB: dnssvc.EmptyCustomDomainDB{},
DNSCheck: dnsCk,
DNSDB: dnsDB,
ErrColl: agdtest.NewErrorCollector(),
@@ -174,6 +177,7 @@ func TestNewHandlers(t *testing.T) {
RateLimit: agdtest.NewRateLimit(),
RuleStat: ruleStat,
MetricsNamespace: path.Base(t.Name()),
NodeName: t.Name(),
FilteringGroups: fltGrps,
ServerGroups: []*dnssvc.ServerGroupConfig{srvGrp},
EDEEnabled: true,

View File

@@ -232,6 +232,7 @@ func newTestService(
},
},
CacheManager: agdcache.EmptyManager{},
CustomDomainDB: dnssvc.EmptyCustomDomainDB{},
DNSCheck: dnsCk,
DNSDB: dnsDB,
ErrColl: errColl,
@@ -245,6 +246,7 @@ func newTestService(
RateLimit: rl,
RuleStat: ruleStat,
MetricsNamespace: path.Base(t.Name()),
NodeName: t.Name(),
FilteringGroups: map[agd.FilteringGroupID]*agd.FilteringGroup{
dnssvctest.FilteringGroupID: fltGrp,
},

View File

@@ -9,10 +9,14 @@ import (
// CustomDomainDB contains information about custom domains and matches domains.
type CustomDomainDB interface {
// Match returns the domain name or wildcard that matches the client-sent
// server name. If there is a match, matchedDomain must be a valid domain
// name or wildcard, and profID must not be empty and must be valid.
// Otherwise, both matchedDomain and profID must be empty.
Match(ctx context.Context, cliSrvName string) (matchedDomain string, profID agd.ProfileID)
// server name. cliSrvName must be lowercased.
//
// If there is a match, matchedDomain must be a valid domain name or
// wildcard, and profIDs must not be empty and its items must be valid.
// Otherwise, matchedDomain must be empty and profIDs must be nil.
//
// TODO(a.garipov, e.burkov): Reduce allocations of profIDs.
Match(ctx context.Context, cliSrvName string) (matchedDomain string, profIDs []agd.ProfileID)
}
// EmptyCustomDomainDB is an [CustomDomainDB] that does nothing.
@@ -26,6 +30,6 @@ var _ CustomDomainDB = EmptyCustomDomainDB{}
func (EmptyCustomDomainDB) Match(
_ context.Context,
_ string,
) (matchedDomain string, profID agd.ProfileID) {
return "", ""
) (matchedDomain string, profIDs []agd.ProfileID) {
return "", nil
}

View File

@@ -4,13 +4,13 @@ import (
"context"
"fmt"
"net/netip"
"slices"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/AdGuardDNS/internal/profiledb"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/AdguardTeam/golibs/logutil/slogutil"
)
@@ -118,16 +118,18 @@ func (f *Default) deviceResultByCustomDomain(
switch dd := cd.deviceData.(type) {
case *deviceDataID:
prof, dev, err := f.profileDB.ProfileByDeviceID(ctx, dd.id)
if err != nil || (prof != nil && prof.ID == cd.profileID) {
if err != nil || (prof != nil && slices.Contains(cd.profileIDs, prof.ID)) {
return f.newDeviceResult(ctx, prof, dev, "custom domain and device id", err)
}
f.metrics.IncrementCustomDomainMismatches(ctx, cd.domain)
optslog.Debug4(
ctx,
f.logger,
"custom-domain device and required profile mismatch",
"domain", cd.domain,
"required_profile_id", cd.profileID,
"required_profile_ids", cd.profileIDs,
"dev_id", dev.ID,
"dev_prof_id", prof.ID,
)
@@ -223,10 +225,7 @@ func (f *Default) deviceByAddrs(
}
// deviceByLocalAddr finds the profile and the device by the local address.
func (f *Default) deviceByLocalAddr(
ctx context.Context,
localIP netip.Addr,
) (r agd.DeviceResult) {
func (f *Default) deviceByLocalAddr(ctx context.Context, localIP netip.Addr) (r agd.DeviceResult) {
p, d, err := f.profileDB.ProfileByDedicatedIP(ctx, localIP)
if err == nil {
optslog.Debug3(
@@ -251,6 +250,8 @@ func (f *Default) deviceByLocalAddr(
}
}
f.metrics.IncrementUnknownDedicated(ctx)
optslog.Debug2(
ctx,
f.logger,
@@ -275,7 +276,7 @@ func (f *Default) authenticatedResult(
dev := res.Device
err := f.authenticate(ctx, srvReqInfo, dev)
if err != nil {
metrics.DNSSvcDoHAuthFailsTotal.Inc()
f.metrics.IncrementDoHAuthenticationFails(ctx)
optslog.Debug2(
ctx,

View File

@@ -5,15 +5,15 @@ import (
"fmt"
"net/url"
"path"
"slices"
"strings"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/validate"
"github.com/miekg/dns"
)
@@ -75,7 +75,7 @@ func (*deviceDataID) isDeviceData() {}
type deviceDataCustomDomain struct {
// deviceData is the underlying device data, which must be either a
// [deviceDataID] or a [deviceDataExtHumanID]. If it's the latter, the
// profile IDs must match.
// profile IDs must contain the profile ID of the device.
deviceData deviceData
// domain is the domain or wildcard that has matched the request. It must
@@ -84,7 +84,7 @@ type deviceDataCustomDomain struct {
// profileID is the ID of the profile owning the custom domain. It must not
// be empty.
profileID agd.ProfileID
profileIDs []agd.ProfileID
}
// type check
@@ -125,11 +125,13 @@ func (f *Default) deviceDataFromEncrypted(
ctx context.Context,
srvReqInfo *dnsserver.RequestInfo,
) (dd deviceData, err error) {
cliSrvName := srvReqInfo.TLSServerName
var customDomain string
var requiredProfileID agd.ProfileID
if cliSrvName != "" {
customDomain, requiredProfileID = f.customDomainDB.Match(ctx, cliSrvName)
var requiredProfileIDs []agd.ProfileID
if cliSrvName := strings.ToLower(srvReqInfo.TLSServerName); cliSrvName != "" {
customDomain, requiredProfileIDs = f.customDomainDB.Match(ctx, cliSrvName)
if customDomain != "" {
f.metrics.IncrementCustomDomainRequests(ctx, customDomain)
}
}
dd, err = f.deviceDataFromSrvReqInfo(ctx, srvReqInfo, customDomain)
@@ -137,7 +139,7 @@ func (f *Default) deviceDataFromEncrypted(
return nil, fmt.Errorf("extracting device data: %w", err)
}
dd, err = f.wrapCustomDomain(ctx, dd, customDomain, requiredProfileID)
dd, err = f.wrapCustomDomain(ctx, dd, customDomain, requiredProfileIDs)
if err != nil {
return nil, fmt.Errorf("wrapping custom domains: %w", err)
}
@@ -255,9 +257,9 @@ func (f *Default) wrapCustomDomain(
ctx context.Context,
dd deviceData,
matchedDomain string,
requiredProfileID agd.ProfileID,
requiredProfileIDs []agd.ProfileID,
) (wrapped deviceData, err error) {
if requiredProfileID == "" {
if requiredProfileIDs == nil {
return dd, nil
}
@@ -265,23 +267,25 @@ func (f *Default) wrapCustomDomain(
case nil:
return nil, nil
case *deviceDataExtHumanID:
err = validate.Equal("profile id in ext id", dd.profileID, requiredProfileID)
if err != nil {
const msg = "custom domain profile and ext id mismatch"
optslog.Debug2(ctx, f.logger, msg, "got", dd.profileID, "want", requiredProfileID)
if !slices.Contains(requiredProfileIDs, dd.profileID) {
const msg = "custom domain profiles and ext id mismatch"
optslog.Debug2(ctx, f.logger, msg, "got", dd.profileID, "want", requiredProfileIDs)
const errMsg = "profile id in ext id: %s: not contained by expected values"
err = fmt.Errorf(errMsg, dd.profileID)
return nil, newDeviceDataError(err, "custom domain")
}
return &deviceDataCustomDomain{
domain: matchedDomain,
profileID: requiredProfileID,
profileIDs: requiredProfileIDs,
deviceData: dd,
}, nil
case *deviceDataID:
return &deviceDataCustomDomain{
domain: matchedDomain,
profileID: requiredProfileID,
profileIDs: requiredProfileIDs,
deviceData: dd,
}, nil
default:

View File

@@ -4,6 +4,7 @@ import (
"context"
"net/url"
"path"
"slices"
"testing"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
@@ -405,7 +406,7 @@ type testCustomDomainDB struct {
onMatch func(
ctx context.Context,
cliSrvName string,
) (matchedDomain string, profID agd.ProfileID)
) (matchedDomain string, profIDs []agd.ProfileID)
}
// type check
@@ -415,16 +416,16 @@ var _ devicefinder.CustomDomainDB = (*testCustomDomainDB)(nil)
func (db *testCustomDomainDB) Match(
ctx context.Context,
cliSrvName string,
) (matchedDomain string, profID agd.ProfileID) {
) (matchedDomain string, profIDs []agd.ProfileID) {
return db.onMatch(ctx, cliSrvName)
}
// newTestCustomDomainDB returns a *testCustomDomainDB that returns the given
// data.
func newTestCustomDomainDB(domain string, id agd.ProfileID) (db *testCustomDomainDB) {
func newTestCustomDomainDB(domain string, ids []agd.ProfileID) (db *testCustomDomainDB) {
return &testCustomDomainDB{
onMatch: func(_ context.Context, _ string) (matchedDomain string, profID agd.ProfileID) {
return domain, id
onMatch: func(_ context.Context, _ string) (matchedDomain string, profIDs []agd.ProfileID) {
return domain, slices.Clone(ids)
},
}
}
@@ -435,17 +436,25 @@ func TestDefault_Find_customDomainDoT(t *testing.T) {
const customDomain = "custom.example"
var (
customDBMatch = newTestCustomDomainDB(customDomain, dnssvctest.ProfileID)
customDBMatchWk = newTestCustomDomainDB("*."+customDomain, dnssvctest.ProfileID)
customDBNoMatch = newTestCustomDomainDB("", "")
)
const profIDOtherStr = "prof5678"
profOther := &agd.Profile{}
*profOther = *profNormal
profOther.ID = agd.ProfileID(profIDOtherStr)
var (
customDBMatch = newTestCustomDomainDB(customDomain, []agd.ProfileID{
dnssvctest.ProfileID,
})
customDBMatchWkSeveral = newTestCustomDomainDB("*."+customDomain, []agd.ProfileID{
dnssvctest.ProfileID,
profIDOtherStr,
})
customDBMatchWk = newTestCustomDomainDB("*."+customDomain, []agd.ProfileID{
dnssvctest.ProfileID,
})
customDBNoMatch = newTestCustomDomainDB("", nil)
)
profDBDefault := agdtest.NewProfileDB()
profDBFoundDevID := agdtest.NewProfileDB()
@@ -473,8 +482,7 @@ func TestDefault_Find_customDomainDoT(t *testing.T) {
const cliSrvNameDev = dnssvctest.DeviceIDStr + "." + customDomain
const errStrMismatch errors.Error = `wrapping custom domains: custom domain device id check: ` +
`profile id in ext id: not equal to expected value: ` +
`got ` + profIDOtherStr + `, want ` + dnssvctest.ProfileIDStr
`profile id in ext id: ` + profIDOtherStr + `: not contained by expected values`
testCases := []struct {
customDB devicefinder.CustomDomainDB
@@ -488,6 +496,12 @@ func TestDefault_Find_customDomainDoT(t *testing.T) {
wantRes: resNormal,
cliSrvName: cliSrvNameDev,
name: "custom_device_match",
}, {
customDB: customDBMatchWkSeveral,
profDB: profDBFoundDevID,
wantRes: resNormal,
cliSrvName: cliSrvNameDev,
name: "custom_device_match_several",
}, {
customDB: customDBNoMatch,
profDB: profDBDefault,

View File

@@ -30,6 +30,10 @@ type Config struct {
// CustomDomainDB is used to match custom domains. It must not be nil.
CustomDomainDB CustomDomainDB
// Metrics are used to collect the statistics of the default device finder.
// It must not be nil.
Metrics Metrics
// ProfileDB is used to find the profiles. It must not be nil.
ProfileDB profiledb.Interface
@@ -44,6 +48,7 @@ type Default struct {
logger *slog.Logger
srv *agd.Server
customDomainDB CustomDomainDB
metrics Metrics
profileDB profiledb.Interface
deviceDomains []string
}
@@ -55,6 +60,7 @@ func NewDefault(c *Config) (f *Default) {
logger: c.Logger,
srv: c.Server,
customDomainDB: c.CustomDomainDB,
metrics: c.Metrics,
profileDB: c.ProfileDB,
deviceDomains: c.DeviceDomains,
}

View File

@@ -280,6 +280,7 @@ func newDefault(tb testing.TB, c *devicefinder.Config) (f *devicefinder.Default)
c.CustomDomainDB,
devicefinder.EmptyCustomDomainDB{},
)
c.Metrics = cmp.Or[devicefinder.Metrics](c.Metrics, devicefinder.EmptyMetrics{})
c.ProfileDB = cmp.Or[profiledb.Interface](c.ProfileDB, agdtest.NewProfileDB())
return devicefinder.NewDefault(c)

View File

@@ -0,0 +1,45 @@
package devicefinder
import "context"
// Metrics is an interface for collection of the statistics of the default
// device finder.
type Metrics interface {
// IncrementCustomDomainMismatches is called when a detected device does not
// belong to the profile which the custom domain belongs to.
IncrementCustomDomainMismatches(ctx context.Context, domain string)
// IncrementCustomDomainRequests is called when a request is recognized as
// being to a custom domain belonging to a profile.
IncrementCustomDomainRequests(ctx context.Context, domain string)
// IncrementDoHAuthenticationFails is called when a request fails DoH
// authentication.
IncrementDoHAuthenticationFails(ctx context.Context)
// IncrementUnknownDedicated is called when the DNS request is sent to an
// unknown local address.
IncrementUnknownDedicated(ctx context.Context)
}
// EmptyMetrics is an empty [Metrics] implementation that does nothing.
type EmptyMetrics struct{}
// type check
var _ Metrics = EmptyMetrics{}
// IncrementCustomDomainRequests implements the [Metrics] interface for
// EmptyMetrics.
func (EmptyMetrics) IncrementCustomDomainRequests(_ context.Context, _ string) {}
// IncrementCustomDomainMismatches implements the [Metrics] interface for
// EmptyMetrics.
func (EmptyMetrics) IncrementCustomDomainMismatches(_ context.Context, _ string) {}
// IncrementDoHAuthenticationFails implements the [Metrics] interface for
// EmptyMetrics.
func (EmptyMetrics) IncrementDoHAuthenticationFails(_ context.Context) {}
// IncrementUnknownDedicated implements the [Metrics] interface for
// EmptyMetrics.
func (EmptyMetrics) IncrementUnknownDedicated(_ context.Context) {}

View File

@@ -15,9 +15,9 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/miekg/dns"
)
@@ -25,8 +25,9 @@ import (
// middleware must be the most outer middleware apart from the ratelimit/access
// middleware.
type Middleware struct {
logger *slog.Logger
ddr *DDRConfig
logger *slog.Logger
metrics Metrics
ddr *DDRConfig
}
// Config is the configuration structure for the initial middleware. All fields
@@ -35,6 +36,9 @@ type Config struct {
// Logger is used to log the operation of the middleware.
Logger *slog.Logger
// Metrics is used to collect the statistics of the middleware.
Metrics Metrics
// DDR is the configuration for the server group's Discovery Of Designated
// Resolvers (DDR) handlers. It must not be nil.
DDR *DDRConfig
@@ -68,8 +72,9 @@ type DDRConfig struct {
// must be valid.
func New(c *Config) (mw *Middleware) {
return &Middleware{
logger: c.Logger,
ddr: c.DDR,
logger: c.Logger,
metrics: c.Metrics,
ddr: c.DDR,
}
}

View File

@@ -0,0 +1,51 @@
package initial
import (
"context"
)
// Request kinds for [Metrics].
const (
// MetricsRequestKindDDR is a label for requests for Discovery of Designated
// Resolvers.
MetricsRequestKindDDR = "ddr"
// MetricsRequestKindBadResolverARPA is a label for requests for malformed
// resolver.arpa queries.
MetricsRequestKindBadResolverARPA = "bad_resolver_arpa"
// MetricsRequestKindChromePrefetch is a label for requests for the domain
// name that Chrome uses to check if it should use its prefetch proxy.
MetricsRequestKindChromePrefetch = "chrome_prefetch"
// MetricsRequestKindFirefox is a label for requests for the domain name
// that Firefox uses to check if it should use its own DNS-over-HTTPS
// settings.
MetricsRequestKindFirefox = "firefox"
// MetricsRequestKindApplePrivateRelay is a label for requests for the
// domain name that Apple devices use to check if Apple Private Relay can be
// enabled.
MetricsRequestKindApplePrivateRelay = "apple_private_relay"
)
// Metrics is an interface that is used for collection of statistics for DNS
// requests for special domain names.
type Metrics interface {
// IncrementRequestsTotal increments the total number of DNS requests for
// special domain names of the specified kind. kind must be one of the
// following: [MetricsRequestKindDDR], [MetricsRequestKindBadResolverARPA],
// [MetricsRequestKindChromePrefetch], [MetricsRequestKindFirefox] or
// [MetricsRequestKindApplePrivateRelay].
IncrementRequestsTotal(ctx context.Context, kind string)
}
// EmptyMetrics is the implementation of the [Metrics] interface that does
// nothing.
type EmptyMetrics struct{}
// type check
var _ Metrics = EmptyMetrics{}
// IncrementRequestsTotal implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) IncrementRequestsTotal(_ context.Context, _ string) {}

View File

@@ -6,7 +6,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
@@ -159,7 +158,7 @@ func (mw *Middleware) handleDDR(
) (err error) {
defer func() { err = errors.Annotate(err, "writing ddr resp for %q: %w", ri.Host) }()
metrics.DNSSvcDDRRequestsTotal.Inc()
mw.metrics.IncrementRequestsTotal(ctx, MetricsRequestKindDDR)
if mw.ddr.Enabled {
return rw.WriteMsg(ctx, req, mw.newRespDDR(req, ri))
@@ -178,7 +177,7 @@ func (mw *Middleware) handleDDRNoData(
) (err error) {
defer func() { err = errors.Annotate(err, "writing ddr resp for %q: %w", ri.Host) }()
metrics.DNSSvcDDRRequestsTotal.Inc()
mw.metrics.IncrementRequestsTotal(ctx, MetricsRequestKindDDR)
if mw.ddr.Enabled {
return rw.WriteMsg(ctx, req, ri.Messages.NewRespRCode(req, dns.RcodeSuccess))
@@ -225,7 +224,7 @@ func (mw *Middleware) handleBadResolverARPA(
req *dns.Msg,
ri *agd.RequestInfo,
) (err error) {
metrics.DNSSvcBadResolverARPA.Inc()
mw.metrics.IncrementRequestsTotal(ctx, MetricsRequestKindBadResolverARPA)
resp := ri.Messages.NewRespRCode(req, dns.RcodeSuccess)
err = rw.WriteMsg(ctx, req, resp)
@@ -288,7 +287,7 @@ func (mw *Middleware) handleChromePrefetch(
req *dns.Msg,
ri *agd.RequestInfo,
) (err error) {
metrics.DNSSvcChromePrefetchRequestsTotal.Inc()
mw.metrics.IncrementRequestsTotal(ctx, MetricsRequestKindChromePrefetch)
resp := ri.Messages.NewRespRCode(req, dns.RcodeNameError)
err = rw.WriteMsg(ctx, req, resp)
@@ -314,7 +313,7 @@ func (mw *Middleware) handleFirefoxCanary(
req *dns.Msg,
ri *agd.RequestInfo,
) (err error) {
metrics.DNSSvcFirefoxRequestsTotal.Inc()
mw.metrics.IncrementRequestsTotal(ctx, MetricsRequestKindFirefox)
resp := ri.Messages.NewRespRCode(req, dns.RcodeRefused)
err = rw.WriteMsg(ctx, req, resp)
@@ -340,7 +339,7 @@ func (mw *Middleware) handlePrivateRelay(
req *dns.Msg,
ri *agd.RequestInfo,
) (err error) {
metrics.DNSSvcApplePrivateRelayRequestsTotal.Inc()
mw.metrics.IncrementRequestsTotal(ctx, MetricsRequestKindApplePrivateRelay)
resp := ri.Messages.NewRespRCode(req, dns.RcodeNameError)
err = rw.WriteMsg(ctx, req, resp)

View File

@@ -122,7 +122,8 @@ func TestMiddleware_Wrap_specialDomain(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mw := initial.New(&initial.Config{
Logger: slogutil.NewDiscardLogger(),
Logger: slogutil.NewDiscardLogger(),
Metrics: initial.EmptyMetrics{},
DDR: &initial.DDRConfig{
Enabled: false,
},

View File

@@ -22,6 +22,7 @@ const (
hdrNameCountry = "country"
hdrNameDeviceID = "device-id"
hdrNameHumanID = "human-id"
hdrNameNodeName = "node-name"
hdrNameProfileID = "profile-id"
hdrNameResType = "res-type"
hdrNameRule = "rule"
@@ -65,6 +66,12 @@ func (mw *Middleware) writeDebugResponse(
return fmt.Errorf("adding %s extra: %w", hdrNameServerIP, err)
}
setQuestionName(debugReq, "", hdrNameNodeName)
err = mw.messages.AppendDebugExtra(debugReq, resp, mw.nodeName)
if err != nil {
return fmt.Errorf("adding %s extra: %w", hdrNameNodeName, err)
}
err = mw.appendDebugExtraFromContext(ctx, debugReq, resp)
if err != nil {
// Don't wrap the error, because it's informative enough as is.

View File

@@ -50,20 +50,23 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
})
require.NoError(t, err)
mw := &Middleware{
messages: msgs,
cloner: cloner,
errColl: agdtest.NewErrorCollector(),
}
// TODO(a.garipov): Consider moving to dnssvctest and DRY'ing with
// mainmw_test.
const (
allowRule = "||" + dnssvctest.DomainAllowed + "^"
blockRule = "||" + dnssvctest.DomainBlocked + "^"
rewriteRule = "||" + dnssvctest.DomainRewritten + "^$dnsrewrite=REFUSED"
nodeName = "test-node"
)
mw := &Middleware{
messages: msgs,
cloner: cloner,
errColl: agdtest.NewErrorCollector(),
nodeName: nodeName,
}
clientIPStr := dnssvctest.ClientIP.String()
serverIPStr := dnssvctest.ServerAddr.String()
@@ -87,6 +90,7 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"node-name.adguard-dns.com.", nodeName},
{"resp.res-type.adguard-dns.com.", "normal"},
}),
}, {
@@ -98,6 +102,7 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"node-name.adguard-dns.com.", nodeName},
{"req.res-type.adguard-dns.com.", "blocked"},
{"req.rule.adguard-dns.com.", blockRule},
{"req.rule-list-id.adguard-dns.com.", dnssvctest.FilterListID1Str},
@@ -111,6 +116,7 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"node-name.adguard-dns.com.", nodeName},
{"resp.res-type.adguard-dns.com.", "blocked"},
{"resp.rule.adguard-dns.com.", blockRule},
{"resp.rule-list-id.adguard-dns.com.", dnssvctest.FilterListID2Str},
@@ -126,6 +132,7 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"node-name.adguard-dns.com.", nodeName},
{"req.res-type.adguard-dns.com.", "allowed"},
{"req.rule.adguard-dns.com.", allowRule},
{"req.rule-list-id.adguard-dns.com.", ""},
@@ -141,6 +148,7 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"node-name.adguard-dns.com.", nodeName},
{"resp.res-type.adguard-dns.com.", "allowed"},
{"resp.rule.adguard-dns.com.", allowRule},
{"resp.rule-list-id.adguard-dns.com.", ""},
@@ -161,6 +169,7 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"node-name.adguard-dns.com.", nodeName},
{"req.res-type.adguard-dns.com.", "modified"},
{"req.rule.adguard-dns.com.", rewriteRule},
{"req.rule-list-id.adguard-dns.com.", ""},
@@ -180,6 +189,7 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"node-name.adguard-dns.com.", nodeName},
{"device-id.adguard-dns.com.", dnssvctest.DeviceIDStr},
{"profile-id.adguard-dns.com.", dnssvctest.ProfileIDStr},
{"resp.res-type.adguard-dns.com.", "normal"},
@@ -196,6 +206,7 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"node-name.adguard-dns.com.", nodeName},
{"country.adguard-dns.com.", string(geoip.CountryAD)},
{"asn.adguard-dns.com.", "0"},
{"resp.res-type.adguard-dns.com.", "normal"},
@@ -212,6 +223,7 @@ func TestMiddleware_writeDebugResponse(t *testing.T) {
wantExtra: newTXTExtra([][2]string{
{"client-ip.adguard-dns.com.", clientIPStr},
{"server-ip.adguard-dns.com.", serverIPStr},
{"node-name.adguard-dns.com.", nodeName},
{"country.adguard-dns.com.", string(geoip.CountryAD)},
{"asn.adguard-dns.com.", "0"},
{"subdivision.adguard-dns.com.", "CA"},

View File

@@ -15,10 +15,10 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
"github.com/AdguardTeam/AdGuardDNS/internal/rulestat"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/AdguardTeam/golibs/syncutil"
"github.com/miekg/dns"
)
@@ -38,6 +38,7 @@ type Middleware struct {
metrics Metrics
queryLog querylog.Interface
ruleStat rulestat.Interface
nodeName string
}
// Config is the configuration structure for the main middleware. All fields
@@ -77,6 +78,9 @@ type Config struct {
// RuleStat is used to collect statistics about matched filtering rules and
// rule lists.
RuleStat rulestat.Interface
// NodeName is the name of this server node.
NodeName string
}
// New returns a new main middleware. c must not be nil.
@@ -101,6 +105,7 @@ func New(c *Config) (mw *Middleware) {
metrics: c.Metrics,
queryLog: c.QueryLog,
ruleStat: c.RuleStat,
nodeName: c.NodeName,
}
}

View File

@@ -41,6 +41,8 @@ const (
"^$dnsrewrite=NOERROR;A;" + testRewriteAddrStr
testRuleRewriteCNAME filter.RuleText = "||" + dnssvctest.DomainRewritten +
"^$dnsrewrite=NOERROR;CNAME;" + dnssvctest.DomainRewrittenCNAME
testNodeName = "test-node"
)
// Common variables for tests.
@@ -226,6 +228,7 @@ func TestMiddleware_Wrap(t *testing.T) {
Metrics: mainmw.EmptyMetrics{},
QueryLog: queryLog,
RuleStat: ruleStat,
NodeName: testNodeName,
}
mw := mainmw.New(c)
@@ -705,6 +708,7 @@ func TestMiddleware_Wrap_filtering(t *testing.T) {
Metrics: mainmw.EmptyMetrics{},
QueryLog: queryLog,
RuleStat: ruleStat,
NodeName: testNodeName,
}
mw := mainmw.New(c)

View File

@@ -13,8 +13,8 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/AdGuardDNS/internal/querylog"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
)

View File

@@ -12,8 +12,8 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/miekg/dns"
)

View File

@@ -5,7 +5,7 @@ import (
"net/netip"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/miekg/dns"
)

View File

@@ -7,8 +7,8 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/miekg/dns"
)

View File

@@ -27,10 +27,6 @@ type Metrics interface {
// IncrementRatelimitedByProfile is called when the DNS request is dropped
// by a profile's ratelimit settings.
IncrementRatelimitedByProfile(ctx context.Context)
// IncrementUnknownDedicated is called when the DNS request is sent to an
// unknown local address.
IncrementUnknownDedicated(ctx context.Context)
}
// EmptyMetrics is an empty [Metrics] implementation that does nothing.
@@ -55,10 +51,6 @@ func (EmptyMetrics) IncrementAccessBlockedBySubnet(_ context.Context) {}
// *EmptyMetrics.
func (EmptyMetrics) IncrementRatelimitedByProfile(_ context.Context) {}
// IncrementUnknownDedicated implements the [Metrics] interface for
// *EmptyMetrics.
func (EmptyMetrics) IncrementUnknownDedicated(_ context.Context) {}
// OnAllowlisted implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) OnAllowlisted(_ context.Context, _ *dns.Msg, _ dnsserver.ResponseWriter) {}

View File

@@ -15,8 +15,8 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/syncutil"
@@ -148,7 +148,7 @@ func (mw *Middleware) Wrap(next dnsserver.Handler) (wrapped dnsserver.Handler) {
ri := mw.newRequestInfo(ctx, req, rw.LocalAddr(), raddr)
defer mw.pool.Put(ri)
cont, err := mw.handleDeviceResult(ctx, ri.DeviceResult)
cont, err := mw.handleDeviceResult(ri.DeviceResult)
if !cont {
// Don't wrap the error, because this is the main flow, and there is
// already [errors.Annotate] here.
@@ -196,14 +196,9 @@ func (mw *Middleware) processLocationErr(
// handleDeviceResult processes the device result and indicates whether the
// handler should proceed and the error to return if not.
func (mw *Middleware) handleDeviceResult(
ctx context.Context,
res agd.DeviceResult,
) (cont bool, err error) {
func (mw *Middleware) handleDeviceResult(res agd.DeviceResult) (cont bool, err error) {
switch res := res.(type) {
case *agd.DeviceResultUnknownDedicated:
mw.metrics.IncrementUnknownDedicated(ctx)
// The request is dropped by the profile search. Don't write anything
// and just return.
return false, nil

View File

@@ -11,7 +11,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
)

View File

@@ -1,16 +1,43 @@
package dnssvc
import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/devicefinder"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/initial"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/mainmw"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc/internal/ratelimitmw"
)
// Re-exports related to configuration.
type (
// MainMiddlewareMetrics is a re-export of the internal filtering-middleware
// metrics interface.
// DDRConfig is the configuration for the server group's Discovery Of
// Designated Resolvers (DDR) handlers.
DDRConfig = initial.DDRConfig
)
// Re-exports related to custom domains.
type (
// CustomDomainDB contains information about custom domains and matches domains.
CustomDomainDB = devicefinder.CustomDomainDB
// EmptyCustomDomainDB is an [CustomDomainDB] that does nothing.
EmptyCustomDomainDB = devicefinder.EmptyCustomDomainDB
)
// Re-exports related to metrics.
type (
// DeviceFinderMetrics is an interface for collection of the statistics of
// the default device finder.
DeviceFinderMetrics = devicefinder.Metrics
// InitialMiddlewareMetrics is an interface for monitoring the initial
// middleware state.
InitialMiddlewareMetrics = initial.Metrics
// MainMiddlewareMetrics is an interface for collection of the statistics of
// the main filtering middleware.
MainMiddlewareMetrics = mainmw.Metrics
// RatelimitMiddlewareMetrics is a re-export of the metrics interface of the
// internal access and ratelimiting middleware.
// RatelimitMiddlewareMetrics is an interface for monitoring the ratelimit
// middleware state.
RatelimitMiddlewareMetrics = ratelimitmw.Metrics
)

View File

@@ -9,7 +9,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/mathutil"
"github.com/miekg/dns"
)
@@ -83,7 +82,7 @@ func (mw *Middleware) itemFromCache(
// Check for cache key collisions.
if item.host != cr.host {
optslog.Warn2(ctx, mw.logger, "cache collision", "item", item, "host", cr.host)
mw.logger.WarnContext(ctx, "cache collision", "item", item, "host", cr.host)
return nil, false
}

View File

File diff suppressed because it is too large Load Diff

View File

@@ -13,8 +13,8 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/optslog"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/syncutil"

View File

@@ -22,7 +22,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
)
func Init(l *slog.Logger) {
func Init(l *slog.Logger, reg prometheus.Registerer) (err error) {
expStr := os.Getenv("EXPERIMENTS")
if expStr == "" {
return
@@ -39,16 +39,8 @@ func Init(l *slog.Logger) {
}
}
enableMetrics()
}
// enableMetrics sets the labels with enabled experiments and sets the gauge
// value to 1.
func enableMetrics() {
expGauge := metrics.ExperimentGauge(prometheus.Labels{
return metrics.SetExperimentGauge(reg, prometheus.Labels{
// NOTE: Add experiments here in the following format:
// idMyExp: metrics.BoolString(expMyExpEnabled),
})
expGauge.Set(1)
}

View File

@@ -50,7 +50,12 @@ func (s *Default) refresh(ctx context.Context, acceptStale bool) (err error) {
fls := resp.toInternal(ctx, s.logger, s.errColl)
s.logger.InfoContext(ctx, "validated lists", "num_lists", len(fls))
newRuleLists := s.refreshRuleLists(ctx, fls, acceptStale)
newRuleLists, err := s.refreshRuleLists(ctx, fls, acceptStale)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
s.logger.InfoContext(ctx, "compiled lists", "num_lists", len(newRuleLists))
err = s.refreshServices(ctx, acceptStale)
@@ -111,7 +116,7 @@ func (s *Default) refreshRuleLists(
ctx context.Context,
filtersData []*indexData,
acceptStale bool,
) (rls ruleLists) {
) (rls ruleLists, err error) {
lenFls := len(filtersData)
resCh := make(chan refrResult, lenFls)
@@ -121,23 +126,31 @@ func (s *Default) refreshRuleLists(
rls = make(ruleLists, lenFls)
for range lenFls {
res := <-resCh
fltID := res.id
if res.err != nil {
err := fmt.Errorf("initializing rulelist %q: %w", fltID, res.err)
errcoll.Collect(ctx, s.errColl, s.logger, "rule-list error", err)
s.metrics.SetFilterStatus(ctx, string(fltID), s.clock.Now(), 0, err)
rls[fltID] = s.prevRuleList(fltID)
continue
select {
case <-ctx.Done():
return nil, ctx.Err()
case res := <-resCh:
rls[res.id] = s.resultRuleList(ctx, res)
}
rls[fltID] = res.refr
}
return rls
return rls, nil
}
// resultRuleList returns a non-nil [rulelist.Refreshable] if res.err is nil.
// Otherwise, it returns the previous rule list for the given fltID and logs
// the error.
func (s *Default) resultRuleList(ctx context.Context, res refrResult) (rl *rulelist.Refreshable) {
fltID := res.id
if res.err != nil {
err := fmt.Errorf("initializing rulelist %q: %w", fltID, res.err)
errcoll.Collect(ctx, s.errColl, s.logger, "rule-list error", err)
s.metrics.SetFilterStatus(ctx, string(fltID), s.clock.Now(), 0, err)
return s.prevRuleList(fltID)
}
return res.refr
}
// refreshRuleList creates a [rulelist.Refreshable] from the data loaded with

View File

@@ -19,6 +19,7 @@ const (
GRPCErrAuthentication GRPCError = "auth"
GRPCErrBadRequest GRPCError = "bad_req"
GRPCErrDeviceQuota GRPCError = "dev_quota"
GRPCErrNotFound GRPCError = "not_found"
GRPCErrOther GRPCError = "other"
GRPCErrRateLimit GRPCError = "rate_limit"
GRPCErrTimeout GRPCError = "timeout"
@@ -30,6 +31,7 @@ type BackendGRPC struct {
errorsTotalAuthentication prometheus.Counter
errorsTotalBadRequest prometheus.Counter
errorsTotalDeviceQuota prometheus.Counter
errorsTotalNotFound prometheus.Counter
errorsTotalOther prometheus.Counter
errorsTotalRateLimit prometheus.Counter
errorsTotalTimeout prometheus.Counter
@@ -52,6 +54,7 @@ func NewBackendGRPC(namespace string, reg prometheus.Registerer) (m *BackendGRPC
errorsTotalAuthentication: grpcErrorsTotalCounterVec.WithLabelValues(GRPCErrAuthentication),
errorsTotalBadRequest: grpcErrorsTotalCounterVec.WithLabelValues(GRPCErrBadRequest),
errorsTotalDeviceQuota: grpcErrorsTotalCounterVec.WithLabelValues(GRPCErrDeviceQuota),
errorsTotalNotFound: grpcErrorsTotalCounterVec.WithLabelValues(GRPCErrNotFound),
errorsTotalOther: grpcErrorsTotalCounterVec.WithLabelValues(GRPCErrOther),
errorsTotalRateLimit: grpcErrorsTotalCounterVec.WithLabelValues(GRPCErrRateLimit),
errorsTotalTimeout: grpcErrorsTotalCounterVec.WithLabelValues(GRPCErrTimeout),
@@ -76,6 +79,8 @@ func (m *BackendGRPC) IncrementErrorCount(_ context.Context, errType GRPCError)
ctr = m.errorsTotalBadRequest
case GRPCErrDeviceQuota:
ctr = m.errorsTotalDeviceQuota
case GRPCErrNotFound:
ctr = m.errorsTotalNotFound
case GRPCErrOther:
ctr = m.errorsTotalOther
case GRPCErrRateLimit:

View File

@@ -1,71 +1,162 @@
package metrics
import (
"context"
"fmt"
"net/netip"
"time"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
bindToDeviceUnknownRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "unknown_requests_total",
Namespace: namespace,
Subsystem: subsystemBindToDevice,
Help: "The total number of DNS requests to unknown local addresses.",
},
[]string{"proto"},
// BindToDevice is the Prometheus-based implementation of the
// [bindtodevice.Metrics] interface.
type BindToDevice struct {
// unknownTCPRequestsTotal is the total counter of DNS requests over TCP to
// unknown local addresses.
unknownTCPRequestsTotal prometheus.Counter
// unknownUDPRequestsTotal is the total counter of DNS requests over UDP to
// unknown local addresses.
unknownUDPRequestsTotal prometheus.Counter
// tcpConnsChanSize is a gauge with the current number of TCP connections in
// the buffer of the channel by each subnet.
tcpConnsChanSize *prometheus.GaugeVec
// udpSessionsChanSize is a gauge with the current number of UDP sessions in
// the buffer of the channel by each subnet.
udpSessionsChanSize *prometheus.GaugeVec
// udpWriteRequestsChanSize is a gauge with the current number of UDP write
// requests in the buffer of the channel for each interface listener.
udpWriteRequestsChanSize *prometheus.GaugeVec
// udpWriteDuration is a histogram of durations of UDP write operations.
// This histogram includes only the write itself and does not include
// deadline setting and resetting.
udpWriteDuration *prometheus.HistogramVec
}
// NewBindToDevice registers the bindtodevice-related metrics in reg and returns
// a properly initialized *BindToDevice.
func NewBindToDevice(namespace string, reg prometheus.Registerer) (m *BindToDevice, err error) {
// #nosec G101 -- There are no hardcoded credentials.
const (
unknownRequestsTotal = "unknown_requests_total"
tcpConnsChanSize = "tcp_conns_chan_size"
udpSessionsChanSize = "udp_sessions_chan_size"
udpWriteRequestsChanSize = "udp_write_requests_chan_size"
udpWriteDuration = "udp_write_duration_seconds"
)
// BindToDeviceUnknownTCPRequestsTotal is the total counter of DNS requests
// over TCP to unknown local addresses.
BindToDeviceUnknownTCPRequestsTotal = bindToDeviceUnknownRequestsTotal.With(prometheus.Labels{
"proto": "tcp",
})
// BindToDeviceUnknownUDPRequestsTotal is the total counter of DNS requests
// over UDP to unknown local addresses.
BindToDeviceUnknownUDPRequestsTotal = bindToDeviceUnknownRequestsTotal.With(prometheus.Labels{
"proto": "udp",
})
)
var (
// BindToDeviceTCPConnsChanSize is a gauge with the current number of TCP
// connections in the buffer of the channel by each subnet.
BindToDeviceTCPConnsChanSize = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "tcp_conns_chan_size",
unknownRequestsTotalCV := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: unknownRequestsTotal,
Namespace: namespace,
Subsystem: subsystemBindToDevice,
Help: "The current number of TCP connections in the channel.",
}, []string{"subnet"})
Help: "The total number of DNS requests to unknown local addresses.",
}, []string{"proto"})
// BindToDeviceUDPSessionsChanSize is a gauge with the current number of UDP
// sessions in the buffer of the channel by each subnet.
BindToDeviceUDPSessionsChanSize = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "udp_sessions_chan_size",
Namespace: namespace,
Subsystem: subsystemBindToDevice,
Help: "The current number of UDP sessions in the channel.",
}, []string{"subnet"})
m = &BindToDevice{
unknownTCPRequestsTotal: unknownRequestsTotalCV.WithLabelValues("tcp"),
unknownUDPRequestsTotal: unknownRequestsTotalCV.WithLabelValues("udp"),
tcpConnsChanSize: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: tcpConnsChanSize,
Namespace: namespace,
Subsystem: subsystemBindToDevice,
Help: "The current number of TCP connections in the channel.",
}, []string{"subnet"}),
udpSessionsChanSize: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: udpSessionsChanSize,
Namespace: namespace,
Subsystem: subsystemBindToDevice,
Help: "The current number of UDP sessions in the channel.",
}, []string{"subnet"}),
udpWriteRequestsChanSize: prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: udpWriteRequestsChanSize,
Namespace: namespace,
Subsystem: subsystemBindToDevice,
Help: "The current number of UDP write requests in the channel.",
}, []string{"name"}),
udpWriteDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: udpWriteDuration,
Namespace: namespace,
Subsystem: subsystemBindToDevice,
Help: "The duration of a write to a UDP socket.",
Buckets: []float64{0.001, 0.01, 0.1, 1},
}, []string{"name"}),
}
// BindToDeviceUDPWriteRequestsChanSize is a gauge with the current number
// of UDP write requests in the buffer of the channel for each interface
// listener.
BindToDeviceUDPWriteRequestsChanSize = promauto.NewGaugeVec(prometheus.GaugeOpts{
Name: "udp_write_requests_chan_size",
Namespace: namespace,
Subsystem: subsystemBindToDevice,
Help: "The current number of UDP write requests in the channel.",
}, []string{"name"})
var errs []error
collectors := container.KeyValues[string, prometheus.Collector]{{
Key: unknownRequestsTotal,
Value: unknownRequestsTotalCV,
}, {
Key: tcpConnsChanSize,
Value: m.tcpConnsChanSize,
}, {
Key: udpSessionsChanSize,
Value: m.udpSessionsChanSize,
}, {
Key: udpWriteRequestsChanSize,
Value: m.udpWriteRequestsChanSize,
}, {
Key: udpWriteDuration,
Value: m.udpWriteDuration,
}}
// BindToDeviceUDPWriteDurationSeconds is a histogram of durations of UDP
// write operations. This histogram includes only the write itself and does
// not include deadline setting and resetting.
BindToDeviceUDPWriteDurationSeconds = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "udp_write_duration_seconds",
Namespace: namespace,
Subsystem: subsystemBindToDevice,
Help: "The duration of a write to a UDP socket.",
Buckets: []float64{0.001, 0.01, 0.1, 1},
}, []string{"name"})
)
for _, c := range collectors {
err = reg.Register(c.Value)
if err != nil {
errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err))
}
}
if err = errors.Join(errs...); err != nil {
return nil, err
}
return m, nil
}
// IncrementUnknownTCPRequests implements the [bindtodevice.Metrics] interface
// for *BindToDevice.
func (m *BindToDevice) IncrementUnknownTCPRequests(context.Context) {
m.unknownTCPRequestsTotal.Inc()
}
// IncrementUnknownUDPRequests implements the [bindtodevice.Metrics] interface
// for *BindToDevice.
func (m *BindToDevice) IncrementUnknownUDPRequests(context.Context) {
m.unknownUDPRequestsTotal.Inc()
}
// SetTCPConnsChanSize implements the [bindtodevice.Metrics] interface for
// *BindToDevice.
func (m *BindToDevice) SetTCPConnsChanSize(_ context.Context, subnet netip.Prefix, n uint) {
m.tcpConnsChanSize.WithLabelValues(subnet.String()).Set(float64(n))
}
// SetUDPSessionsChanSize implements the [bindtodevice.Metrics] interface for
// *BindToDevice.
func (m *BindToDevice) SetUDPSessionsChanSize(_ context.Context, subnet netip.Prefix, n uint) {
m.udpSessionsChanSize.WithLabelValues(subnet.String()).Set(float64(n))
}
// SetUDPWriteRequestsChanSize implements the [bindtodevice.Metrics] interface
// for *BindToDevice.
func (m *BindToDevice) SetUDPWriteRequestsChanSize(_ context.Context, ifaceName string, n uint) {
m.udpWriteRequestsChanSize.WithLabelValues(ifaceName).Set(float64(n))
}
// ObserveUDPWriteDuration implements the [bindtodevice.Metrics] interface for
// *BindToDevice.
func (m *BindToDevice) ObserveUDPWriteDuration(
_ context.Context,
ifaceName string,
dur time.Duration,
) {
m.udpWriteDuration.WithLabelValues(ifaceName).Observe(float64(dur.Seconds()))
}

View File

@@ -12,8 +12,6 @@ import (
// BackendCustomDomainStorage is the Prometheus-based implementation of the
// [backendpb.CustomDomainStorageMetrics] interface.
//
// TODO(a.garipov): Use.
type BackendCustomDomainStorage struct {
// errorsTotal is a counter of the total number of errors when requesting
// certificate data.
@@ -91,7 +89,11 @@ func NewBackendCustomDomainStorage(
// ObserveRequest implements the [backendpb.CustomDomainStorageMetrics]
// interface for *BackendCustomDomainStorage.
func (m *BackendCustomDomainStorage) ObserveRequest(_ context.Context, dur time.Duration, err error) {
func (m *BackendCustomDomainStorage) ObserveRequest(
_ context.Context,
dur time.Duration,
err error,
) {
m.requestsTotal.Inc()
if err == nil {
m.requestDuration.Observe(float64(dur))

View File

@@ -0,0 +1,117 @@
package metrics
import (
"context"
"fmt"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/prometheus/client_golang/prometheus"
)
// DeviceFinder is the Prometheus-based implementation of the
// [dnssvc.DeviceFinderMetrics] interface.
type DeviceFinder struct {
customDomainMismatchesTotal *prometheus.CounterVec
customDomainRequestsTotal *prometheus.CounterVec
dohAuthenticationFails prometheus.Counter
unknownDedicatedTotal prometheus.Counter
}
// IncrementCustomDomainMismatches implements the [Metrics] interface for
// m *DeviceFinder.
func (m *DeviceFinder) IncrementCustomDomainMismatches(_ context.Context, domain string) {
m.customDomainMismatchesTotal.WithLabelValues(domain).Inc()
}
// IncrementCustomDomainRequests implements the [Metrics] interface for
// m *DeviceFinder.
func (m *DeviceFinder) IncrementCustomDomainRequests(_ context.Context, domain string) {
m.customDomainRequestsTotal.WithLabelValues(domain).Inc()
}
// IncrementDoHAuthenticationFails implements the [Metrics] interface for
// m *DeviceFinder.
func (m *DeviceFinder) IncrementDoHAuthenticationFails(_ context.Context) {
m.dohAuthenticationFails.Inc()
}
// IncrementUnknownDedicated implements the [Metrics] interface for
// m *DeviceFinder.
func (m *DeviceFinder) IncrementUnknownDedicated(_ context.Context) {
m.unknownDedicatedTotal.Inc()
}
// NewDeviceFinder registers the device-finder metrics in reg and returns a
// properly initialized *DeviceFinder. All arguments must be set.
func NewDeviceFinder(namespace string, reg prometheus.Registerer) (m *DeviceFinder, err error) {
const (
customDomainMismatchesTotal = "custom_domain_matches_total"
customDomainRequestsTotal = "custom_domain_requests_total"
dohAuthenticationFails = "doh_authentication_fails"
// TODO(a.garipov): Consider renaming to "unknown_dedicated_total".
unknownDedicatedTotal = "unknown_dedicated"
)
// TODO(a.garipov): Consider creating a new subsystem.
m = &DeviceFinder{
customDomainMismatchesTotal: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: customDomainMismatchesTotal,
Namespace: namespace,
Subsystem: subsystemDNSSvc,
Help: "The number of requests from devices that do not belong to the profile " +
"which the custom domain belongs to.",
}, []string{"domain"}),
customDomainRequestsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: customDomainRequestsTotal,
Namespace: namespace,
Subsystem: subsystemDNSSvc,
Help: "The total number requests recognized as being to a custom domain " +
"belonging to a profile.",
}, []string{"domain"}),
dohAuthenticationFails: prometheus.NewCounter(prometheus.CounterOpts{
Name: dohAuthenticationFails,
Namespace: namespace,
Subsystem: subsystemDNSSvc,
Help: "The number of authentication failures for DoH auth.",
}),
unknownDedicatedTotal: prometheus.NewCounter(prometheus.CounterOpts{
Name: unknownDedicatedTotal,
Namespace: namespace,
Subsystem: subsystemDNSSvc,
Help: "The number of dropped queries for unrecognized dedicated addresses.",
}),
}
var errs []error
collectors := container.KeyValues[string, prometheus.Collector]{{
Key: customDomainMismatchesTotal,
Value: m.customDomainMismatchesTotal,
}, {
Key: customDomainRequestsTotal,
Value: m.customDomainRequestsTotal,
}, {
Key: dohAuthenticationFails,
Value: m.dohAuthenticationFails,
}, {
Key: unknownDedicatedTotal,
Value: m.unknownDedicatedTotal,
}}
for _, c := range collectors {
err = reg.Register(c.Value)
if err != nil {
errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err))
}
}
if err = errors.Join(errs...); err != nil {
return nil, err
}
return m, nil
}

View File

@@ -1,57 +1,62 @@
package metrics
import (
"context"
"fmt"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
specialRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "special_requests_total",
Namespace: namespace,
Subsystem: subsystemDNSSvc,
Help: "The number of DNS requests for special domain names.",
}, []string{"kind"})
// InitialMiddleware is the Prometheus-based implementation of the
// [dnssvc.InitialMiddlewareMetrics] interface.
type InitialMiddleware struct {
specialRequestsTotal *prometheus.CounterVec
}
// DNSSvcDDRRequestsTotal is a counter with total number of requests for
// Discovery of Designated Resolvers.
DNSSvcDDRRequestsTotal = specialRequestsTotal.With(prometheus.Labels{
"kind": "ddr",
})
// NewInitialMiddleware registers the filtering-middleware metrics in reg and
// returns a properly initialized *InitialMiddleware. All arguments must be
// set.
func NewInitialMiddleware(
namespace string,
reg prometheus.Registerer,
) (m *InitialMiddleware, err error) {
const (
specialRequestsTotal = "special_requests_total"
)
// DNSSvcBadResolverARPA is a counter with total number of requests for
// malformed resolver.arpa queries.
DNSSvcBadResolverARPA = specialRequestsTotal.With(prometheus.Labels{
"kind": "bad_resolver_arpa",
})
m = &InitialMiddleware{
specialRequestsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{
Name: specialRequestsTotal,
Namespace: namespace,
Subsystem: subsystemDNSSvc,
Help: "The number of DNS requests for special domain names.",
}, []string{"kind"}),
}
// DNSSvcChromePrefetchRequestsTotal is a counter with total number of
// requests for the domain name that Chrome uses to check if it should use
// its prefetch proxy.
DNSSvcChromePrefetchRequestsTotal = specialRequestsTotal.With(prometheus.Labels{
"kind": "chrome_prefetch",
})
var errs []error
collectors := container.KeyValues[string, prometheus.Collector]{{
Key: specialRequestsTotal,
Value: m.specialRequestsTotal,
}}
// DNSSvcFirefoxRequestsTotal is a counter with total number of requests for
// the domain name that Firefox uses to check if it should use its own
// DNS-over-HTTPS settings.
DNSSvcFirefoxRequestsTotal = specialRequestsTotal.With(prometheus.Labels{
"kind": "firefox",
})
for _, c := range collectors {
err = reg.Register(c.Value)
if err != nil {
errs = append(errs, fmt.Errorf("registering metrics %q: %w", c.Key, err))
}
}
// DNSSvcApplePrivateRelayRequestsTotal is a counter with total number of
// requests for the domain name that Apple devices use to check if Apple
// Private Relay can be enabled.
DNSSvcApplePrivateRelayRequestsTotal = specialRequestsTotal.With(prometheus.Labels{
"kind": "apple_private_relay",
})
if err = errors.Join(errs...); err != nil {
return nil, err
}
// DNSSvcDoHAuthFailsTotal is the counter of DoH basic authentication
// failures.
DNSSvcDoHAuthFailsTotal = promauto.NewCounter(prometheus.CounterOpts{
Name: "doh_authentication_fails",
Namespace: namespace,
Subsystem: subsystemDNSSvc,
Help: "The number of authentication failures for DoH auth.",
})
)
return m, nil
}
// IncrementRequestsTotal implements the [Metrics] interface for
// *InitialMiddleware.
func (m *InitialMiddleware) IncrementRequestsTotal(_ context.Context, kind string) {
m.specialRequestsTotal.WithLabelValues(kind).Inc()
}

View File

@@ -13,14 +13,6 @@ import (
"github.com/prometheus/client_golang/prometheus"
)
// MainMiddleware is an interface for collection of the statistics of the main
// filtering middleware.
//
// NOTE: Keep in sync with [dnssvc.MainMiddleware].
type MainMiddleware interface {
OnRequest(ctx context.Context, m *MainMiddlewareRequestMetrics)
}
// MainMiddlewareRequestMetrics is an alias for a structure that contains the
// information about a request that has reached the filtering middleware.
//
@@ -36,9 +28,9 @@ type MainMiddlewareRequestMetrics = struct {
IsBlocked bool
}
// DefaultMainMiddleware is the Prometheus-based implementation of the
// [MainMiddleware] interface.
type DefaultMainMiddleware struct {
// MainMiddleware is the Prometheus-based implementation of the
// [dnssvc.MainMiddleware] interface.
type MainMiddleware struct {
// filteringDuration is a histogram with the durations of actually filtering
// (e.g. applying filters, safebrowsing, etc) to queries.
filteringDuration prometheus.Histogram
@@ -63,14 +55,13 @@ type DefaultMainMiddleware struct {
userCounter *UserCounter
}
// NewDefaultMainMiddleware registers the filtering-middleware metrics in reg
// and returns a properly initialized *DefaultMainMiddleware. All arguments
// must be set.
func NewDefaultMainMiddleware(
// NewMainMiddleware registers the filtering-middleware metrics in reg and
// returns a properly initialized *MainMiddleware. All arguments must be set.
func NewMainMiddleware(
logger *slog.Logger,
namespace string,
reg prometheus.Registerer,
) (m *DefaultMainMiddleware, err error) {
) (m *MainMiddleware, err error) {
const (
filteringDuration = "filtering_duration_seconds"
requestPerASNTotal = "request_per_asn_total"
@@ -80,7 +71,7 @@ func NewDefaultMainMiddleware(
usersLastHourCount = "users_last_hour_count"
)
m = &DefaultMainMiddleware{
m = &MainMiddleware{
filteringDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "filtering_duration_seconds",
Namespace: namespace,
@@ -184,8 +175,8 @@ func NewDefaultMainMiddleware(
return m, nil
}
// OnRequest implements the [Metrics] interface for *DefaultMainMiddleware.
func (m *DefaultMainMiddleware) OnRequest(ctx context.Context, rm *MainMiddlewareRequestMetrics) {
// OnRequest implements the [Metrics] interface for *MainMiddleware.
func (m *MainMiddleware) OnRequest(ctx context.Context, rm *MainMiddlewareRequestMetrics) {
m.filteringDuration.Observe(rm.FilteringDuration.Seconds())
asnStr := strconv.FormatUint(uint64(rm.ASN), 10)

Some files were not shown because too many files have changed in this diff Show More