Sync v2.12.0

This commit is contained in:
Andrey Meshkov
2025-03-07 11:05:38 +03:00
parent f1791135af
commit 1ff64f80f5
265 changed files with 9158 additions and 8328 deletions

View File

@@ -2,14 +2,22 @@
All notable environment, configuration file, and other changes to this project will be documented in this file.
The format is **not** based on [Keep a Changelog][kec], since the project **doesn't** currently adhere to [Semantic Versioning][sem].
The format is **not** based on [Keep a Changelog][kec], since the project **doesnt** currently adhere to [Semantic Versioning][sem].
[kec]: https://keepachangelog.com/en/1.0.0/
[sem]: https://semver.org/spec/v2.0.0.html
## AGDNS-2360 / Build 969
- The environment variable `LOG_FORMAT` has been added.
## AGDNS-1519 / Build 944
- Profiles file cache version was incremented.
## AGDNS-2507 / Build 926
- Profile's file cache version was incremented. The file cache structure has been optimized, so messages like the following are to be expected:
- Profiles file cache version was incremented. The file cache structure has been optimized, so messages like the following are to be expected:
```none
profiledb: warning: error loading fs cache err="decoding protobuf: proto: cannot parse invalid wire-format data"
@@ -17,7 +25,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
## AGDNS-2327 / Build 916
- Profile's file cache version was incremented. The new field `BlockChromePrefetch` has been added to profile's object.
- Profiles file cache version was incremented. The new field `BlockChromePrefetch` has been added to profiles object.
- The objects within the `filtering_groups` have a new property, `block_chrome_prefetch`. So replace this:
@@ -164,7 +172,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
## AGDNS-2331 / Build 818
- Profile's file cache version was incremented. The new field `RateLimit` has been added to profile's object.
- Profiles file cache version was incremented. The new field `RateLimit` has been added to profiles object.
## AGDNS-2008 / Build 809
@@ -238,7 +246,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
- The objects within the `server_groups` array had a change in their `block_page_redirect` configuration, it now supports arrays of IP addresses in `ipv4` and `ipv6` fields.
- Profile's file cache version was incremented. In case of `BlockingModeCustomIP` the `profile.blocking_mode` IPv4/IPv6 fields are now arrays of IP addresses.
- Profiles file cache version was incremented. In case of `BlockingModeCustomIP` the `profile.blocking_mode` IPv4/IPv6 fields are now arrays of IP addresses.
## AGDNS-2012 / Build 732
@@ -246,7 +254,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
## AGDNS-1879 / Build 729
- Profile's file cache version was incremented. The new field `authentication` has been added to profile's device object.
- Profiles file cache version was incremented. The new field `authentication` has been added to profiles device object.
## AGDNS-1934 / Build 728
@@ -333,7 +341,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
## AGDNS-1761 / Build 702
- The property `upstream` has been modified. Its property `timeout` has been replaced with the new property `servers.timeout` for each server in the `servers` list. Concomitantly the `fallback.timeout` has been replaced with `fallback.servers.timeout` for each fallback server. The `fallback.servers` now supports not only the addresses of the servers, but URLs in the `[scheme://]ip:port` format like it's done with the main servers. So replace this:
- The property `upstream` has been modified. Its property `timeout` has been replaced with the new property `servers.timeout` for each server in the `servers` list. Concomitantly the `fallback.timeout` has been replaced with `fallback.servers.timeout` for each fallback server. The `fallback.servers` now supports not only the addresses of the servers, but URLs in the `[scheme://]ip:port` format like its done with the main servers. So replace this:
```yaml
upstream:
@@ -494,7 +502,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
## AGDNS-1684 / Build 661
- Profile's file cache version was incremented. The new field `access` has been added.
- Profiles file cache version was incremented. The new field `access` has been added.
## AGDNS-1664 / Build 636
@@ -665,7 +673,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
## AGDNS-1556 / Build 547
- The object `cache` has a new property `ttl_override`. It describes the TTL override settings, such as the minimum TTL for cache items and the `enabled` switch. It overwrites the TTL from DNS response in case it's less than this minimum value. So replace this:
- The object `cache` has a new property `ttl_override`. It describes the TTL override settings, such as the minimum TTL for cache items and the `enabled` switch. It overwrites the TTL from DNS response in case its less than this minimum value. So replace this:
```yaml
cache:
@@ -1137,7 +1145,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
# …
```
Adjust the values, if necessary. Make sure to synchronize and keep in sync the addresses and ports with the values of the server groups' servers.
Adjust the values, if necessary. Make sure to synchronize and keep in sync the addresses and ports with the values of the server groups servers.
## AGDNS-624 / Build 317
@@ -1334,7 +1342,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
## AGDNS-344 / Build 226
- The environment variables `CONSUL_DNSCHECK_KV_URL` and `CONSUL_DNSCHECK_SESSION_URL` are now unset by default. Which means that by default HTTP key-value database isn't used.
- The environment variables `CONSUL_DNSCHECK_KV_URL` and `CONSUL_DNSCHECK_SESSION_URL` are now unset by default. Which means that by default HTTP key-value database isnt used.
## AGDNS-431 / Build 211
@@ -1455,7 +1463,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
## AGDNS-305 / Build 114
- The new required environment variable `BLOCKED_SERVICE_INDEX_URL` has been added. It has no default value, so it's necessary to set it.
- The new required environment variable `BLOCKED_SERVICE_INDEX_URL` has been added. It has no default value, so its necessary to set it.
## AGDNS-319 / Build 113
@@ -1479,7 +1487,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
- The environment variable `CONSUL_URL` has been renamed to `CONSUL_ALLOWLIST_URL`.
- The new required environment variables `CONSUL_DNSCHECK_KV_URL` and `CONSUL_DNSCHECK_SESSION_URL` are added. They have no default value, so it's necessary to set them.
- The new required environment variables `CONSUL_DNSCHECK_KV_URL` and `CONSUL_DNSCHECK_SESSION_URL` are added. They have no default value, so its necessary to set them.
- The object `check` has a new property, `ttl`. Set it to a human-readable duration, for example `1m`.
@@ -1499,7 +1507,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
The default value is `1`, adjust the value, if necessary.
- The environment variable `VERBOSE` doesn't support a set but empty value. Unset the value or replace it with a `0`.
- The environment variable `VERBOSE` doesnt support a set but empty value. Unset the value or replace it with a `0`.
## AGDNS-295 / Build 105
@@ -1702,7 +1710,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
## AGDNS-139 / Build 73
- The new required environment variable `CONSUL_URL` has been added. It has no default value, so it's necessary to set it.
- The new required environment variable `CONSUL_URL` has been added. It has no default value, so its necessary to set it.
- The ratelimit configuration for a server has changed from this:
@@ -1741,7 +1749,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
## AGDNS-230 / Build 67
- The new required environment variable `FILTER_INDEX_URL` has been added. It has no default value, so it's necessary to set it.
- The new required environment variable `FILTER_INDEX_URL` has been added. It has no default value, so its necessary to set it.
- The environment variable `BACKEND_ENDPOINT` is now required and has no default value.
@@ -1951,7 +1959,7 @@ The format is **not** based on [Keep a Changelog][kec], since the project **does
key: /test/cert.key
```
The domains to be used in device ID detection are now expected to be contained in the certificate's DNS Names section of SAN.
The domains to be used in device ID detection are now expected to be contained in the certificates DNS Names section of SAN.
## AGDNS-167 / Build 39

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

View File

@@ -55,7 +55,6 @@ Supported IDs:
- `dns/ecscache_no_ecs`
- `dns/ecscache_with_ecs`
- `filters/blocked_service/*`
- `filters/custom`
- `filters/hashprefix/adult_blocking`
- `filters/hashprefix/newly_registered_domains`
- `filters/hashprefix/safe_browsing`

View File

@@ -27,6 +27,7 @@ AdGuard DNS uses [environment variables][wiki-env] to store some of the more sen
- [`LINKED_IP_TARGET_URL`](#LINKED_IP_TARGET_URL)
- [`LISTEN_ADDR`](#LISTEN_ADDR)
- [`LISTEN_PORT`](#LISTEN_PORT)
- [`LOG_FORMAT`](#LOG_FORMAT)
- [`LOG_TIMESTAMP`](#LOG_TIMESTAMP)
- [`METRICS_NAMESPACE`](#METRICS_NAMESPACE)
- [`NEW_REG_DOMAINS_ENABLED`](#NEW_REG_DOMAINS_ENABLED)
@@ -224,6 +225,18 @@ The port on which to bind the [debug HTTP API][debughttp], which includes the he
**Default:** `8181`.
## <a href="#LOG_FORMAT" id="LOG_FORMAT" name="LOG_FORMAT">`LOG_FORMAT`</a>
The format for the server logs:
- `text`: Structured text format, it is the default value.
- `default`: Simple and human-readable plain-text format.
- `json`: JSON format.
- `jsonhybrid`: JSON with a schema consisting of `level`, `msg`, and `time` properties.
## <a href="#LOG_TIMESTAMP" id="LOG_TIMESTAMP" name="LOG_TIMESTAMP">`LOG_TIMESTAMP`</a>
If `1`, show timestamps in the plain text logs. If `0`, don't show the timestamps.

51
go.mod
View File

@@ -1,34 +1,34 @@
module github.com/AdguardTeam/AdGuardDNS
go 1.23.4
go 1.23.6
require (
github.com/AdguardTeam/AdGuardDNS/internal/dnsserver v0.0.0-20240607112746-5690301129fe
github.com/AdguardTeam/golibs v0.30.4
github.com/AdguardTeam/golibs v0.32.1
github.com/AdguardTeam/urlfilter v0.20.0
github.com/ameshkov/dnscrypt/v2 v2.3.0
github.com/axiomhq/hyperloglog v0.2.0
github.com/axiomhq/hyperloglog v0.2.3
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.29.1
github.com/getsentry/sentry-go v0.31.1
github.com/gomodule/redigo v1.9.2
github.com/google/renameio/v2 v2.0.0
github.com/miekg/dns v1.1.62
github.com/miekg/dns v1.1.63
github.com/oschwald/maxminddb-golang v1.13.1
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
github.com/prometheus/client_golang v1.20.5
github.com/prometheus/client_model v0.6.1
github.com/prometheus/common v0.60.1
github.com/quic-go/quic-go v0.48.2
github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.30.0
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
golang.org/x/net v0.32.0
golang.org/x/sys v0.28.0
golang.org/x/time v0.8.0
google.golang.org/grpc v1.68.0
google.golang.org/protobuf v1.35.1
github.com/prometheus/common v0.62.0
github.com/quic-go/quic-go v0.49.0
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.32.0
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3
golang.org/x/net v0.34.0
golang.org/x/sys v0.30.0
golang.org/x/time v0.10.0
google.golang.org/grpc v1.70.0
google.golang.org/protobuf v1.36.5
gopkg.in/yaml.v2 v2.4.0
)
@@ -39,22 +39,25 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/pprof v0.0.0-20241203143554-1e3fdc7de467 // indirect
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 // indirect
github.com/kamstrup/intmap v0.5.1 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo/v2 v2.22.0 // indirect
github.com/panjf2000/ants/v2 v2.10.0 // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/panjf2000/ants/v2 v2.11.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.28.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.29.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

128
go.sum
View File

@@ -1,5 +1,5 @@
github.com/AdguardTeam/golibs v0.30.4 h1:zfFX1v4hkOCz6BifkneiBW2PCwSK523kYNr+VwaFrIw=
github.com/AdguardTeam/golibs v0.30.4/go.mod h1:Ir9dlHfb8nRQsG3Qgo1zoGL+k1qMbcBtb8tcnsvzdAE=
github.com/AdguardTeam/golibs v0.32.1 h1:Ajf6Q0k+A9zjFbj8HOzNAbHImrV4JtbT0vwy02D6VeI=
github.com/AdguardTeam/golibs v0.32.1/go.mod h1:dXRLSsnJQDxOfQVl0ochy1bfk4NgnJQGQdR1YPJdwcw=
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs=
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
@@ -10,8 +10,8 @@ github.com/ameshkov/dnscrypt/v2 v2.3.0 h1:pDXDF7eFa6Lw+04C0hoMh8kCAQM8NwUdFEllSP
github.com/ameshkov/dnscrypt/v2 v2.3.0/go.mod h1:N5hDwgx2cNb4Ay7AhvOSKst+eUiOZ/vbKRO9qMpQttE=
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
github.com/axiomhq/hyperloglog v0.2.0 h1:u1XT3yyY1rjzlWuP6NQIrV4bRYHOaqZaovqjcBEvZJo=
github.com/axiomhq/hyperloglog v0.2.0/go.mod h1:GcgMjz9gaDKZ3G0UMS6Fq/VkZ4l7uGgcJyxA7M+omIM=
github.com/axiomhq/hyperloglog v0.2.3 h1:2ZGwz3FGcx77e9/aNjqJijsGhH6RZOlglzxnDpVBCQY=
github.com/axiomhq/hyperloglog v0.2.3/go.mod h1:DLUK9yIzpU5B6YFLjxTIcbHu1g4Y1WQb1m5RH3radaM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
@@ -22,17 +22,19 @@ github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg
github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/getsentry/sentry-go v0.29.1 h1:DyZuChN8Hz3ARxGVV8ePaNXh1dQ7d76AiB117xcREwA=
github.com/getsentry/sentry-go v0.29.1/go.mod h1:x3AtIzN01d6SiWkderzaH28Tm0lgkafpJ5Bm3li39O0=
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/getsentry/sentry-go v0.31.1 h1:ELVc0h7gwyhnXHDouXkhqTFSO5oslsRDk0++eyE0KJ4=
github.com/getsentry/sentry-go v0.31.1/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
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.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
@@ -43,10 +45,14 @@ github.com/gomodule/redigo v1.9.2 h1:HrutZBLhSIU8abiSfW8pj8mPhOyMYjZT/wcA4/L9L9s
github.com/gomodule/redigo v1.9.2/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20241203143554-1e3fdc7de467 h1:keEZFtbLJugfE0qHn+Ge1JCE71spzkchQobDf3mzS/4=
github.com/google/pprof v0.0.0-20241203143554-1e3fdc7de467/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 h1:wlQI2cYY0BsWmmPPAnxfQ8SDW0S3Jasn+4B8kXFxprg=
github.com/google/pprof v0.0.0-20250202011525-fc3143867406/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/kamstrup/intmap v0.5.1 h1:ENGAowczZA+PJPYYlreoqJvWgQVtAmX1l899WfYFVK0=
github.com/kamstrup/intmap v0.5.1/go.mod h1:gWUVWHKzWj8xpJVFf5GC0O26bWmv3GqdnIX/LMT6Aq4=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -57,18 +63,18 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8=
github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
github.com/panjf2000/ants/v2 v2.11.0 h1:sHrqEwTBQTQ2w6PMvbMfvBtVUuhsaYPzUmAYDLYmJPg=
github.com/panjf2000/ants/v2 v2.11.0/go.mod h1:V9HhTupTWxcaRmIglJvGwvzqXUTnIZW9uO6q4hAfApw=
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo=
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
@@ -83,66 +89,74 @@ github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
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.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489 h1:5bKytslY8ViY0Cj/ewmRtrWHW64bNF03cAatUUFCdFI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,4 +1,4 @@
go 1.23.4
go 1.23.6
use (
.

View File

@@ -2,6 +2,8 @@ cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w=
cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8=
cel.dev/expr v0.19.0 h1:lXuo+nDhpyJSpWxpPVi5cPUwzKb+dsdOiw6IreM5yt0=
cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
@@ -48,6 +50,8 @@ cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2Aawl
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
cloud.google.com/go/contactcenterinsights v1.13.0/go.mod h1:ieq5d5EtHsu8vhe2y3amtZ+BE+AQwX5qAy7cpo0POsI=
cloud.google.com/go/container v1.31.0/go.mod h1:7yABn5s3Iv3lmw7oMmyGbeV6tQj86njcTijkkGuvdZA=
cloud.google.com/go/containeranalysis v0.11.4/go.mod h1:cVZT7rXYBS9NG1rhQbWL9pWbXCKHWJPYraE8/FTSYPE=
@@ -163,6 +167,9 @@ github.com/AdguardTeam/golibs v0.30.0/go.mod h1:vjw1OVZG6BYyoqGRY88U4LCJLOMfhBFh
github.com/AdguardTeam/golibs v0.30.1 h1:/yv7dq2h7WXw/jTDxkE3FP9zHerRT+i03PZRHJX4fPU=
github.com/AdguardTeam/golibs v0.30.1/go.mod h1:FkwcNQEJoGsgDGXcalrVa/4gWbE68KsmE2guXWtBQUE=
github.com/AdguardTeam/golibs v0.30.3/go.mod h1:Ir9dlHfb8nRQsG3Qgo1zoGL+k1qMbcBtb8tcnsvzdAE=
github.com/AdguardTeam/golibs v0.30.6-0.20241204165356-565456b436b4/go.mod h1:wIkZ9o2UnppeW6/YD7yJB71dYbMhiuC1Fh/I2ElW7GQ=
github.com/AdguardTeam/golibs v0.31.2 h1:UMeyMlJoLVCtPpOJcTuxk6RXANXbRJyaWKsF0YaJpQk=
github.com/AdguardTeam/golibs v0.31.2/go.mod h1:DzCfc0HFaaKv7sV17gZnSMUiHRtUJ1ypX82VlXUWI6M=
github.com/AdguardTeam/gomitmproxy v0.2.0 h1:rvCOf17pd1/CnMyMQW891zrEiIQBpQ8cIGjKN9pinUU=
github.com/AdguardTeam/gomitmproxy v0.2.1 h1:p9gr8Er1TYvf+7ic81Ax1sZ62UNCsMTZNbm7tC59S9o=
github.com/AdguardTeam/gomitmproxy v0.2.1/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
@@ -179,6 +186,8 @@ github.com/CloudyKit/jet/v6 v6.1.0 h1:hvO96X345XagdH1fAoBjpBYG4a1ghhL/QzalkduPuX
github.com/CloudyKit/jet/v6 v6.1.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk=
github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
@@ -210,6 +219,8 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625 h1:ckJgFhFWywOx+
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
@@ -253,6 +264,7 @@ github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido6
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI=
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
@@ -272,12 +284,15 @@ github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQ
github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI=
github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8=
github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE=
github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
@@ -314,7 +329,6 @@ github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
@@ -350,6 +364,8 @@ github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwm
github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/glog v1.2.3 h1:oDTdz9f5VGVVNGu/Q7UXKWYsD0873HXLHdJUNBsSEKM=
github.com/golang/glog v1.2.3/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -410,8 +426,6 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
@@ -429,6 +443,8 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb
github.com/grpc-ecosystem/grpc-gateway v1.5.0 h1:WcmKMm43DR7RdtlkEXQJyo5ws8iTp98CyhCCbOHMvNI=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
@@ -591,6 +607,7 @@ github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwb
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
@@ -740,13 +757,25 @@ go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8=
go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0 h1:jBpDk4HAUsrnVO1FsfCfCOTEc/MkInJmvfCHYLFiT80=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0/go.mod h1:H9LUIM1daaeZaz91vZcfeM0fejXPmgCYE8ZhzqfJuiU=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go4.org v0.0.0-20180809161055-417644f6feb5 h1:+hE86LblG4AyDgwMCLTE6FOlM9+qjHSYS+rKqxUVdsM=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
@@ -769,6 +798,7 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20221019170559-20944726eadf/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
@@ -837,6 +867,7 @@ golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -857,6 +888,8 @@ golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852 h1:xYq6+9AtI+xP3M4r0N1hCkHrInHDBohhquRgx9Kk6gI=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -949,6 +982,8 @@ golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -1046,6 +1081,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
google.golang.org/genproto/googleapis/api v0.0.0-20250122153221-138b5a5a4fd4 h1://y4MHaM7tNLqTeWKyfBIeoAMxwKwRm/nODb5IKA3BE=
google.golang.org/genproto/googleapis/api v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:AfA77qWLcidQWywD0YgqfpJzf50w2VjzBml3TybHeJU=
google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=

View File

@@ -15,6 +15,8 @@ import (
)
// blockedHostEngine is a lazy blocklist rules engine.
//
// TODO(a.garipov): Replace/merge with [custom.Filter].
type blockedHostEngine struct {
lazyEngine *urlfilter.DNSEngine
initOnce *sync.Once

View File

@@ -1,41 +1,2 @@
// Package agd contains common entities and interfaces of AdGuard DNS.
package agd
import (
"fmt"
)
// firstNonIDRune returns the first non-printable or non-ASCII rune and its
// index. If slashes is true, it also looks for slashes. If there are no such
// runes, i is -1.
func firstNonIDRune(s string, slashes bool) (i int, r rune) {
for i, r = range s {
if r < '!' || r > '~' || (slashes && r == '/') {
return i, r
}
}
return -1, 0
}
// Unit name constants.
const (
UnitByte = "bytes"
UnitRune = "runes"
)
// ValidateInclusion returns an error if n is greater than max or less than min.
// unitName is used for error messages, see UnitFoo constants.
//
// TODO(a.garipov): Consider switching min and max; the current order seems
// confusing.
func ValidateInclusion(n, max, min int, unitName string) (err error) {
switch {
case n > max:
return fmt.Errorf("too long: got %d %s, max %d", n, unitName, max)
case n < min:
return fmt.Errorf("too short: got %d %s, min %d", n, unitName, min)
default:
return nil
}
}

View File

@@ -80,15 +80,13 @@ type RequestInfo struct {
// to this request.
Messages *dnsmsg.Constructor
// ServerGroup is the server group which handles this request.
ServerGroup *ServerGroup
// ServerInfo contains the information about the server processing the query
// and its server group. It must not be nil.
ServerInfo *RequestServerInfo
// RemoteIP is the remote IP address of the client.
RemoteIP netip.Addr
// Server is the name of the server which handles this request.
Server ServerName
// Host is the lowercased, non-FQDN version of the hostname from the
// question of the request.
Host string
@@ -102,11 +100,32 @@ type RequestInfo struct {
// QClass is the class of question for this request.
QClass dnsmsg.Class
// Proto is the protocol by which this request is made.
Proto Protocol
}
// RequestServerInfo contains the information about the server and its group
// relevant to the request.
type RequestServerInfo struct {
// GroupName is the unique name of the server group. It must not be empty.
GroupName ServerGroupName
// Name is the unique name of the server. It must not be empty.
Name ServerName
// DeviceDomains is the list of domain names that the server group uses to
// detect device IDs from clients' server names.
DeviceDomains []string
// Protocol is the protocol by which this request is made.
Protocol Protocol
// ProfilesEnabled, if true, enables recognition of user devices and
// profiles for the server group.
ProfilesEnabled bool
}
// ServerGroupName is the name of a server group.
type ServerGroupName string
// DeviceData returns the profile and device data if any. Either both p and d
// are nil or neither is nil.
func (ri *RequestInfo) DeviceData() (p *Profile, d *Device) {

View File

@@ -6,6 +6,7 @@ import (
"unicode/utf8"
"github.com/AdguardTeam/AdGuardDNS/internal/agdpasswd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdvalidate"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
)
@@ -66,7 +67,12 @@ func NewDeviceID(s string) (id DeviceID, err error) {
}
}()
err = ValidateInclusion(len(s), MaxDeviceIDLen, MinDeviceIDLen, UnitByte)
err = agdvalidate.Inclusion(
len(s),
MinDeviceIDLen,
MaxDeviceIDLen,
agdvalidate.UnitByte,
)
if err != nil {
// The error will be wrapped by the deferred helper.
return "", err
@@ -102,7 +108,12 @@ func NewDeviceName(s string) (n DeviceName, err error) {
}
}()
err = ValidateInclusion(utf8.RuneCountInString(s), MaxDeviceNameRuneLen, 0, UnitRune)
err = agdvalidate.Inclusion(
utf8.RuneCountInString(s),
0,
MaxDeviceNameRuneLen,
agdvalidate.UnitRune,
)
if err != nil {
// The error will be wrapped by the deferred helper.
return "", err

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/AdguardTeam/AdGuardDNS/internal/agdvalidate"
"github.com/AdguardTeam/golibs/errors"
)
@@ -54,7 +55,7 @@ func DeviceTypeFromDNS(s string) (dt DeviceType, err error) {
}
}()
err = ValidateInclusion(len(s), 3, 3, UnitByte)
err = agdvalidate.Inclusion(len(s), 3, 3, agdvalidate.UnitByte)
if err != nil {
// The error will be wrapped by the deferred helper.
return DeviceTypeNone, err

View File

@@ -3,6 +3,9 @@ package agd
import "github.com/AdguardTeam/AdGuardDNS/internal/filter"
// FilteringGroup represents a set of filtering settings.
//
// TODO(a.garipov): Extract the pre-filtering booleans and logic into a new
// package.
type FilteringGroup struct {
// FilterConfig is the configuration of the filters used for this filtering
// group. It must not be nil.

View File

@@ -6,6 +6,7 @@ import (
"strings"
"unicode/utf8"
"github.com/AdguardTeam/AdGuardDNS/internal/agdvalidate"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/syncutil"
@@ -42,7 +43,7 @@ func NewHumanID(s string) (id HumanID, err error) {
// valid. It does not wrap the error to be used in places where that could
// create additional allocations.
func newHumanID(s string) (id HumanID, err error) {
err = ValidateInclusion(len(s), MaxHumanIDLen, MinHumanIDLen, UnitByte)
err = agdvalidate.Inclusion(len(s), MinHumanIDLen, MaxHumanIDLen, agdvalidate.UnitByte)
if err != nil {
// Don't wrap the error, because the caller should do that.
return "", err
@@ -130,7 +131,12 @@ func (p *HumanIDParser) ParseNormalized(s string) (id HumanID, err error) {
}()
// Immediately validate it against the upper DNS hostname-length limit.
err = ValidateInclusion(len(s), netutil.MaxDomainNameLen, MinHumanIDLen, UnitByte)
err = agdvalidate.Inclusion(
len(s),
MinHumanIDLen,
netutil.MaxDomainNameLen,
agdvalidate.UnitByte,
)
if err != nil {
// Don't wrap the error, because there is already a deferred wrap, and
// the error is informative enough as is.

View File

@@ -5,6 +5,7 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agdvalidate"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
)
@@ -15,6 +16,9 @@ import (
//
// NOTE: Do not change fields of this structure without incrementing
// [internal/profiledb/internal.FileCacheVersion].
//
// TODO(a.garipov): Extract the pre-filtering booleans and logic into a new
// package.
type Profile struct {
// FilterConfig is the configuration of the filters used for this profile
// and all its devices that don't have filtering disabled. It must not be
@@ -85,14 +89,14 @@ const MaxProfileIDLen = 8
// NewProfileID converts a simple string into a ProfileID and makes sure that
// it's valid. This should be preferred to a simple type conversion.
func NewProfileID(s string) (id ProfileID, err error) {
if err = ValidateInclusion(len(s), MaxProfileIDLen, 0, UnitByte); err != nil {
if err = agdvalidate.Inclusion(len(s), 0, MaxProfileIDLen, agdvalidate.UnitByte); err != nil {
return "", fmt.Errorf("bad profile id %q: %w", s, err)
}
// For now, allow only the printable, non-whitespace ASCII characters.
// Technically we only need to exclude carriage return and line feed
// characters, but let's be more strict just in case.
if i, r := firstNonIDRune(s, false); i != -1 {
if i, r := agdvalidate.FirstNonIDRune(s, false); i != -1 {
return "", fmt.Errorf("bad profile id: bad char %q at index %d", r, i)
}

View File

@@ -3,9 +3,8 @@ package agd
import (
"encoding/base64"
"fmt"
"time"
"golang.org/x/exp/rand"
"github.com/AdguardTeam/AdGuardDNS/internal/agdrand"
)
// RequestIDLen is the length of a [RequestID] in bytes. A RequestID is
@@ -20,13 +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 = rand.New(&rand.LockedSource{})
// InitRequestID initializes the [RequestID] generator.
func InitRequestID() {
// #nosec G115 -- The Unix epoch time is highly unlikely to be negative.
requestIDRand.Seed(uint64(time.Now().UnixNano()))
}
var requestIDRand = agdrand.NewReader(agdrand.MustNewSeed())
// NewRequestID returns a new pseudorandom RequestID. Prefer this to manual
// conversion from other string types.

View File

@@ -10,8 +10,6 @@ import (
var reqIDSink agd.RequestID
func BenchmarkNewRequestID(b *testing.B) {
agd.InitRequestID()
b.ReportAllocs()
b.ResetTimer()
for range b.N {
@@ -20,10 +18,10 @@ func BenchmarkNewRequestID(b *testing.B) {
require.NotEmpty(b, reqIDSink)
// Most recent result, on a ThinkPad X13 with a Ryzen Pro 7 CPU:
// goos: linux
// goarch: amd64
// Most recent results:
// goos: darwin
// goarch: arm64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/agd
// cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
// BenchmarkNewRequestID-16 50985721 24.91 ns/op 0 B/op 0 allocs/op
// cpu: Apple M1 Pro
// BenchmarkNewRequestID-8 56177144 21.33 ns/op 0 B/op 0 allocs/op
}

View File

@@ -1,60 +0,0 @@
package agd
import (
"github.com/AdguardTeam/golibs/container"
"github.com/miekg/dns"
)
// ServerGroup is a group of DNS servers all of which use the same filtering
// settings.
type ServerGroup struct {
// DDR is the configuration for the server group's Discovery Of Designated
// Resolvers (DDR) handlers. DDR must not be nil.
DDR *DDR
// DeviceDomains is the list of domain names used to detect device IDs from
// clients' server names.
//
// TODO(s.chzhen): Consider using a custom type.
DeviceDomains []string
// Name is the unique name of the server group.
Name ServerGroupName
// FilteringGroup is the ID of the filtering group for this server group.
FilteringGroup FilteringGroupID
// Servers are the settings for servers. Each element must be non-nil.
Servers []*Server
// ProfilesEnabled, if true, enables recognition of user devices and
// profiles for this server group.
ProfilesEnabled bool
}
// ServerGroupName is the name of a server group.
type ServerGroupName string
// DDR is the configuration for the server group's Discovery Of Designated
// Resolvers (DDR) handlers.
type DDR struct {
// DeviceTargets is the set of all domain names, subdomains of which should
// be checked for DDR queries with device IDs.
DeviceTargets *container.MapSet[string]
// PublicTargets is the set of all public domain names, DDR queries for
// which should be processed.
PublicTargets *container.MapSet[string]
// DeviceRecordTemplates are used to respond to DDR queries from recognized
// devices.
DeviceRecordTemplates []*dns.SVCB
// PubilcRecordTemplates are used to respond to DDR queries from
// unrecognized devices.
PublicRecordTemplates []*dns.SVCB
// Enabled shows if DDR queries are processed. If it is false, DDR domain
// name queries receive an NXDOMAIN response.
Enabled bool
}

View File

@@ -0,0 +1,76 @@
// 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

@@ -0,0 +1,118 @@
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{}
// Sinks for benchmarks.
var (
errSink error
intSink int
uint64Sink uint64
)
func BenchmarkReader_Read(b *testing.B) {
const length = 16
reader := agdrand.NewReader(testSeed)
b.ReportAllocs()
b.ResetTimer()
buf := make([]byte, length)
for range b.N {
intSink, errSink = reader.Read(buf)
}
require.Equal(b, length, intSink)
require.NoError(b, errSink)
// Most recent results:
// goos: darwin
// goarch: arm64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/agdrand
// cpu: Apple M1 Pro
// BenchmarkReader_Read-8 57008931 20.60 ns/op 0 B/op 0 allocs/op
}
func BenchmarkLockedSource_Uint64(b *testing.B) {
src := agdrand.NewLockedSource(rand.NewChaCha8(testSeed))
b.ReportAllocs()
b.ResetTimer()
for range b.N {
uint64Sink = src.Uint64()
}
// Most recent results:
// goos: darwin
// goarch: arm64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/agdrand
// cpu: Apple M1 Pro
// BenchmarkLockedSource_Uint64-8 77621248 15.35 ns/op 0 B/op 0 allocs/op
}

View File

@@ -1,7 +0,0 @@
// Package agdservice defines types and interfaces for long-running services.
//
// TODO(a.garipov): Move more to golibs.
package agdservice
// unit is a convenient alias for struct{}.
type unit = struct{}

View File

@@ -1,6 +0,0 @@
package agdservice_test
import "time"
// testTimeout is the timeout for common test operations.
const testTimeout = 1 * time.Second

View File

@@ -1,248 +0,0 @@
package agdservice
import (
"context"
"fmt"
"log/slog"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/service"
"github.com/AdguardTeam/golibs/timeutil"
"golang.org/x/exp/rand"
)
// Refresher is the interface for entities that can update themselves.
type Refresher interface {
// Refresh is called by a [RefreshWorker]. The error returned by Refresh is
// only returned from [RefreshWorker.Shutdown] and only when
// [RefreshWorkerConfig.RefreshOnShutdown] is true. In all other cases, the
// error is ignored, and refreshers must handle error reporting themselves.
Refresh(ctx context.Context) (err error)
}
// RefresherFunc is an adapter to allow the use of ordinary functions as
// [Refresher].
type RefresherFunc func(ctx context.Context) (err error)
// type check
var _ Refresher = RefresherFunc(nil)
// Refresh implements the [Refresher] interface for RefresherFunc.
func (f RefresherFunc) Refresh(ctx context.Context) (err error) {
return f(ctx)
}
// RefreshWorker is an [Interface] implementation that updates its [Refresher]
// every tick of the provided ticker.
type RefreshWorker struct {
logger *slog.Logger
done chan unit
context func() (ctx context.Context, cancel context.CancelFunc)
tick *time.Ticker
rand *rand.Rand
refr Refresher
maxStartSleep time.Duration
refrOnShutdown bool
}
// RefreshWorkerConfig is the configuration structure for a *RefreshWorker.
type RefreshWorkerConfig struct {
// Context is used to provide a context for the Refresh method of Refresher.
//
// NOTE: It is not used for the shutdown refresh.
//
// TODO(a.garipov): Consider ways of fixing that.
Context func() (ctx context.Context, cancel context.CancelFunc)
// Refresher is the entity being refreshed.
Refresher Refresher
// Logger is used for logging the operation of the worker.
Logger *slog.Logger
// Interval is the refresh interval. Must be greater than zero.
//
// TODO(a.garipov): Consider switching to an interface à la
// github.com/robfig/cron/v3.Schedule.
Interval time.Duration
// RefreshOnShutdown, if true, instructs the worker to call the Refresher's
// Refresh method before shutting down the worker. This is useful for items
// that should persist to disk or remote storage before shutting down.
RefreshOnShutdown bool
// RandomizeStart, if true, instructs the worker to sleep before starting a
// refresh. The duration of the sleep is a random duration of up to 10 % of
// Interval.
//
// TODO(a.garipov): Switch to something like a cron schedule and see if this
// is still necessary
RandomizeStart bool
}
// NewRefreshWorker returns a new valid *RefreshWorker with the provided
// parameters. c must not be nil.
func NewRefreshWorker(c *RefreshWorkerConfig) (w *RefreshWorker) {
var maxStartSleep time.Duration
var rng *rand.Rand
if c.RandomizeStart {
maxStartSleep = c.Interval / 10
// #nosec G115 -- The Unix epoch time is highly unlikely to be negative.
rng = rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
}
return &RefreshWorker{
logger: c.Logger,
done: make(chan unit),
context: c.Context,
tick: time.NewTicker(c.Interval),
rand: rng,
refr: c.Refresher,
maxStartSleep: maxStartSleep,
refrOnShutdown: c.RefreshOnShutdown,
}
}
// type check
var _ service.Interface = (*RefreshWorker)(nil)
// Start implements the [service.Interface] interface for *RefreshWorker. err
// is always nil.
func (w *RefreshWorker) Start(_ context.Context) (err error) {
go w.refreshInALoop()
return nil
}
// Shutdown implements the [service.Interface] interface for *RefreshWorker.
//
// NOTE: The context provided by [RefreshWorkerConfig.Context] is not used for
// the shutdown refresh.
func (w *RefreshWorker) Shutdown(ctx context.Context) (err error) {
if w.refrOnShutdown {
err = w.refr.Refresh(slogutil.ContextWithLogger(ctx, w.logger))
}
close(w.done)
w.tick.Stop()
if err != nil {
err = fmt.Errorf("refresh on shutdown: %w", err)
} else {
w.logger.InfoContext(ctx, "shut down successfully")
}
return err
}
// refreshInALoop refreshes the entity every tick of w.tick until Shutdown is
// called.
func (w *RefreshWorker) refreshInALoop() {
ctx := context.Background()
defer slogutil.RecoverAndLog(ctx, w.logger)
w.logger.InfoContext(ctx, "starting refresh loop")
for {
select {
case <-w.done:
w.logger.InfoContext(ctx, "finished refresh loop")
return
case <-w.tick.C:
if w.sleepRandom(ctx) {
w.refresh()
}
}
}
}
// sleepRandom sleeps for up to maxStartSleep unless it's zero. shouldRefresh
// shows if a refresh should be performed once the sleep is finished.
func (w *RefreshWorker) sleepRandom(ctx context.Context) (shouldRefresh bool) {
if w.maxStartSleep == 0 {
return true
}
sleepDur := time.Duration(w.rand.Int63n(int64(w.maxStartSleep)))
// TODO(a.garipov): Augment our JSON handler to use time.Duration.String
// automatically?
w.logger.DebugContext(ctx, "sleeping before refresh", "dur", timeutil.Duration{
Duration: sleepDur,
})
timer := time.NewTimer(sleepDur)
defer func() {
if !timer.Stop() {
// We don't know if the timer's value has been consumed yet or not,
// so use a select with default to make sure that this doesn't
// block.
select {
case <-timer.C:
default:
}
}
}()
select {
case <-w.done:
return false
case <-timer.C:
return true
}
}
// refresh refreshes the entity and logs the status of the refresh.
func (w *RefreshWorker) refresh() {
// TODO(a.garipov): Consider adding a helper for enriching errors with
// context deadline data without duplication. See an example in method
// filter.refreshableFilter.refresh.
ctx, cancel := w.context()
defer cancel()
ctx = slogutil.ContextWithLogger(ctx, w.logger)
_ = w.refr.Refresh(ctx)
}
// RefresherWithErrColl reports all refresh errors to errColl and logs them
// using a provided logging function.
type RefresherWithErrColl struct {
logger *slog.Logger
refr Refresher
errColl errcoll.Interface
prefix string
}
// NewRefresherWithErrColl wraps refr into a refresher that collects errors and
// logs them.
func NewRefresherWithErrColl(
refr Refresher,
logger *slog.Logger,
errColl errcoll.Interface,
prefix string,
) (wrapped *RefresherWithErrColl) {
return &RefresherWithErrColl{
refr: refr,
logger: logger,
errColl: errColl,
prefix: prefix,
}
}
// type check
var _ Refresher = (*RefresherWithErrColl)(nil)
// Refresh implements the [Refresher] interface for *RefresherWithErrColl.
func (r *RefresherWithErrColl) Refresh(ctx context.Context) (err error) {
err = r.refr.Refresh(ctx)
if err != nil {
errcoll.Collect(ctx, r.errColl, r.logger, "refreshing", err)
}
return err
}

View File

@@ -1,125 +0,0 @@
package agdservice_test
import (
"context"
"testing"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// sig is a convenient alias for struct{} when it's used as a signal for
// synchronization.
type sig = struct{}
const (
testIvl = 5 * time.Millisecond
testIvlLong = 1 * time.Hour
name = "test refresher"
testError errors.Error = "test error"
)
// newTestRefresher is a helper that returns refr and linked syncCh channel.
func newTestRefresher(t *testing.T, respErr error) (refr *agdtest.Refresher, syncCh chan sig) {
t.Helper()
pt := testutil.PanicT{}
syncCh = make(chan sig, 1)
refr = &agdtest.Refresher{
OnRefresh: func(_ context.Context) (err error) {
testutil.RequireSend(pt, syncCh, sig{}, testTimeout)
return respErr
},
}
return refr, syncCh
}
// newRefrConfig returns worker configuration.
func newRefrConfig(
t *testing.T,
refr agdservice.Refresher,
ivl time.Duration,
refrOnShutDown bool,
) (conf *agdservice.RefreshWorkerConfig) {
t.Helper()
return &agdservice.RefreshWorkerConfig{
Context: func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), testTimeout)
},
Logger: slogutil.NewDiscardLogger(),
Refresher: refr,
Interval: ivl,
RefreshOnShutdown: refrOnShutDown,
RandomizeStart: false,
}
}
func TestRefreshWorker(t *testing.T) {
t.Run("success", func(t *testing.T) {
refr, syncCh := newTestRefresher(t, nil)
w := agdservice.NewRefreshWorker(newRefrConfig(t, refr, testIvl, false))
err := w.Start(testutil.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
testutil.RequireReceive(t, syncCh, testTimeout)
err = w.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
})
t.Run("success_on_shutdown", func(t *testing.T) {
refr, syncCh := newTestRefresher(t, nil)
errCh := make(chan sig, 1)
w := agdservice.NewRefreshWorker(newRefrConfig(t, refr, testIvlLong, true))
err := w.Start(testutil.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
err = w.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
testutil.RequireReceive(t, syncCh, testTimeout)
require.Empty(t, errCh)
})
t.Run("error", func(t *testing.T) {
refrWithError, syncCh := newTestRefresher(t, testError)
w := agdservice.NewRefreshWorker(newRefrConfig(t, refrWithError, testIvl, false))
err := w.Start(testutil.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
testutil.RequireReceive(t, syncCh, testTimeout)
err = w.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
})
t.Run("error_on_shutdown", func(t *testing.T) {
refrWithError, syncCh := newTestRefresher(t, testError)
w := agdservice.NewRefreshWorker(newRefrConfig(t, refrWithError, testIvlLong, true))
err := w.Start(testutil.ContextWithTimeout(t, testTimeout))
require.NoError(t, err)
err = w.Shutdown(testutil.ContextWithTimeout(t, testTimeout))
assert.ErrorIs(t, err, testError)
testutil.RequireReceive(t, syncCh, testTimeout)
})
}

View File

@@ -10,8 +10,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdpasswd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
@@ -97,36 +95,6 @@ func (a *Authenticator) Authenticate(ctx context.Context, passwd []byte) (ok boo
return a.OnAuthenticate(ctx, passwd)
}
// Package agdservice
// type check
var _ agdservice.Refresher = (*Refresher)(nil)
// Refresher is an [agdservice.Refresher] for tests.
type Refresher struct {
OnRefresh func(ctx context.Context) (err error)
}
// Refresh implements the [agdservice.Refresher] interface for *Refresher.
func (r *Refresher) Refresh(ctx context.Context) (err error) {
return r.OnRefresh(ctx)
}
// Package agdtime
// type check
var _ agdtime.Clock = (*Clock)(nil)
// Clock is a [agdtime.Clock] for tests.
type Clock struct {
OnNow func() (now time.Time)
}
// Now implements the [agdtime.Clock] interface for *Clock.
func (c *Clock) Now() (now time.Time) {
return c.OnNow()
}
// Package billstat
// type check

View File

@@ -8,24 +8,6 @@ import (
"github.com/AdguardTeam/golibs/errors"
)
// Clock is an interface for time-related operations.
//
// TODO(a.garipov): Expand with operations like After or Tick.
//
// TODO(a.garipov): Move to golibs/timeutil.
type Clock interface {
Now() (now time.Time)
}
// SystemClock is a [Clock] that uses the functions from package time.
type SystemClock struct{}
// type check
var _ Clock = SystemClock{}
// Now implements the [Clock] interface for SystemClock.
func (SystemClock) Now() (now time.Time) { return time.Now() }
// Location is a wrapper around time.Location that can de/serialize itself from
// and to JSON.
//

View File

@@ -0,0 +1,37 @@
// Package agdvalidate contains validation utilities.
package agdvalidate
import "fmt"
// FirstNonIDRune returns the first non-printable or non-ASCII rune and its
// index. If includeSlashes is true, it also looks for slashes. If there are
// no such runes, i is -1.
func FirstNonIDRune(s string, excludeSlashes bool) (i int, r rune) {
for i, r = range s {
if r < '!' || r > '~' || (excludeSlashes && r == '/') {
return i, r
}
}
return -1, 0
}
// Unit name constants.
const (
UnitByte = "bytes"
UnitRune = "runes"
)
// Inclusion returns an error if n is greater than maxVal or less than minVal.
// unitName is used for error messages, see [UnitByte] and the related
// constants.
func Inclusion(n, minVal, maxVal int, unitName string) (err error) {
switch {
case n > maxVal:
return fmt.Errorf("too long: got %d %s, max %d", n, unitName, maxVal)
case n < minVal:
return fmt.Errorf("too short: got %d %s, min %d", n, unitName, minVal)
default:
return nil
}
}

View File

@@ -28,7 +28,11 @@ func newClient(apiURL *url.URL) (client *grpc.ClientConn, err error) {
return nil, fmt.Errorf("bad grpc url scheme %q", s)
}
conn, err := grpc.NewClient(apiURL.Host, grpc.WithTransportCredentials(creds))
conn, err := grpc.NewClient(
apiURL.Host,
grpc.WithDisableServiceConfig(),
grpc.WithTransportCredentials(creds),
)
if err != nil {
return nil, fmt.Errorf("dialing: %w", err)
}

View File

@@ -24,8 +24,8 @@ const (
TestProfileID agd.ProfileID = TestProfileIDStr
)
// TestUpdTime is the common update time for tests.
var TestUpdTime = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
// TestSyncTime is the common update time for tests.
var TestSyncTime = time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC)
// TestBind includes any IPv4 address.
//

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.2
// protoc v5.28.3
// protoc-gen-go v1.36.5
// protoc v5.29.1
// source: dns.proto
package backendpb
@@ -14,6 +14,7 @@ import (
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
@@ -94,9 +95,9 @@ func (DeviceType) EnumDescriptor() ([]byte, []int) {
}
type RateLimitSettingsRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RateLimitSettingsRequest) Reset() {
@@ -130,11 +131,10 @@ func (*RateLimitSettingsRequest) Descriptor() ([]byte, []int) {
}
type RateLimitSettingsResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AllowedSubnets []*CidrRange `protobuf:"bytes,1,rep,name=allowed_subnets,json=allowedSubnets,proto3" json:"allowed_subnets,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
AllowedSubnets []*CidrRange `protobuf:"bytes,1,rep,name=allowed_subnets,json=allowedSubnets,proto3" json:"allowed_subnets,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RateLimitSettingsResponse) Reset() {
@@ -175,11 +175,10 @@ func (x *RateLimitSettingsResponse) GetAllowedSubnets() []*CidrRange {
}
type DNSProfilesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
SyncTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=sync_time,json=syncTime,proto3" json:"sync_time,omitempty"`
unknownFields protoimpl.UnknownFields
SyncTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=sync_time,json=syncTime,proto3" json:"sync_time,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *DNSProfilesRequest) Reset() {
@@ -220,23 +219,20 @@ func (x *DNSProfilesRequest) GetSyncTime() *timestamppb.Timestamp {
}
type DNSProfile struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
DnsId string `protobuf:"bytes,1,opt,name=dns_id,json=dnsId,proto3" json:"dns_id,omitempty"`
FilteringEnabled bool `protobuf:"varint,2,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"`
QueryLogEnabled bool `protobuf:"varint,3,opt,name=query_log_enabled,json=queryLogEnabled,proto3" json:"query_log_enabled,omitempty"`
Deleted bool `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"`
SafeBrowsing *SafeBrowsingSettings `protobuf:"bytes,5,opt,name=safe_browsing,json=safeBrowsing,proto3" json:"safe_browsing,omitempty"`
Parental *ParentalSettings `protobuf:"bytes,6,opt,name=parental,proto3" json:"parental,omitempty"`
RuleLists *RuleListsSettings `protobuf:"bytes,7,opt,name=rule_lists,json=ruleLists,proto3" json:"rule_lists,omitempty"`
Devices []*DeviceSettings `protobuf:"bytes,8,rep,name=devices,proto3" json:"devices,omitempty"`
CustomRules []string `protobuf:"bytes,9,rep,name=custom_rules,json=customRules,proto3" json:"custom_rules,omitempty"`
FilteredResponseTtl *durationpb.Duration `protobuf:"bytes,10,opt,name=filtered_response_ttl,json=filteredResponseTtl,proto3" json:"filtered_response_ttl,omitempty"`
BlockPrivateRelay bool `protobuf:"varint,11,opt,name=block_private_relay,json=blockPrivateRelay,proto3" json:"block_private_relay,omitempty"`
BlockFirefoxCanary bool `protobuf:"varint,12,opt,name=block_firefox_canary,json=blockFirefoxCanary,proto3" json:"block_firefox_canary,omitempty"`
// Types that are assignable to BlockingMode:
state protoimpl.MessageState `protogen:"open.v1"`
DnsId string `protobuf:"bytes,1,opt,name=dns_id,json=dnsId,proto3" json:"dns_id,omitempty"`
FilteringEnabled bool `protobuf:"varint,2,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"`
QueryLogEnabled bool `protobuf:"varint,3,opt,name=query_log_enabled,json=queryLogEnabled,proto3" json:"query_log_enabled,omitempty"`
Deleted bool `protobuf:"varint,4,opt,name=deleted,proto3" json:"deleted,omitempty"`
SafeBrowsing *SafeBrowsingSettings `protobuf:"bytes,5,opt,name=safe_browsing,json=safeBrowsing,proto3" json:"safe_browsing,omitempty"`
Parental *ParentalSettings `protobuf:"bytes,6,opt,name=parental,proto3" json:"parental,omitempty"`
RuleLists *RuleListsSettings `protobuf:"bytes,7,opt,name=rule_lists,json=ruleLists,proto3" json:"rule_lists,omitempty"`
Devices []*DeviceSettings `protobuf:"bytes,8,rep,name=devices,proto3" json:"devices,omitempty"`
CustomRules []string `protobuf:"bytes,9,rep,name=custom_rules,json=customRules,proto3" json:"custom_rules,omitempty"`
FilteredResponseTtl *durationpb.Duration `protobuf:"bytes,10,opt,name=filtered_response_ttl,json=filteredResponseTtl,proto3" json:"filtered_response_ttl,omitempty"`
BlockPrivateRelay bool `protobuf:"varint,11,opt,name=block_private_relay,json=blockPrivateRelay,proto3" json:"block_private_relay,omitempty"`
BlockFirefoxCanary bool `protobuf:"varint,12,opt,name=block_firefox_canary,json=blockFirefoxCanary,proto3" json:"block_firefox_canary,omitempty"`
// Types that are valid to be assigned to BlockingMode:
//
// *DNSProfile_BlockingModeCustomIp
// *DNSProfile_BlockingModeNxdomain
@@ -248,6 +244,8 @@ type DNSProfile struct {
AutoDevicesEnabled bool `protobuf:"varint,19,opt,name=auto_devices_enabled,json=autoDevicesEnabled,proto3" json:"auto_devices_enabled,omitempty"`
RateLimit *RateLimitSettings `protobuf:"bytes,20,opt,name=rate_limit,json=rateLimit,proto3" json:"rate_limit,omitempty"`
BlockChromePrefetch bool `protobuf:"varint,21,opt,name=block_chrome_prefetch,json=blockChromePrefetch,proto3" json:"block_chrome_prefetch,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DNSProfile) Reset() {
@@ -364,37 +362,45 @@ func (x *DNSProfile) GetBlockFirefoxCanary() bool {
return false
}
func (m *DNSProfile) GetBlockingMode() isDNSProfile_BlockingMode {
if m != nil {
return m.BlockingMode
func (x *DNSProfile) GetBlockingMode() isDNSProfile_BlockingMode {
if x != nil {
return x.BlockingMode
}
return nil
}
func (x *DNSProfile) GetBlockingModeCustomIp() *BlockingModeCustomIP {
if x, ok := x.GetBlockingMode().(*DNSProfile_BlockingModeCustomIp); ok {
return x.BlockingModeCustomIp
if x != nil {
if x, ok := x.BlockingMode.(*DNSProfile_BlockingModeCustomIp); ok {
return x.BlockingModeCustomIp
}
}
return nil
}
func (x *DNSProfile) GetBlockingModeNxdomain() *BlockingModeNXDOMAIN {
if x, ok := x.GetBlockingMode().(*DNSProfile_BlockingModeNxdomain); ok {
return x.BlockingModeNxdomain
if x != nil {
if x, ok := x.BlockingMode.(*DNSProfile_BlockingModeNxdomain); ok {
return x.BlockingModeNxdomain
}
}
return nil
}
func (x *DNSProfile) GetBlockingModeNullIp() *BlockingModeNullIP {
if x, ok := x.GetBlockingMode().(*DNSProfile_BlockingModeNullIp); ok {
return x.BlockingModeNullIp
if x != nil {
if x, ok := x.BlockingMode.(*DNSProfile_BlockingModeNullIp); ok {
return x.BlockingModeNullIp
}
}
return nil
}
func (x *DNSProfile) GetBlockingModeRefused() *BlockingModeREFUSED {
if x, ok := x.GetBlockingMode().(*DNSProfile_BlockingModeRefused); ok {
return x.BlockingModeRefused
if x != nil {
if x, ok := x.BlockingMode.(*DNSProfile_BlockingModeRefused); ok {
return x.BlockingModeRefused
}
}
return nil
}
@@ -463,13 +469,12 @@ func (*DNSProfile_BlockingModeNullIp) isDNSProfile_BlockingMode() {}
func (*DNSProfile_BlockingModeRefused) isDNSProfile_BlockingMode() {}
type SafeBrowsingSettings struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
BlockDangerousDomains bool `protobuf:"varint,2,opt,name=block_dangerous_domains,json=blockDangerousDomains,proto3" json:"block_dangerous_domains,omitempty"`
BlockNrd bool `protobuf:"varint,3,opt,name=block_nrd,json=blockNrd,proto3" json:"block_nrd,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
BlockDangerousDomains bool `protobuf:"varint,2,opt,name=block_dangerous_domains,json=blockDangerousDomains,proto3" json:"block_dangerous_domains,omitempty"`
BlockNrd bool `protobuf:"varint,3,opt,name=block_nrd,json=blockNrd,proto3" json:"block_nrd,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *SafeBrowsingSettings) Reset() {
@@ -524,10 +529,7 @@ func (x *SafeBrowsingSettings) GetBlockNrd() bool {
}
type DeviceSettings struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
FilteringEnabled bool `protobuf:"varint,3,opt,name=filtering_enabled,json=filteringEnabled,proto3" json:"filtering_enabled,omitempty"`
@@ -535,7 +537,9 @@ type DeviceSettings struct {
DedicatedIps [][]byte `protobuf:"bytes,5,rep,name=dedicated_ips,json=dedicatedIps,proto3" json:"dedicated_ips,omitempty"`
Authentication *AuthenticationSettings `protobuf:"bytes,6,opt,name=authentication,proto3" json:"authentication,omitempty"`
// Value in lower case. Will be empty for "ordinary" devices and non-empty for "automatically" created devices.
HumanIdLower string `protobuf:"bytes,7,opt,name=human_id_lower,json=humanIdLower,proto3" json:"human_id_lower,omitempty"`
HumanIdLower string `protobuf:"bytes,7,opt,name=human_id_lower,json=humanIdLower,proto3" json:"human_id_lower,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeviceSettings) Reset() {
@@ -618,16 +622,15 @@ func (x *DeviceSettings) GetHumanIdLower() string {
}
type ParentalSettings struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
BlockAdult bool `protobuf:"varint,2,opt,name=block_adult,json=blockAdult,proto3" json:"block_adult,omitempty"`
GeneralSafeSearch bool `protobuf:"varint,3,opt,name=general_safe_search,json=generalSafeSearch,proto3" json:"general_safe_search,omitempty"`
YoutubeSafeSearch bool `protobuf:"varint,4,opt,name=youtube_safe_search,json=youtubeSafeSearch,proto3" json:"youtube_safe_search,omitempty"`
BlockedServices []string `protobuf:"bytes,5,rep,name=blocked_services,json=blockedServices,proto3" json:"blocked_services,omitempty"`
Schedule *ScheduleSettings `protobuf:"bytes,6,opt,name=schedule,proto3" json:"schedule,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
BlockAdult bool `protobuf:"varint,2,opt,name=block_adult,json=blockAdult,proto3" json:"block_adult,omitempty"`
GeneralSafeSearch bool `protobuf:"varint,3,opt,name=general_safe_search,json=generalSafeSearch,proto3" json:"general_safe_search,omitempty"`
YoutubeSafeSearch bool `protobuf:"varint,4,opt,name=youtube_safe_search,json=youtubeSafeSearch,proto3" json:"youtube_safe_search,omitempty"`
BlockedServices []string `protobuf:"bytes,5,rep,name=blocked_services,json=blockedServices,proto3" json:"blocked_services,omitempty"`
Schedule *ScheduleSettings `protobuf:"bytes,6,opt,name=schedule,proto3" json:"schedule,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ParentalSettings) Reset() {
@@ -703,12 +706,11 @@ func (x *ParentalSettings) GetSchedule() *ScheduleSettings {
}
type ScheduleSettings struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Tmz string `protobuf:"bytes,1,opt,name=tmz,proto3" json:"tmz,omitempty"`
WeeklyRange *WeeklyRange `protobuf:"bytes,2,opt,name=weeklyRange,proto3" json:"weeklyRange,omitempty"`
unknownFields protoimpl.UnknownFields
Tmz string `protobuf:"bytes,1,opt,name=tmz,proto3" json:"tmz,omitempty"`
WeeklyRange *WeeklyRange `protobuf:"bytes,2,opt,name=weeklyRange,proto3" json:"weeklyRange,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *ScheduleSettings) Reset() {
@@ -756,17 +758,16 @@ func (x *ScheduleSettings) GetWeeklyRange() *WeeklyRange {
}
type WeeklyRange struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Mon *DayRange `protobuf:"bytes,1,opt,name=mon,proto3" json:"mon,omitempty"`
Tue *DayRange `protobuf:"bytes,2,opt,name=tue,proto3" json:"tue,omitempty"`
Wed *DayRange `protobuf:"bytes,3,opt,name=wed,proto3" json:"wed,omitempty"`
Thu *DayRange `protobuf:"bytes,4,opt,name=thu,proto3" json:"thu,omitempty"`
Fri *DayRange `protobuf:"bytes,5,opt,name=fri,proto3" json:"fri,omitempty"`
Sat *DayRange `protobuf:"bytes,6,opt,name=sat,proto3" json:"sat,omitempty"`
Sun *DayRange `protobuf:"bytes,7,opt,name=sun,proto3" json:"sun,omitempty"`
unknownFields protoimpl.UnknownFields
Mon *DayRange `protobuf:"bytes,1,opt,name=mon,proto3" json:"mon,omitempty"`
Tue *DayRange `protobuf:"bytes,2,opt,name=tue,proto3" json:"tue,omitempty"`
Wed *DayRange `protobuf:"bytes,3,opt,name=wed,proto3" json:"wed,omitempty"`
Thu *DayRange `protobuf:"bytes,4,opt,name=thu,proto3" json:"thu,omitempty"`
Fri *DayRange `protobuf:"bytes,5,opt,name=fri,proto3" json:"fri,omitempty"`
Sat *DayRange `protobuf:"bytes,6,opt,name=sat,proto3" json:"sat,omitempty"`
Sun *DayRange `protobuf:"bytes,7,opt,name=sun,proto3" json:"sun,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *WeeklyRange) Reset() {
@@ -849,12 +850,11 @@ func (x *WeeklyRange) GetSun() *DayRange {
}
type DayRange struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Start *durationpb.Duration `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"`
End *durationpb.Duration `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"`
unknownFields protoimpl.UnknownFields
Start *durationpb.Duration `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"`
End *durationpb.Duration `protobuf:"bytes,2,opt,name=end,proto3" json:"end,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *DayRange) Reset() {
@@ -902,12 +902,11 @@ func (x *DayRange) GetEnd() *durationpb.Duration {
}
type RuleListsSettings struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
Ids []string `protobuf:"bytes,2,rep,name=ids,proto3" json:"ids,omitempty"`
unknownFields protoimpl.UnknownFields
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
Ids []string `protobuf:"bytes,2,rep,name=ids,proto3" json:"ids,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *RuleListsSettings) Reset() {
@@ -955,12 +954,11 @@ func (x *RuleListsSettings) GetIds() []string {
}
type BlockingModeCustomIP struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Ipv4 []byte `protobuf:"bytes,1,opt,name=ipv4,proto3" json:"ipv4,omitempty"`
Ipv6 []byte `protobuf:"bytes,2,opt,name=ipv6,proto3" json:"ipv6,omitempty"`
unknownFields protoimpl.UnknownFields
Ipv4 []byte `protobuf:"bytes,1,opt,name=ipv4,proto3" json:"ipv4,omitempty"`
Ipv6 []byte `protobuf:"bytes,2,opt,name=ipv6,proto3" json:"ipv6,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *BlockingModeCustomIP) Reset() {
@@ -1008,9 +1006,9 @@ func (x *BlockingModeCustomIP) GetIpv6() []byte {
}
type BlockingModeNXDOMAIN struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BlockingModeNXDOMAIN) Reset() {
@@ -1044,9 +1042,9 @@ func (*BlockingModeNXDOMAIN) Descriptor() ([]byte, []int) {
}
type BlockingModeNullIP struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BlockingModeNullIP) Reset() {
@@ -1080,9 +1078,9 @@ func (*BlockingModeNullIP) Descriptor() ([]byte, []int) {
}
type BlockingModeREFUSED struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *BlockingModeREFUSED) Reset() {
@@ -1116,17 +1114,16 @@ func (*BlockingModeREFUSED) Descriptor() ([]byte, []int) {
}
type DeviceBillingStat struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
state protoimpl.MessageState `protogen:"open.v1"`
LastActivityTime *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=last_activity_time,json=lastActivityTime,proto3" json:"last_activity_time,omitempty"`
DeviceId string `protobuf:"bytes,2,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
ClientCountry string `protobuf:"bytes,3,opt,name=client_country,json=clientCountry,proto3" json:"client_country,omitempty"`
// Protocol type. Possible values see here: https://bit.adguard.com/projects/DNS/repos/dns-server/browse#ql-properties
Proto uint32 `protobuf:"varint,4,opt,name=proto,proto3" json:"proto,omitempty"`
Asn uint32 `protobuf:"varint,5,opt,name=asn,proto3" json:"asn,omitempty"`
Queries uint32 `protobuf:"varint,6,opt,name=queries,proto3" json:"queries,omitempty"`
Proto uint32 `protobuf:"varint,4,opt,name=proto,proto3" json:"proto,omitempty"`
Asn uint32 `protobuf:"varint,5,opt,name=asn,proto3" json:"asn,omitempty"`
Queries uint32 `protobuf:"varint,6,opt,name=queries,proto3" json:"queries,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeviceBillingStat) Reset() {
@@ -1202,16 +1199,15 @@ func (x *DeviceBillingStat) GetQueries() uint32 {
}
type AccessSettings struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
AllowlistCidr []*CidrRange `protobuf:"bytes,1,rep,name=allowlist_cidr,json=allowlistCidr,proto3" json:"allowlist_cidr,omitempty"`
BlocklistCidr []*CidrRange `protobuf:"bytes,2,rep,name=blocklist_cidr,json=blocklistCidr,proto3" json:"blocklist_cidr,omitempty"`
AllowlistAsn []uint32 `protobuf:"varint,3,rep,packed,name=allowlist_asn,json=allowlistAsn,proto3" json:"allowlist_asn,omitempty"`
BlocklistAsn []uint32 `protobuf:"varint,4,rep,packed,name=blocklist_asn,json=blocklistAsn,proto3" json:"blocklist_asn,omitempty"`
BlocklistDomainRules []string `protobuf:"bytes,5,rep,name=blocklist_domain_rules,json=blocklistDomainRules,proto3" json:"blocklist_domain_rules,omitempty"`
Enabled bool `protobuf:"varint,6,opt,name=enabled,proto3" json:"enabled,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
AllowlistCidr []*CidrRange `protobuf:"bytes,1,rep,name=allowlist_cidr,json=allowlistCidr,proto3" json:"allowlist_cidr,omitempty"`
BlocklistCidr []*CidrRange `protobuf:"bytes,2,rep,name=blocklist_cidr,json=blocklistCidr,proto3" json:"blocklist_cidr,omitempty"`
AllowlistAsn []uint32 `protobuf:"varint,3,rep,packed,name=allowlist_asn,json=allowlistAsn,proto3" json:"allowlist_asn,omitempty"`
BlocklistAsn []uint32 `protobuf:"varint,4,rep,packed,name=blocklist_asn,json=blocklistAsn,proto3" json:"blocklist_asn,omitempty"`
BlocklistDomainRules []string `protobuf:"bytes,5,rep,name=blocklist_domain_rules,json=blocklistDomainRules,proto3" json:"blocklist_domain_rules,omitempty"`
Enabled bool `protobuf:"varint,6,opt,name=enabled,proto3" json:"enabled,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AccessSettings) Reset() {
@@ -1287,12 +1283,11 @@ func (x *AccessSettings) GetEnabled() bool {
}
type CidrRange struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
Prefix uint32 `protobuf:"varint,2,opt,name=prefix,proto3" json:"prefix,omitempty"`
unknownFields protoimpl.UnknownFields
Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
Prefix uint32 `protobuf:"varint,2,opt,name=prefix,proto3" json:"prefix,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *CidrRange) Reset() {
@@ -1340,15 +1335,14 @@ func (x *CidrRange) GetPrefix() uint32 {
}
type AuthenticationSettings struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
DohAuthOnly bool `protobuf:"varint,1,opt,name=doh_auth_only,json=dohAuthOnly,proto3" json:"doh_auth_only,omitempty"`
// Types that are assignable to DohPasswordHash:
state protoimpl.MessageState `protogen:"open.v1"`
DohAuthOnly bool `protobuf:"varint,1,opt,name=doh_auth_only,json=dohAuthOnly,proto3" json:"doh_auth_only,omitempty"`
// Types that are valid to be assigned to DohPasswordHash:
//
// *AuthenticationSettings_PasswordHashBcrypt
DohPasswordHash isAuthenticationSettings_DohPasswordHash `protobuf_oneof:"doh_password_hash"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AuthenticationSettings) Reset() {
@@ -1388,16 +1382,18 @@ func (x *AuthenticationSettings) GetDohAuthOnly() bool {
return false
}
func (m *AuthenticationSettings) GetDohPasswordHash() isAuthenticationSettings_DohPasswordHash {
if m != nil {
return m.DohPasswordHash
func (x *AuthenticationSettings) GetDohPasswordHash() isAuthenticationSettings_DohPasswordHash {
if x != nil {
return x.DohPasswordHash
}
return nil
}
func (x *AuthenticationSettings) GetPasswordHashBcrypt() []byte {
if x, ok := x.GetDohPasswordHash().(*AuthenticationSettings_PasswordHashBcrypt); ok {
return x.PasswordHashBcrypt
if x != nil {
if x, ok := x.DohPasswordHash.(*AuthenticationSettings_PasswordHashBcrypt); ok {
return x.PasswordHashBcrypt
}
}
return nil
}
@@ -1413,13 +1409,12 @@ type AuthenticationSettings_PasswordHashBcrypt struct {
func (*AuthenticationSettings_PasswordHashBcrypt) isAuthenticationSettings_DohPasswordHash() {}
type CreateDeviceRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
DnsId string `protobuf:"bytes,1,opt,name=dns_id,json=dnsId,proto3" json:"dns_id,omitempty"`
HumanId string `protobuf:"bytes,2,opt,name=human_id,json=humanId,proto3" json:"human_id,omitempty"`
DeviceType DeviceType `protobuf:"varint,3,opt,name=device_type,json=deviceType,proto3,enum=DeviceType" json:"device_type,omitempty"`
unknownFields protoimpl.UnknownFields
DnsId string `protobuf:"bytes,1,opt,name=dns_id,json=dnsId,proto3" json:"dns_id,omitempty"`
HumanId string `protobuf:"bytes,2,opt,name=human_id,json=humanId,proto3" json:"human_id,omitempty"`
DeviceType DeviceType `protobuf:"varint,3,opt,name=device_type,json=deviceType,proto3,enum=DeviceType" json:"device_type,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *CreateDeviceRequest) Reset() {
@@ -1474,11 +1469,10 @@ func (x *CreateDeviceRequest) GetDeviceType() DeviceType {
}
type CreateDeviceResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Device *DeviceSettings `protobuf:"bytes,1,opt,name=device,proto3" json:"device,omitempty"`
unknownFields protoimpl.UnknownFields
Device *DeviceSettings `protobuf:"bytes,1,opt,name=device,proto3" json:"device,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *CreateDeviceResponse) Reset() {
@@ -1519,12 +1513,11 @@ func (x *CreateDeviceResponse) GetDevice() *DeviceSettings {
}
type RateLimitedError struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
RetryDelay *durationpb.Duration `protobuf:"bytes,2,opt,name=retry_delay,json=retryDelay,proto3" json:"retry_delay,omitempty"`
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
RetryDelay *durationpb.Duration `protobuf:"bytes,2,opt,name=retry_delay,json=retryDelay,proto3" json:"retry_delay,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *RateLimitedError) Reset() {
@@ -1572,11 +1565,10 @@ func (x *RateLimitedError) GetRetryDelay() *durationpb.Duration {
}
type DeviceQuotaExceededError struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *DeviceQuotaExceededError) Reset() {
@@ -1617,11 +1609,10 @@ func (x *DeviceQuotaExceededError) GetMessage() string {
}
type BadRequestError struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *BadRequestError) Reset() {
@@ -1662,11 +1653,10 @@ func (x *BadRequestError) GetMessage() string {
}
type AuthenticationFailedError struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
unknownFields protoimpl.UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *AuthenticationFailedError) Reset() {
@@ -1707,13 +1697,12 @@ func (x *AuthenticationFailedError) GetMessage() string {
}
type RateLimitSettings struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
Rps uint32 `protobuf:"varint,2,opt,name=rps,proto3" json:"rps,omitempty"`
ClientCidr []*CidrRange `protobuf:"bytes,3,rep,name=client_cidr,json=clientCidr,proto3" json:"client_cidr,omitempty"`
unknownFields protoimpl.UnknownFields
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
Rps uint32 `protobuf:"varint,2,opt,name=rps,proto3" json:"rps,omitempty"`
ClientCidr []*CidrRange `protobuf:"bytes,3,rep,name=client_cidr,json=clientCidr,proto3" json:"client_cidr,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *RateLimitSettings) Reset() {
@@ -1768,11 +1757,10 @@ func (x *RateLimitSettings) GetClientCidr() []*CidrRange {
}
type RemoteKVGetRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *RemoteKVGetRequest) Reset() {
@@ -1813,15 +1801,14 @@ func (x *RemoteKVGetRequest) GetKey() string {
}
type RemoteKVGetResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Value:
state protoimpl.MessageState `protogen:"open.v1"`
// Types that are valid to be assigned to Value:
//
// *RemoteKVGetResponse_Data
// *RemoteKVGetResponse_Empty
Value isRemoteKVGetResponse_Value `protobuf_oneof:"value"`
Value isRemoteKVGetResponse_Value `protobuf_oneof:"value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RemoteKVGetResponse) Reset() {
@@ -1854,23 +1841,27 @@ func (*RemoteKVGetResponse) Descriptor() ([]byte, []int) {
return file_dns_proto_rawDescGZIP(), []int{27}
}
func (m *RemoteKVGetResponse) GetValue() isRemoteKVGetResponse_Value {
if m != nil {
return m.Value
func (x *RemoteKVGetResponse) GetValue() isRemoteKVGetResponse_Value {
if x != nil {
return x.Value
}
return nil
}
func (x *RemoteKVGetResponse) GetData() []byte {
if x, ok := x.GetValue().(*RemoteKVGetResponse_Data); ok {
return x.Data
if x != nil {
if x, ok := x.Value.(*RemoteKVGetResponse_Data); ok {
return x.Data
}
}
return nil
}
func (x *RemoteKVGetResponse) GetEmpty() *emptypb.Empty {
if x, ok := x.GetValue().(*RemoteKVGetResponse_Empty); ok {
return x.Empty
if x != nil {
if x, ok := x.Value.(*RemoteKVGetResponse_Empty); ok {
return x.Empty
}
}
return nil
}
@@ -1892,13 +1883,12 @@ func (*RemoteKVGetResponse_Data) isRemoteKVGetResponse_Value() {}
func (*RemoteKVGetResponse_Empty) isRemoteKVGetResponse_Value() {}
type RemoteKVSetRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
Ttl *durationpb.Duration `protobuf:"bytes,3,opt,name=ttl,proto3" json:"ttl,omitempty"`
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
Ttl *durationpb.Duration `protobuf:"bytes,3,opt,name=ttl,proto3" json:"ttl,omitempty"`
sizeCache protoimpl.SizeCache
}
func (x *RemoteKVSetRequest) Reset() {
@@ -1953,9 +1943,9 @@ func (x *RemoteKVSetRequest) GetTtl() *durationpb.Duration {
}
type RemoteKVSetResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RemoteKVSetResponse) Reset() {
@@ -1990,7 +1980,7 @@ func (*RemoteKVSetResponse) Descriptor() ([]byte, []int) {
var File_dns_proto protoreflect.FileDescriptor
var file_dns_proto_rawDesc = []byte{
var file_dns_proto_rawDesc = string([]byte{
0x0a, 0x09, 0x64, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f,
@@ -2295,16 +2285,16 @@ var file_dns_proto_rawDesc = []byte{
0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, 0x10,
0x44, 0x4e, 0x53, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f,
0x50, 0x01, 0xa2, 0x02, 0x03, 0x44, 0x4e, 0x53, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
})
var (
file_dns_proto_rawDescOnce sync.Once
file_dns_proto_rawDescData = file_dns_proto_rawDesc
file_dns_proto_rawDescData []byte
)
func file_dns_proto_rawDescGZIP() []byte {
file_dns_proto_rawDescOnce.Do(func() {
file_dns_proto_rawDescData = protoimpl.X.CompressGZIP(file_dns_proto_rawDescData)
file_dns_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_dns_proto_rawDesc), len(file_dns_proto_rawDesc)))
})
return file_dns_proto_rawDescData
}
@@ -2423,7 +2413,7 @@ func file_dns_proto_init() {
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_dns_proto_rawDesc,
RawDescriptor: unsafe.Slice(unsafe.StringData(file_dns_proto_rawDesc), len(file_dns_proto_rawDesc)),
NumEnums: 1,
NumMessages: 30,
NumExtensions: 0,
@@ -2435,7 +2425,6 @@ func file_dns_proto_init() {
MessageInfos: file_dns_proto_msgTypes,
}.Build()
File_dns_proto = out.File
file_dns_proto_rawDesc = nil
file_dns_proto_goTypes = nil
file_dns_proto_depIdxs = nil
}

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.28.3
// - protoc v5.29.1
// source: dns.proto
package backendpb

View File

@@ -13,6 +13,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/custom"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
@@ -25,10 +26,10 @@ import (
// TODO(a.garipov): Refactor into methods of [*ProfileStorage].
func (x *DNSProfile) toInternal(
ctx context.Context,
updTime time.Time,
bindSet netutil.SubnetSet,
errColl errcoll.Interface,
logger *slog.Logger,
baseCustomLogger *slog.Logger,
mtrc ProfileDBMetrics,
respSzEst datasize.ByteSize,
) (profile *agd.Profile, devices []*agd.Device, err error) {
@@ -59,12 +60,20 @@ func (x *DNSProfile) toInternal(
}
customRules := rulesToInternal(ctx, x.CustomRules, errColl, logger)
customEnabled := len(customRules) > 0
var customFilter filter.Custom
if customEnabled {
customFilter = custom.New(&custom.Config{
Logger: baseCustomLogger.With("client_id", string(profID)),
Rules: customRules,
})
}
custom := &filter.ConfigCustom{
ID: string(x.DnsId),
UpdateTime: updTime,
Rules: customRules,
Filter: customFilter,
// TODO(a.garipov): Consider adding an explicit flag to the protocol.
Enabled: len(customRules) > 0,
Enabled: customEnabled,
}
return &agd.Profile{

View File

@@ -13,7 +13,9 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/AdGuardDNS/internal/filter/custom"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/c2h5oh/datasize"
"github.com/stretchr/testify/assert"
@@ -33,10 +35,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
got, gotDevices, err := NewTestDNSProfile(t).toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -57,10 +59,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
}
got, gotDevices, err := newDNSProfileWithBadData(t).toInternal(
ctx,
TestUpdTime,
TestBind,
savingErrColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -89,10 +91,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
bindSet := netip.MustParsePrefix("2.2.2.2/32")
got, gotDevices, err := NewTestDNSProfile(t).toInternal(
ctx,
TestUpdTime,
bindSet,
savingErrColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -115,10 +117,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
var emptyDNSProfile *DNSProfile
_, _, err := emptyDNSProfile.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -135,10 +137,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
got, gotDevices, err := dp.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -158,10 +160,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
_, _, err := dp.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -183,10 +185,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
_, _, err := dp.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -206,10 +208,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
_, _, err := dp.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -225,10 +227,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
_, _, err := dp.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -245,10 +247,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
_, _, err := dp.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -263,10 +265,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
got, gotDevices, err := dp.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -288,10 +290,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
got, _, err := dp.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -312,10 +314,10 @@ func TestDNSProfile_ToInternal(t *testing.T) {
got, _, err := dp.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)
@@ -504,6 +506,14 @@ func newProfile(tb testing.TB) (p *agd.Profile) {
End: 60,
}
wantCustom := &filter.ConfigCustom{
Filter: custom.New(&custom.Config{
Logger: slogutil.NewDiscardLogger(),
Rules: []filter.RuleText{"||example.org^"},
}),
Enabled: true,
}
wantParental := &filter.ConfigParental{
PauseSchedule: &filter.ConfigSchedule{
Week: &filter.WeeklySchedule{
@@ -526,6 +536,11 @@ func newProfile(tb testing.TB) (p *agd.Profile) {
SafeSearchYouTubeEnabled: false,
}
wantRuleList := &filter.ConfigRuleList{
IDs: []filter.ID{"1"},
Enabled: true,
}
wantSafeBrowsing := &filter.ConfigSafeBrowsing{
Enabled: true,
DangerousDomainsEnabled: true,
@@ -553,17 +568,9 @@ func newProfile(tb testing.TB) (p *agd.Profile) {
return &agd.Profile{
FilterConfig: &filter.ConfigClient{
Custom: &filter.ConfigCustom{
ID: TestProfileIDStr,
UpdateTime: TestUpdTime,
Rules: []filter.RuleText{"||example.org^"},
Enabled: true,
},
Parental: wantParental,
RuleList: &filter.ConfigRuleList{
IDs: []filter.ID{"1"},
Enabled: true,
},
Custom: wantCustom,
Parental: wantParental,
RuleList: wantRuleList,
SafeBrowsing: wantSafeBrowsing,
},
Access: wantAccess,
@@ -654,10 +661,10 @@ func BenchmarkDNSProfile_ToInternal(b *testing.B) {
for range b.N {
profSink, _, errSink = dp.toInternal(
ctx,
TestUpdTime,
TestBind,
errColl,
TestLogger,
TestLogger,
EmptyProfileDBMetrics{},
TestRespSzEst,
)

View File

@@ -23,6 +23,13 @@ import (
// ProfileStorageConfig is the configuration for the business logic backend
// profile storage.
type ProfileStorageConfig struct {
// Logger is used for logging the operation of the profile storage. It must
// not be nil.
Logger *slog.Logger
// BaseCustomLogger is the base logger used for the custom filters.
BaseCustomLogger *slog.Logger
// BindSet is the subnet set created from DNS servers listening addresses.
// It must not be nil.
BindSet netutil.SubnetSet
@@ -31,10 +38,6 @@ type ProfileStorageConfig struct {
// non-critical errors. It must not be nil.
ErrColl errcoll.Interface
// Logger is used for logging the operation of the profile storage. It must
// not be nil.
Logger *slog.Logger
// GRPCMetrics is used for the collection of the protobuf communication
// statistics.
GRPCMetrics GRPCMetrics
@@ -63,15 +66,16 @@ type ProfileStorageConfig struct {
// that retrieves the profile and device information from the business logic
// backend. It is safe for concurrent use.
type ProfileStorage struct {
bindSet netutil.SubnetSet
errColl errcoll.Interface
client DNSServiceClient
logger *slog.Logger
grpcMetrics GRPCMetrics
metrics ProfileDBMetrics
apiKey string
respSzEst datasize.ByteSize
maxProfSize datasize.ByteSize
logger *slog.Logger
baseCustomLogger *slog.Logger
bindSet netutil.SubnetSet
errColl errcoll.Interface
client DNSServiceClient
grpcMetrics GRPCMetrics
metrics ProfileDBMetrics
apiKey string
respSzEst datasize.ByteSize
maxProfSize datasize.ByteSize
}
// NewProfileStorage returns a new [ProfileStorage] that retrieves information
@@ -84,15 +88,16 @@ func NewProfileStorage(c *ProfileStorageConfig) (s *ProfileStorage, err error) {
}
return &ProfileStorage{
bindSet: c.BindSet,
errColl: c.ErrColl,
client: NewDNSServiceClient(client),
logger: c.Logger,
grpcMetrics: c.GRPCMetrics,
metrics: c.Metrics,
apiKey: c.APIKey,
respSzEst: c.ResponseSizeEstimate,
maxProfSize: c.MaxProfilesSize,
logger: c.Logger,
baseCustomLogger: c.BaseCustomLogger,
bindSet: c.BindSet,
errColl: c.ErrColl,
client: NewDNSServiceClient(client),
grpcMetrics: c.GRPCMetrics,
metrics: c.Metrics,
apiKey: c.APIKey,
respSzEst: c.ResponseSizeEstimate,
maxProfSize: c.MaxProfilesSize,
}, nil
}
@@ -179,10 +184,10 @@ func (s *ProfileStorage) Profiles(
stats.startDec()
prof, devices, profErr := profile.toInternal(
ctx,
time.Now(),
s.bindSet,
s.errColl,
s.logger,
s.baseCustomLogger,
s.metrics,
s.respSzEst,
)

View File

@@ -13,7 +13,7 @@ import (
func TestSyncTimeFromTrailer(t *testing.T) {
t.Parallel()
milliseconds := strconv.FormatInt(TestUpdTime.UnixMilli(), 10)
milliseconds := strconv.FormatInt(TestSyncTime.UnixMilli(), 10)
testCases := []struct {
in metadata.MD
@@ -38,7 +38,7 @@ func TestSyncTimeFromTrailer(t *testing.T) {
}, {
in: metadata.MD{"sync_time": []string{milliseconds}},
wantError: "",
want: TestUpdTime,
want: TestSyncTime,
name: "success",
}}

View File

@@ -66,11 +66,12 @@ func TestProfileStorage_CreateAutoDevice(t *testing.T) {
require.NoError(t, err)
s, err := backendpb.NewProfileStorage(&backendpb.ProfileStorageConfig{
BindSet: backendpb.TestBind,
ErrColl: agdtest.NewErrorCollector(),
Logger: backendpb.TestLogger,
GRPCMetrics: backendpb.EmptyGRPCMetrics{},
Metrics: backendpb.EmptyProfileDBMetrics{},
BindSet: backendpb.TestBind,
ErrColl: agdtest.NewErrorCollector(),
Logger: backendpb.TestLogger,
BaseCustomLogger: backendpb.TestLogger,
GRPCMetrics: backendpb.EmptyGRPCMetrics{},
Metrics: backendpb.EmptyProfileDBMetrics{},
Endpoint: &url.URL{
Scheme: "grpc",
Host: l.Addr().String(),
@@ -122,7 +123,7 @@ var (
)
func BenchmarkProfileStorage_Profiles(b *testing.B) {
syncTime := strconv.FormatInt(backendpb.TestUpdTime.UnixMilli(), 10)
syncTime := strconv.FormatInt(backendpb.TestSyncTime.UnixMilli(), 10)
srvProf := backendpb.NewTestDNSProfile(b)
trailerMD := metadata.MD{
"sync_time": []string{syncTime},
@@ -157,11 +158,12 @@ 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,
GRPCMetrics: backendpb.EmptyGRPCMetrics{},
Metrics: backendpb.EmptyProfileDBMetrics{},
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(),

View File

@@ -6,10 +6,10 @@ import (
"log/slog"
"net/url"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/consul"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/service"
)
// RateLimiterConfig is the configuration structure for the business logic
@@ -41,8 +41,8 @@ type RateLimiterConfig struct {
APIKey string
}
// RateLimiter is the implementation of the [agdservice.Refresher] interface
// that retrieves the rate limit settings from the business logic backend.
// RateLimiter is the implementation of the [service.Refresher] interface that
// retrieves the rate limit settings from the business logic backend.
type RateLimiter struct {
logger *slog.Logger
grpcMetrics GRPCMetrics
@@ -74,9 +74,9 @@ func NewRateLimiter(c *RateLimiterConfig) (l *RateLimiter, err error) {
}
// type check
var _ agdservice.Refresher = (*RateLimiter)(nil)
var _ service.Refresher = (*RateLimiter)(nil)
// Refresh implements the [agdservice.Refresher] interface for *RateLimiter.
// Refresh implements the [service.Refresher] interface for *RateLimiter.
func (l *RateLimiter) Refresh(ctx context.Context) (err error) {
l.logger.InfoContext(ctx, "refresh started")
defer l.logger.InfoContext(ctx, "refresh finished")

View File

@@ -5,11 +5,11 @@ import "context"
// Metrics is an interface that is used for the collection of the billing
// statistics.
type Metrics interface {
// BufferSizeSet sets the number of stored records to n.
BufferSizeSet(ctx context.Context, n float64)
// SetRecordCount sets the total number of records stored.
SetRecordCount(ctx context.Context, count int)
// HandleUploadDuration handles the upload duration of billing statistics.
HandleUploadDuration(ctx context.Context, dur float64, isSuccess bool)
HandleUploadDuration(ctx context.Context, dur float64, err error)
}
// EmptyMetrics is the implementation of the [Metrics] interface that does
@@ -19,8 +19,8 @@ type EmptyMetrics struct{}
// type check
var _ Metrics = EmptyMetrics{}
// BufferSizeSet implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) BufferSizeSet(_ context.Context, _ float64) {}
// SetRecordCount implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) SetRecordCount(_ context.Context, _ int) {}
// HandleUploadDuration implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) HandleUploadDuration(_ context.Context, _ float64, _ bool) {}
func (EmptyMetrics) HandleUploadDuration(_ context.Context, _ float64, _ error) {}

View File

@@ -7,9 +7,9 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/geoip"
"github.com/AdguardTeam/golibs/service"
)
// RuntimeRecorderConfig is the configuration structure for a runtime billing
@@ -88,7 +88,7 @@ func (r *RuntimeRecorder) Record(
Proto: proto,
}
r.metrics.BufferSizeSet(ctx, float64(len(r.records)))
r.metrics.SetRecordCount(ctx, len(r.records))
} else {
rec.Time = start
rec.Country = ctry
@@ -99,9 +99,9 @@ func (r *RuntimeRecorder) Record(
}
// type check
var _ agdservice.Refresher = (*RuntimeRecorder)(nil)
var _ service.Refresher = (*RuntimeRecorder)(nil)
// Refresh implements the [agdserivce.Refresher] interface for *RuntimeRecorder.
// Refresh implements the [service.Refresher] interface for *RuntimeRecorder.
// It uploads the currently available data and resets it.
func (r *RuntimeRecorder) Refresh(ctx context.Context) (err error) {
r.logger.DebugContext(ctx, "refresh started")
@@ -112,14 +112,12 @@ func (r *RuntimeRecorder) Refresh(ctx context.Context) (err error) {
startTime := time.Now()
defer func() {
dur := time.Since(startTime).Seconds()
r.metrics.HandleUploadDuration(ctx, dur, err)
isSuccess := err == nil
if !isSuccess {
if err != nil {
r.remergeRecords(ctx, records)
r.logger.WarnContext(ctx, "refresh failed, records remerged")
}
r.metrics.HandleUploadDuration(ctx, dur, isSuccess)
}()
err = r.uploader.Upload(ctx, records)
@@ -138,7 +136,7 @@ func (r *RuntimeRecorder) resetRecords(ctx context.Context) (records Records) {
records, r.records = r.records, Records{}
r.metrics.BufferSizeSet(ctx, 0)
r.metrics.SetRecordCount(ctx, 0)
return records
}
@@ -157,5 +155,5 @@ func (r *RuntimeRecorder) remergeRecords(ctx context.Context, records Records) {
}
}
r.metrics.BufferSizeSet(ctx, float64(len(r.records)))
r.metrics.SetRecordCount(ctx, len(r.records))
}

View File

@@ -3,6 +3,7 @@ package cmd
import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/validate"
)
// accessConfig is the configuration that controls IP and hosts blocking.
@@ -15,10 +16,10 @@ type accessConfig struct {
}
// type check
var _ validator = (*accessConfig)(nil)
var _ validate.Interface = (*accessConfig)(nil)
// validate implements the [validator] interface for *accessConfig.
func (c *accessConfig) validate() (err error) {
// Validate implements the [validate.Interface] interface for *accessConfig.
func (c *accessConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}

View File

@@ -5,6 +5,8 @@ import (
"maps"
"slices"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
"github.com/prometheus/common/model"
)
@@ -12,15 +14,20 @@ import (
type additionalInfo map[string]string
// type check
var _ validator = additionalInfo(nil)
var _ validate.Interface = additionalInfo(nil)
// validate implements the [validator] interface for additionalInfo.
func (c additionalInfo) validate() (err error) {
// Validate implements the [validate.Interface] interface for additionalInfo.
func (c additionalInfo) Validate() (err error) {
var errs []error
for _, k := range slices.Sorted(maps.Keys(c)) {
if !model.LabelName(k).IsValid() {
return fmt.Errorf("prometheus labels must match %s, got %q", model.LabelNameRE, k)
errs = append(errs, fmt.Errorf(
"prometheus labels must match %s, got %q",
model.LabelNameRE,
k,
))
}
}
return nil
return errors.Join(errs...)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
)
// backendConfig is the backend module configuration.
@@ -39,26 +40,21 @@ type backendConfig struct {
}
// type check
var _ validator = (*backendConfig)(nil)
var _ validate.Interface = (*backendConfig)(nil)
// validate implements the [validator] interface for *backendConfig.
func (c *backendConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *backendConfig.
func (c *backendConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case c.Timeout.Duration < 0:
return newNegativeError("timeout", c.Timeout)
case c.RefreshIvl.Duration <= 0:
return newNotPositiveError("refresh_interval", c.RefreshIvl)
case c.FullRefreshIvl.Duration <= 0:
return newNotPositiveError("full_refresh_interval", c.FullRefreshIvl)
case c.FullRefreshRetryIvl.Duration <= 0:
return newNotPositiveError("full_refresh_retry_interval", c.FullRefreshRetryIvl)
case c.BillStatIvl.Duration <= 0:
return newNotPositiveError("bill_stat_interval", c.BillStatIvl)
default:
return nil
}
return errors.Join(
validate.NotNegative("timeout", c.Timeout),
validate.Positive("refresh_interval", c.RefreshIvl),
validate.Positive("full_refresh_interval", c.FullRefreshIvl),
validate.Positive("full_refresh_retry_interval", c.FullRefreshRetryIvl),
validate.Positive("bill_stat_interval", c.BillStatIvl),
)
}
// initProfDB refreshes the profile database initially. It logs an error if

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"log/slog"
"maps"
"math/rand/v2"
"net/netip"
"net/url"
"path"
@@ -15,8 +16,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/access"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtime"
"github.com/AdguardTeam/AdGuardDNS/internal/agdrand"
"github.com/AdguardTeam/AdGuardDNS/internal/backendpb"
"github.com/AdguardTeam/AdGuardDNS/internal/billstat"
"github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
@@ -29,6 +29,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
dnssvcprom "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
@@ -42,11 +43,13 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/rulestat"
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
"github.com/AdguardTeam/AdGuardDNS/internal/websvc"
"github.com/AdguardTeam/golibs/contextutil"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/osutil"
"github.com/AdguardTeam/golibs/service"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/c2h5oh/datasize"
"github.com/prometheus/client_golang/prometheus"
)
@@ -87,6 +90,7 @@ type builder struct {
mtrcNamespace string
plugins *plugin.Registry
promRegisterer prometheus.Registerer
rand *rand.Rand
sigHdlr *service.SignalHandler
// The fields below are initialized later by calling the builder's methods.
@@ -114,6 +118,7 @@ type builder struct {
newRegDomains *hashprefix.Filter
newRegDomainsHashes *hashprefix.Storage
profileDB profiledb.Interface
queryLog querylog.Interface
rateLimit *ratelimit.Backoff
ruleStat rulestat.Interface
safeBrowsing *hashprefix.Filter
@@ -125,7 +130,7 @@ type builder struct {
// The fields below are initialized later, just like with the fields above,
// but are placed in this order for alignment optimization.
serverGroups []*agd.ServerGroup
serverGroups []*dnssvc.ServerGroupConfig
profilesEnabled bool
}
@@ -170,6 +175,10 @@ func newBuilder(c *builderConfig) (b *builder) {
plugins: c.plugins,
promRegisterer: prometheus.DefaultRegisterer,
debugRefrs: debugsvc.Refreshers{},
// #nosec G115 G404 -- The Unix epoch time is highly unlikely to be
// negative and we don't need a real random for simple refresh time
// randomization.
rand: rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), 0)),
sigHdlr: service.NewSignalHandler(&service.SignalHandlerConfig{
Logger: c.baseLogger.With(slogutil.KeyPrefix, service.SignalHandlerPrefix),
ShutdownTimeout: shutdownTimeout,
@@ -199,9 +208,17 @@ func (b *builder) initGeoIP(ctx context.Context) {
asn, ctry := b.env.GeoIPASNPath, b.env.GeoIPCountryPath
b.logger.DebugContext(ctx, "using geoip files", "asn", asn, "ctry", ctry)
mtrc, err := metrics.NewGeoIP(b.mtrcNamespace, b.promRegisterer, asn, ctry)
if err != nil {
err = fmt.Errorf("registering geoip metrics: %w", err)
return
}
c := b.conf.GeoIP
b.geoIP = geoip.NewFile(&geoip.FileConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "geoip"),
Metrics: mtrc,
CacheManager: b.cacheManager,
ASNPath: asn,
CountryPath: ctry,
@@ -280,8 +297,22 @@ func (b *builder) initAdultBlocking(
}
c := b.conf.AdultBlocking
id := filter.IDAdultBlocking
refrIvl := time.Duration(c.RefreshIvl)
refrTimeout := time.Duration(c.RefreshTimeout)
const id = filter.IDAdultBlocking
hashPrefMtcs, err := metrics.NewHashPrefixFilter(
b.mtrcNamespace,
string(id),
b.promRegisterer,
)
if err != nil {
return fmt.Errorf("registering hashprefix filter metrics: %w", err)
}
prefix := path.Join(hashprefix.IDPrefix, string(id))
b.adultBlocking, err = hashprefix.NewFilter(&hashprefix.FilterConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, prefix),
Cloner: b.cloner,
@@ -289,13 +320,14 @@ func (b *builder) initAdultBlocking(
Hashes: b.adultBlockingHashes,
URL: &b.env.AdultBlockingURL.URL,
ErrColl: b.errColl,
HashPrefixMtcs: hashPrefMtcs,
Metrics: b.filterMtrc,
ID: id,
CachePath: filepath.Join(cacheDir, string(id)),
ReplacementHost: c.BlockHost,
Staleness: c.RefreshIvl.Duration,
RefreshTimeout: c.RefreshTimeout.Duration,
CacheTTL: c.CacheTTL.Duration,
Staleness: refrIvl,
RefreshTimeout: refrTimeout,
CacheTTL: time.Duration(c.CacheTTL),
// TODO(a.garipov): Make all sizes [datasize.ByteSize] and rename cache
// entity counts to fooCount.
CacheCount: c.CacheSize,
@@ -310,15 +342,14 @@ func (b *builder) initAdultBlocking(
return fmt.Errorf("initial refresh: %w", err)
}
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
// Note that we also set the same timeout for the http.Client in
// [hashprefix.NewFilter].
Context: newCtxWithTimeoutCons(c.RefreshTimeout.Duration),
Refresher: b.adultBlocking,
Logger: b.baseLogger.With(slogutil.KeyPrefix, string(id)+"_refresh"),
Interval: c.RefreshIvl.Duration,
RefreshOnShutdown: false,
RandomizeStart: false,
ContextConstructor: contextutil.NewTimeoutConstructor(refrTimeout),
ErrorHandler: newSlogErrorHandler(b.baseLogger, string(id)+"_refresh"),
Refresher: b.adultBlocking,
Schedule: timeutil.NewConstSchedule(refrIvl),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
if err != nil {
@@ -334,6 +365,16 @@ func (b *builder) initAdultBlocking(
return nil
}
// newSlogErrorHandler is a convenient wrapper around
// [service.NewSlogErrorHandler].
func newSlogErrorHandler(baseLogger *slog.Logger, prefix string) (h *service.SlogErrorHandler) {
return service.NewSlogErrorHandler(
baseLogger.With(slogutil.KeyPrefix, prefix),
slog.LevelError,
"refreshing",
)
}
// initNewRegDomains initializes the newly-registered domain filter and hash
// storage. It also adds the refresher with ID
// [hashprefix.IDPrefix]/[filter.IDNewRegDomains] to the debug refreshers.
@@ -357,8 +398,22 @@ func (b *builder) initNewRegDomains(
// Reuse the general safe-browsing filter configuration with a new URL and
// ID.
c := b.conf.SafeBrowsing
id := filter.IDNewRegDomains
refrIvl := time.Duration(c.RefreshIvl)
refrTimeout := time.Duration(c.RefreshTimeout)
const id = filter.IDNewRegDomains
hashPrefMtcs, err := metrics.NewHashPrefixFilter(
b.mtrcNamespace,
string(id),
b.promRegisterer,
)
if err != nil {
return fmt.Errorf("registering hashprefix filter metrics: %w", err)
}
prefix := path.Join(hashprefix.IDPrefix, string(id))
b.newRegDomains, err = hashprefix.NewFilter(&hashprefix.FilterConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, prefix),
Cloner: b.cloner,
@@ -366,13 +421,14 @@ func (b *builder) initNewRegDomains(
Hashes: b.newRegDomainsHashes,
URL: &b.env.NewRegDomainsURL.URL,
ErrColl: b.errColl,
HashPrefixMtcs: hashPrefMtcs,
Metrics: b.filterMtrc,
ID: id,
CachePath: filepath.Join(cacheDir, string(id)),
ReplacementHost: c.BlockHost,
Staleness: c.RefreshIvl.Duration,
RefreshTimeout: c.RefreshTimeout.Duration,
CacheTTL: c.CacheTTL.Duration,
Staleness: refrIvl,
RefreshTimeout: refrTimeout,
CacheTTL: time.Duration(c.CacheTTL),
CacheCount: c.CacheSize,
MaxSize: maxSize,
})
@@ -385,15 +441,14 @@ func (b *builder) initNewRegDomains(
return fmt.Errorf("initial refresh: %w", err)
}
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
// Note that we also set the same timeout for the http.Client in
// [hashprefix.NewFilter].
Context: newCtxWithTimeoutCons(c.RefreshTimeout.Duration),
Refresher: b.newRegDomains,
Logger: b.baseLogger.With(slogutil.KeyPrefix, string(id)+"_refresh"),
Interval: c.RefreshIvl.Duration,
RefreshOnShutdown: false,
RandomizeStart: false,
ContextConstructor: contextutil.NewTimeoutConstructor(refrTimeout),
ErrorHandler: newSlogErrorHandler(b.baseLogger, string(id)+"_refresh"),
Refresher: b.newRegDomains,
Schedule: timeutil.NewConstSchedule(refrIvl),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
if err != nil {
@@ -429,8 +484,22 @@ func (b *builder) initSafeBrowsing(
}
c := b.conf.SafeBrowsing
id := filter.IDSafeBrowsing
refrIvl := time.Duration(c.RefreshIvl)
refrTimeout := time.Duration(c.RefreshTimeout)
const id = filter.IDSafeBrowsing
hashPrefMtcs, err := metrics.NewHashPrefixFilter(
b.mtrcNamespace,
string(id),
b.promRegisterer,
)
if err != nil {
return fmt.Errorf("registering hashprefix filter metrics: %w", err)
}
prefix := path.Join(hashprefix.IDPrefix, string(id))
b.safeBrowsing, err = hashprefix.NewFilter(&hashprefix.FilterConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, prefix),
Cloner: b.cloner,
@@ -438,13 +507,14 @@ func (b *builder) initSafeBrowsing(
Hashes: b.safeBrowsingHashes,
URL: &b.env.SafeBrowsingURL.URL,
ErrColl: b.errColl,
HashPrefixMtcs: hashPrefMtcs,
Metrics: b.filterMtrc,
ID: id,
CachePath: filepath.Join(cacheDir, string(id)),
ReplacementHost: c.BlockHost,
Staleness: c.RefreshIvl.Duration,
RefreshTimeout: c.RefreshTimeout.Duration,
CacheTTL: c.CacheTTL.Duration,
Staleness: refrIvl,
RefreshTimeout: refrTimeout,
CacheTTL: time.Duration(c.CacheTTL),
CacheCount: c.CacheSize,
MaxSize: maxSize,
})
@@ -457,15 +527,14 @@ func (b *builder) initSafeBrowsing(
return fmt.Errorf("initial refresh: %w", err)
}
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
// Note that we also set the same timeout for the http.Client in
// [hashprefix.NewFilter].
Context: newCtxWithTimeoutCons(c.RefreshTimeout.Duration),
Refresher: b.safeBrowsing,
Logger: b.baseLogger.With(slogutil.KeyPrefix, string(id)+"_refresh"),
Interval: c.RefreshIvl.Duration,
RefreshOnShutdown: false,
RandomizeStart: false,
ContextConstructor: contextutil.NewTimeoutConstructor(refrTimeout),
ErrorHandler: newSlogErrorHandler(b.baseLogger, string(id)+"_refresh"),
Refresher: b.safeBrowsing,
Schedule: timeutil.NewConstSchedule(refrIvl),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
if err != nil {
@@ -487,14 +556,19 @@ func (b *builder) initSafeBrowsing(
// [builder.initHashPrefixFilters] must be called before this method.
func (b *builder) initFilterStorage(ctx context.Context) (err error) {
c := b.conf.Filters
refrIvl := c.RefreshIvl.Duration
refrTimeout := c.RefreshTimeout.Duration
refrIvl := time.Duration(c.RefreshIvl)
refrTimeout := time.Duration(c.RefreshTimeout)
var blockedSvcIdxURL *url.URL
if b.env.BlockedServiceEnabled {
blockedSvcIdxURL = &b.env.BlockedServiceIndexURL.URL
}
b.filterStorage, err = filterstorage.New(&filterstorage.Config{
BaseLogger: b.baseLogger,
Logger: b.baseLogger.With(slogutil.KeyPrefix, filter.StoragePrefix),
BlockedServices: &filterstorage.ConfigBlockedServices{
IndexURL: &b.env.BlockedServiceIndexURL.URL,
IndexURL: blockedSvcIdxURL,
// TODO(a.garipov): Consider adding a separate parameter here.
IndexMaxSize: c.MaxSize,
// TODO(a.garipov): Consider making configurable.
@@ -520,7 +594,7 @@ func (b *builder) initFilterStorage(ctx context.Context) (err error) {
// TODO(a.garipov): Consider adding a separate parameter here.
IndexMaxSize: c.MaxSize,
MaxSize: c.MaxSize,
IndexRefreshTimeout: c.IndexRefreshTimeout.Duration,
IndexRefreshTimeout: time.Duration(c.IndexRefreshTimeout),
// TODO(a.garipov): Consider adding a separate parameter here.
IndexStaleness: refrIvl,
RefreshTimeout: refrTimeout,
@@ -540,7 +614,7 @@ func (b *builder) initFilterStorage(ctx context.Context) (err error) {
bool(b.env.YoutubeSafeSearchEnabled),
),
CacheManager: b.cacheManager,
Clock: agdtime.SystemClock{},
Clock: timeutil.SystemClock{},
ErrColl: b.errColl,
Metrics: b.filterMtrc,
CacheDir: b.env.FilterCachePath,
@@ -554,13 +628,12 @@ func (b *builder) initFilterStorage(ctx context.Context) (err error) {
return fmt.Errorf("refreshing default filter storage: %w", err)
}
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: newCtxWithTimeoutCons(refrTimeout),
Refresher: b.filterStorage,
Logger: b.baseLogger.With(slogutil.KeyPrefix, "filters/storage_refresh"),
Interval: refrIvl,
RefreshOnShutdown: false,
RandomizeStart: false,
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
ContextConstructor: contextutil.NewTimeoutConstructor(refrTimeout),
ErrorHandler: newSlogErrorHandler(b.baseLogger, "filters/storage_refresh"),
Refresher: b.filterStorage,
Schedule: timeutil.NewConstSchedule(refrIvl),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
if err != nil {
@@ -597,9 +670,9 @@ func (b *builder) newSafeSearchConfig(
// TODO(a.garipov): Consider making this configurable.
ResultCacheTTL: 1 * time.Hour,
// TODO(a.garipov): Consider adding a separate parameter here.
RefreshTimeout: fltConf.RefreshTimeout.Duration,
RefreshTimeout: time.Duration(fltConf.RefreshTimeout),
// TODO(a.garipov): Consider adding a separate parameter here.
Staleness: fltConf.RefreshIvl.Duration,
Staleness: time.Duration(fltConf.RefreshIvl),
ResultCacheCount: fltConf.SafeSearchCacheSize,
Enabled: true,
}
@@ -651,6 +724,59 @@ func (b *builder) initBindToDevice(ctx context.Context) (err error) {
return nil
}
// initDNSDB initializes the DNS database.
func (b *builder) initDNSDB(ctx context.Context) (err error) {
if !b.conf.DNSDB.Enabled {
b.dnsDB = dnsdb.Empty{}
return nil
}
mtrc, err := metrics.NewDNSDB(b.mtrcNamespace, b.promRegisterer)
if err != nil {
return fmt.Errorf("registering dnsdb metrics: %w", err)
}
b.dnsDB = dnsdb.New(&dnsdb.DefaultConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "dnsdb"),
ErrColl: b.errColl,
Metrics: mtrc,
MaxSize: b.conf.DNSDB.MaxSize,
})
b.logger.DebugContext(ctx, "initialized dns database")
return nil
}
// initQueryLog initializes the appropriate query log implementation from the
// configuration and environment data.
func (b *builder) initQueryLog(ctx context.Context) (err error) {
if !b.conf.QueryLog.File.Enabled {
b.queryLog = querylog.Empty{}
b.logger.DebugContext(ctx, "initialized empty query log")
return nil
}
mtrc, err := metrics.NewQueryLog(b.mtrcNamespace, b.promRegisterer)
if err != nil {
return fmt.Errorf("registering querylog metrics: %w", err)
}
b.queryLog = querylog.NewFileSystem(&querylog.FileSystemConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "querylog"),
Path: b.env.QueryLogPath,
Metrics: mtrc,
RandSeed: agdrand.MustNewSeed(),
})
b.logger.DebugContext(ctx, "initialized file-based query log")
return nil
}
// Constants for the experimental Structured DNS Errors feature.
//
// TODO(a.garipov): Make configurable.
@@ -685,7 +811,7 @@ func (b *builder) initMsgConstructor(ctx context.Context) (err error) {
Cloner: b.cloner,
BlockingMode: &dnsmsg.BlockingModeNullIP{},
StructuredErrors: b.sdeConf,
FilteredResponseTTL: fltConf.ResponseTTL.Duration,
FilteredResponseTTL: time.Duration(fltConf.ResponseTTL),
EDEEnabled: fltConf.EDEEnabled,
})
if err != nil {
@@ -823,20 +949,19 @@ func (b *builder) startBindToDevice(ctx context.Context) (err error) {
//
// [builder.initTLSManager] must be called before this method.
func (b *builder) initTicketRotator(ctx context.Context) (err error) {
tickRot := agdservice.RefresherFunc(b.tlsManager.RotateTickets)
tickRot := service.RefresherFunc(b.tlsManager.RotateTickets)
err = tickRot.Refresh(ctx)
if err != nil {
return fmt.Errorf("initial session ticket refresh: %w", err)
}
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: tickRot,
Logger: b.baseLogger.With(slogutil.KeyPrefix, "tickrot_refresh"),
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
ContextConstructor: contextutil.NewTimeoutConstructor(defaultTimeout),
ErrorHandler: newSlogErrorHandler(b.baseLogger, "tickrot_refresh"),
Refresher: tickRot,
// TODO(a.garipov): Make configurable.
Interval: 1 * time.Minute,
Schedule: timeutil.NewConstSchedule(1 * time.Minute),
RefreshOnShutdown: false,
RandomizeStart: false,
})
err = refr.Start(ctx)
if err != nil {
@@ -852,13 +977,17 @@ func (b *builder) initTicketRotator(ctx context.Context) (err error) {
return nil
}
// defaultTimeout is the timeout used for some operations where another timeout
// hasn't been defined yet.
const defaultTimeout = 30 * time.Second
// initGRPCMetrics initializes the gRPC metrics if necessary.
// [builder.initServerGroups] must be called before this method.
func (b *builder) initGRPCMetrics(ctx context.Context) (err error) {
switch {
case
b.profilesEnabled,
b.conf.Check.RemoteKV.Type == kvModeBackend,
b.conf.Check.KV.Type == kvModeBackend,
b.conf.RateLimit.Allowlist.Type == rlAllowlistTypeBackend:
// Go on.
default:
@@ -904,17 +1033,16 @@ func (b *builder) initBillStat(ctx context.Context) (err error) {
})
c := b.conf.Backend
refrIvl := c.BillStatIvl.Duration
timeout := c.Timeout.Duration
refrIvl := time.Duration(c.BillStatIvl)
timeout := time.Duration(c.Timeout)
b.billStat = billStat
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: newCtxWithTimeoutCons(timeout),
Refresher: billStat,
Logger: b.baseLogger.With(slogutil.KeyPrefix, "billstat_refresh"),
Interval: refrIvl,
RefreshOnShutdown: true,
RandomizeStart: false,
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
ContextConstructor: contextutil.NewTimeoutConstructor(timeout),
ErrorHandler: newSlogErrorHandler(b.baseLogger, "billstat_refresh"),
Refresher: billStat,
Schedule: timeutil.NewConstSchedule(refrIvl),
RefreshOnShutdown: true,
})
err = refr.Start(ctx)
if err != nil {
@@ -971,10 +1099,12 @@ 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,
GRPCMetrics: b.backendGRPCMtrc,
Metrics: backendProfileDBMtrc,
Endpoint: apiURL,
@@ -992,15 +1122,16 @@ func (b *builder) initProfileDB(ctx context.Context) (err error) {
}
c := b.conf.Backend
timeout := c.Timeout.Duration
timeout := time.Duration(c.Timeout)
profDB, err := profiledb.New(&profiledb.Config{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "profiledb"),
BaseCustomLogger: customLogger,
Storage: strg,
ErrColl: b.errColl,
Metrics: profDBMtrc,
CacheFilePath: b.env.ProfilesCachePath,
FullSyncIvl: c.FullRefreshIvl.Duration,
FullSyncRetryIvl: c.FullRefreshRetryIvl.Duration,
FullSyncIvl: time.Duration(c.FullRefreshIvl),
FullSyncRetryIvl: time.Duration(c.FullRefreshRetryIvl),
ResponseSizeEstimate: respSzEst,
})
if err != nil {
@@ -1014,13 +1145,22 @@ func (b *builder) initProfileDB(ctx context.Context) (err error) {
// TODO(a.garipov): Add a separate refresher ID for full refreshes.
b.profileDB = profDB
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: newCtxWithTimeoutCons(timeout),
Logger: b.baseLogger.With(slogutil.KeyPrefix, "profiledb_refresh"),
Refresher: profDB,
Interval: c.RefreshIvl.Duration,
RefreshOnShutdown: false,
RandomizeStart: true,
// Randomize the start of the profile DB refresh by up to 10 % to not
// overload the profile storage.
refrIvl := time.Duration(c.RefreshIvl)
sched := timeutil.NewRandomizedSchedule(
timeutil.NewConstSchedule(refrIvl),
b.rand,
0,
refrIvl/10,
)
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
ContextConstructor: contextutil.NewTimeoutConstructor(timeout),
ErrorHandler: newSlogErrorHandler(b.baseLogger, "profiledb_refresh"),
Refresher: profDB,
Schedule: sched,
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
if err != nil {
@@ -1082,21 +1222,26 @@ func (b *builder) initRuleStat(ctx context.Context) (err error) {
return nil
}
mtrc, err := metrics.NewRuleStat(b.mtrcNamespace, b.promRegisterer)
if err != nil {
return fmt.Errorf("rulestat metrics: %w", err)
}
ruleStat := rulestat.NewHTTP(&rulestat.HTTPConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "rulestat"),
ErrColl: b.errColl,
Metrics: mtrc,
URL: &u.URL,
})
b.ruleStat = ruleStat
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: ruleStat,
Logger: b.baseLogger.With(slogutil.KeyPrefix, "rulestat_refresh"),
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
ContextConstructor: contextutil.NewTimeoutConstructor(defaultTimeout),
ErrorHandler: newSlogErrorHandler(b.baseLogger, "rulestat_refresh"),
Refresher: ruleStat,
// TODO(a.garipov): Make configurable.
Interval: 10 * time.Minute,
Schedule: timeutil.NewConstSchedule(10 * time.Minute),
RefreshOnShutdown: true,
RandomizeStart: false,
})
err = refr.Start(ctx)
if err != nil {
@@ -1129,7 +1274,7 @@ func (b *builder) initRateLimiter(ctx context.Context) (err error) {
return fmt.Errorf("ratelimit metrics: %w", err)
}
var updater agdservice.Refresher
var updater service.Refresher
if typ == rlAllowlistTypeBackend {
updater, err = backendpb.NewRateLimiter(&backendpb.RateLimiterConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "backend_ratelimiter"),
@@ -1160,13 +1305,12 @@ func (b *builder) initRateLimiter(ctx context.Context) (err error) {
return fmt.Errorf("allowlist: initial refresh: %w", err)
}
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: updater,
Logger: b.baseLogger.With(slogutil.KeyPrefix, "ratelimit_allowlist_refresh"),
Interval: c.Allowlist.RefreshIvl.Duration,
RefreshOnShutdown: false,
RandomizeStart: false,
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
ContextConstructor: contextutil.NewTimeoutConstructor(defaultTimeout),
ErrorHandler: newSlogErrorHandler(b.baseLogger, "ratelimit_allowlist_refresh"),
Refresher: updater,
Schedule: timeutil.NewConstSchedule(time.Duration(c.Allowlist.RefreshIvl)),
RefreshOnShutdown: false,
})
err = refr.Start(ctx)
if err != nil {
@@ -1175,7 +1319,11 @@ func (b *builder) initRateLimiter(ctx context.Context) (err error) {
b.sigHdlr.Add(refr)
b.connLimit = c.ConnectionLimit.toInternal(b.baseLogger)
err = b.initConnLimit(ctx, c.ConnectionLimit)
if err != nil {
return fmt.Errorf("connlimit: %w", err)
}
b.rateLimit = ratelimit.NewBackoff(c.toInternal(allowlist))
b.debugRefrs[debugIDAllowlist] = updater
@@ -1185,11 +1333,27 @@ func (b *builder) initRateLimiter(ctx context.Context) (err error) {
return nil
}
// initConnLimit initializes the connection limiter from the given conf.
func (b *builder) initConnLimit(ctx context.Context, conf *connLimitConfig) (err error) {
if !conf.Enabled {
return nil
}
mtrc, err := metrics.NewConnLimiter(b.mtrcNamespace, b.promRegisterer)
if err != nil {
return fmt.Errorf("metrics: %w", err)
}
b.connLimit = connlimiter.New(conf.toInternal(ctx, b.baseLogger, mtrc))
return nil
}
// initWeb initializes the web service, starts it, and registers it in the
// signal handler. [builder.initDNSCheck] must be call before this method.
func (b *builder) initWeb(ctx context.Context) (err error) {
c := b.conf.Web
webConf, err := c.toInternal(ctx, b.env, b.dnsCheck, b.errColl, b.tlsManager)
webConf, err := c.toInternal(ctx, b.env, b.dnsCheck, b.errColl, b.baseLogger, b.tlsManager)
if err != nil {
return fmt.Errorf("converting web configuration: %w", err)
}
@@ -1201,14 +1365,13 @@ func (b *builder) initWeb(ctx context.Context) (err error) {
return fmt.Errorf("web: initial refresh: %w", err)
}
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
Refresher: b.webSvc,
Logger: b.baseLogger.With(slogutil.KeyPrefix, "websvc_refresh"),
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
ContextConstructor: contextutil.NewTimeoutConstructor(defaultTimeout),
ErrorHandler: newSlogErrorHandler(b.baseLogger, "websvc_refresh"),
Refresher: b.webSvc,
// TODO(a.garipov): Consider making configurable.
Interval: 5 * time.Minute,
Schedule: timeutil.NewConstSchedule(5 * time.Minute),
RefreshOnShutdown: false,
RandomizeStart: false,
})
err = refr.Start(ctx)
if err != nil {
@@ -1240,22 +1403,16 @@ func (b *builder) waitGeoIP(ctx context.Context) (err error) {
const prefix = "geoip_refresh"
refrLogger := b.baseLogger.With(slogutil.KeyPrefix, prefix)
refr := agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: ctxWithDefaultTimeout,
refr := service.NewRefreshWorker(&service.RefreshWorkerConfig{
ContextConstructor: contextutil.NewTimeoutConstructor(defaultTimeout),
// Do not add errColl to geoip's config, as that would create an import
// cycle.
//
// TODO(a.garipov): Resolve that.
Refresher: agdservice.NewRefresherWithErrColl(
b.geoIP,
refrLogger,
b.errColl,
prefix,
),
Logger: refrLogger,
Interval: b.conf.GeoIP.RefreshIvl.Duration,
ErrorHandler: errcoll.NewRefreshErrorHandler(refrLogger, b.errColl),
Refresher: b.geoIP,
Schedule: timeutil.NewConstSchedule(time.Duration(b.conf.GeoIP.RefreshIvl)),
RefreshOnShutdown: false,
RandomizeStart: false,
})
err = refr.Start(ctx)
if err != nil {
@@ -1275,17 +1432,27 @@ func (b *builder) waitGeoIP(ctx context.Context) (err error) {
// - [builder.initAccess]
// - [builder.initBillStat]
// - [builder.initBindToDevice]
// - [builder.initDNSDB]
// - [builder.initFilterStorage]
// - [builder.initFilteringGroups]
// - [builder.initMsgConstructor]
// - [builder.initQueryLog]
// - [builder.initProfileDB]
// - [builder.initRateLimiter]
// - [builder.initRuleStat]
// - [builder.initWeb]
// - [builder.waitGeoIP]
func (b *builder) initDNS(ctx context.Context) (err error) {
b.fwdHandler = forward.NewHandler(b.conf.Upstream.toInternal(b.baseLogger))
b.dnsDB = b.conf.DNSDB.toInternal(b.baseLogger, b.errColl)
mtrcListener, err := dnssvcprom.NewForwardMetricsListener(
b.mtrcNamespace,
b.promRegisterer,
len(b.conf.Upstream.Servers)+len(b.conf.Upstream.Fallback.Servers),
)
if err != nil {
return fmt.Errorf("forward metrics listener: %w", err)
}
b.fwdHandler = forward.NewHandler(b.conf.Upstream.toInternal(b.baseLogger, mtrcListener))
dnsHdlrsConf := &dnssvc.HandlersConfig{
BaseLogger: b.baseLogger,
@@ -1307,7 +1474,7 @@ func (b *builder) initDNS(ctx context.Context) (err error) {
HashMatcher: b.hashMatcher,
ProfileDB: b.profileDB,
PrometheusRegisterer: b.promRegisterer,
QueryLog: b.queryLog(),
QueryLog: b.queryLog,
RateLimit: b.rateLimit,
RuleStat: b.ruleStat,
MetricsNamespace: b.mtrcNamespace,
@@ -1322,15 +1489,17 @@ func (b *builder) initDNS(ctx context.Context) (err error) {
}
dnsConf := &dnssvc.Config{
Handlers: dnsHdlrs,
Cloner: b.cloner,
ControlConf: b.controlConf,
ConnLimiter: b.connLimit,
NonDNS: b.webSvc,
ErrColl: b.errColl,
MetricsNamespace: b.mtrcNamespace,
ServerGroups: b.serverGroups,
HandleTimeout: b.conf.DNS.HandleTimeout.Duration,
BaseLogger: b.baseLogger,
Handlers: dnsHdlrs,
Cloner: b.cloner,
ControlConf: b.controlConf,
ConnLimiter: b.connLimit,
NonDNS: b.webSvc,
ErrColl: b.errColl,
PrometheusRegisterer: b.promRegisterer,
MetricsNamespace: b.mtrcNamespace,
ServerGroups: b.serverGroups,
HandleTimeout: time.Duration(b.conf.DNS.HandleTimeout),
}
b.dnsSvc, err = dnssvc.New(dnsConf)
@@ -1343,22 +1512,6 @@ func (b *builder) initDNS(ctx context.Context) (err error) {
return nil
}
// queryLog returns the appropriate query log implementation from the
// configuration and environment data.
func (b *builder) queryLog() (l querylog.Interface) {
fileNeeded := b.conf.QueryLog.File.Enabled
if !fileNeeded {
return querylog.Empty{}
}
return querylog.NewFileSystem(&querylog.FileSystemConfig{
Logger: b.baseLogger.With(slogutil.KeyPrefix, "querylog"),
Path: b.env.QueryLogPath,
// #nosec G115 -- The Unix epoch time is highly unlikely to be negative.
RandSeed: uint64(time.Now().UnixNano()),
})
}
// performConnCheck performs the connectivity check in accordance to the
// configuration given so far.
//

View File

@@ -2,10 +2,12 @@ package cmd
import (
"fmt"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
)
// cacheConfig is the configuration of the DNS cacheConfig module
@@ -38,6 +40,18 @@ type ttlOverride struct {
Enabled bool `yaml:"enabled"`
}
// type check
var _ validate.Interface = (*ttlOverride)(nil)
// Validate implements the [validate.Interface] interface for *ttlOverride.
func (c *ttlOverride) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
return validate.Positive("min", c.Min)
}
// Cache types.
const (
cacheTypeECS = "ecs"
@@ -58,7 +72,7 @@ func (c *cacheConfig) toInternal() (cacheConf *dnssvc.CacheConfig) {
}
return &dnssvc.CacheConfig{
MinTTL: c.TTLOverride.Min.Duration,
MinTTL: time.Duration(c.TTLOverride.Min),
ECSCount: c.ECSSize,
NoECSCount: c.Size,
Type: typ,
@@ -67,47 +81,36 @@ func (c *cacheConfig) toInternal() (cacheConf *dnssvc.CacheConfig) {
}
// type check
var _ validator = (*cacheConfig)(nil)
var _ validate.Interface = (*cacheConfig)(nil)
// validate implements the [validator] interface for *cacheConfig.
func (c *cacheConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *cacheConfig.
func (c *cacheConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case c.Type != cacheTypeSimple && c.Type != cacheTypeECS:
return fmt.Errorf(
}
errs := []error{
validate.NotNegative("size", c.Size),
}
errs = validate.Append(errs, "ttl_override", c.TTLOverride)
switch c.Type {
case cacheTypeSimple:
// Go on.
case cacheTypeECS:
if err = validate.NotNegative("ecs_size", c.ECSSize); err != nil {
// Don't wrap the error, because it's informative enough as is.
errs = append(errs, err)
}
default:
errs = append(errs, fmt.Errorf(
"type: %w: %q, supported: %q",
errors.ErrBadEnumValue,
c.Type,
[]string{cacheTypeSimple, cacheTypeECS},
)
case c.Size < 0:
return newNegativeError("size", c.Size)
case c.Type == cacheTypeECS && c.ECSSize < 0:
return newNegativeError("ecs_size", c.ECSSize)
default:
// Go on.
))
}
err = c.TTLOverride.validate()
if err != nil {
return fmt.Errorf("ttl_override: %w", err)
}
return nil
}
// type check
var _ validator = (*ttlOverride)(nil)
// validate implements the [validator] interface for *ttlOverride.
func (c *ttlOverride) validate() (err error) {
switch {
case c == nil:
return errors.ErrNoValue
case c.Min.Duration <= 0:
return newNotPositiveError("min", c.Min)
default:
return nil
}
return errors.Join(errs...)
}

View File

@@ -17,11 +17,11 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/remotekv"
"github.com/AdguardTeam/AdGuardDNS/internal/remotekv/consulkv"
"github.com/AdguardTeam/AdGuardDNS/internal/remotekv/rediskv"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
"github.com/c2h5oh/datasize"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/time/rate"
@@ -29,8 +29,8 @@ import (
// checkConfig is the DNS server checking configuration.
type checkConfig struct {
// RemoteKV is remote key-value store configuration for DNS server checking.
RemoteKV *remoteKVConfig `yaml:"kv"`
// 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"`
@@ -61,7 +61,12 @@ func (c *checkConfig) toInternal(
reg prometheus.Registerer,
grpcMtrc backendpb.GRPCMetrics,
) (conf *dnscheck.RemoteKVConfig, err error) {
kv, err := c.RemoteKV.newRemoteKV(envs, namespace, reg, grpcMtrc)
mtrc, err := metrics.NewDNSCheck(namespace, reg)
if err != nil {
return nil, fmt.Errorf("dnscheck metrics: %w", err)
}
kv, err := c.KV.newRemoteKV(envs, namespace, reg, grpcMtrc)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return nil, err
@@ -75,6 +80,7 @@ func (c *checkConfig) toInternal(
return &dnscheck.RemoteKVConfig{
Logger: baseLogger.With(slogutil.KeyPrefix, "dnscheck"),
Messages: messages,
Metrics: mtrc,
RemoteKV: kv,
ErrColl: errColl,
Domains: domains,
@@ -135,8 +141,8 @@ func (c *remoteKVConfig) newRemoteKV(
},
MaxActive: envs.RedisMaxActive,
MaxIdle: envs.RedisMaxIdle,
IdleTimeout: envs.RedisIdleTimeout.Duration,
TTL: c.TTL.Duration,
IdleTimeout: time.Duration(envs.RedisIdleTimeout),
TTL: time.Duration(c.TTL),
})
case kvModeConsul:
kv, err = c.newConsulRemoteKV(envs)
@@ -167,7 +173,7 @@ func (c *remoteKVConfig) newBackendRemoteKV(
Metrics: backendKVMtrc,
Endpoint: &envs.DNSCheckRemoteKVURL.URL,
APIKey: envs.DNSCheckRemoteKVAPIKey,
TTL: c.TTL.Duration,
TTL: time.Duration(c.TTL),
})
if err != nil {
return nil, fmt.Errorf("initializing backend dnscheck kv: %w", err)
@@ -206,7 +212,7 @@ func (c *remoteKVConfig) newConsulRemoteKV(envs *environment) (kv remotekv.Inter
}),
// TODO(ameshkov): Consider making configurable.
Limiter: rate.NewLimiter(rate.Limit(200)/60, 1),
TTL: c.TTL.Duration,
TTL: time.Duration(c.TTL),
MaxRespSize: maxRespSize,
})
if err != nil {
@@ -217,53 +223,38 @@ func (c *remoteKVConfig) newConsulRemoteKV(envs *environment) (kv remotekv.Inter
}
// type check
var _ validator = (*checkConfig)(nil)
var _ validate.Interface = (*checkConfig)(nil)
// validate implements the [validator] interface for *checkConfig.
func (c *checkConfig) validate() (err error) {
// Validate implements the [validate.Interface] interface for *checkConfig.
func (c *checkConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
notEmptyParams := container.KeyValues[string, string]{{
Key: "node_location",
Value: c.NodeLocation,
}, {
Key: "node_name",
Value: c.NodeName,
}}
for _, kv := range notEmptyParams {
if kv.Value == "" {
return fmt.Errorf("%s: %w", kv.Key, errors.ErrEmptyValue)
}
}
if len(c.Domains) == 0 {
return fmt.Errorf("domains: %w", errors.ErrEmptyValue)
errs := []error{
validate.NotEmpty("node_location", c.NodeLocation),
validate.NotEmpty("node_name", c.NodeName),
validate.NotEmptySlice("domains", c.Domains),
}
err = validateNonNilIPs(c.IPv4, netutil.AddrFamilyIPv4)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
errs = append(errs, err)
}
err = validateNonNilIPs(c.IPv6, netutil.AddrFamilyIPv6)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
errs = append(errs, err)
}
err = c.RemoteKV.validate()
if err != nil {
return fmt.Errorf("kv: %w", err)
}
errs = validate.Append(errs, "kv", c.KV)
return nil
return errors.Join(errs...)
}
// validateNonNilIPs returns an error if ips is empty or had IP addresses of
// ValidateNonNilIPs returns an error if ips is empty or had IP addresses of
// incorrect protocol version.
//
// TODO(a.garipov): Merge with [validateAddrs].
@@ -313,45 +304,26 @@ type remoteKVConfig struct {
}
// type check
var _ validator = (*remoteKVConfig)(nil)
var _ validate.Interface = (*remoteKVConfig)(nil)
// validate implements the [validator] interface for *remoteKVConfig.
func (c *remoteKVConfig) validate() (err error) {
// Validate implements the [validate.Interface] interface for *remoteKVConfig.
func (c *remoteKVConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
ttl := c.TTL
ttl := time.Duration(c.TTL)
switch c.Type {
case kvModeBackend:
if ttl.Duration <= 0 {
return newNotPositiveError("ttl", ttl)
}
return validate.Positive("ttl", ttl)
case kvModeCache:
// Go on.
return nil
case kvModeConsul:
if ttl.Duration < consulkv.MinTTL || ttl.Duration > consulkv.MaxTTL {
return fmt.Errorf(
"ttl: %w: must be between %s and %s; got %s",
errors.ErrOutOfRange,
consulkv.MinTTL,
consulkv.MaxTTL,
ttl,
)
}
return validate.InRange("ttl", ttl, consulkv.MinTTL, consulkv.MaxTTL)
case kvModeRedis:
if ttl.Duration < rediskv.MinTTL {
return fmt.Errorf(
"ttl: %w: must be greater than or equal to %s got %s",
errors.ErrOutOfRange,
rediskv.MinTTL,
ttl,
)
}
return validate.NoLessThan("ttl", ttl, rediskv.MinTTL)
default:
return fmt.Errorf("type: %w: %q", errors.ErrBadEnumValue, c.Type)
}
return nil
}

View File

@@ -9,32 +9,37 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/cmd/plugin"
"github.com/AdguardTeam/AdGuardDNS/internal/experiment"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/version"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/sentryutil"
)
// Main is the entry point of application.
func Main(plugins *plugin.Registry) {
// TODO(a.garipov, e.burkov): Consider adding timeouts for initialization.
agd.InitRequestID()
ctx := context.Background()
// Log only to stdout and let users decide how to process it.
log.SetOutput(os.Stdout)
envs := errors.Must(parseEnvironment())
errors.Check(envs.Validate())
errors.Check(envs.validate())
lvl := errors.Must(slogutil.VerbosityToLevel(envs.Verbosity))
baseLogger := slogutil.New(&slogutil.Config{
// Don't use [slogutil.NewFormat] here, because the value is validated.
Format: slogutil.Format(envs.LogFormat),
AddTimestamp: bool(envs.LogTimestamp),
Level: lvl,
})
// TODO(a.garipov): Use slog everywhere.
logger := envs.configureLogs()
sentryutil.SetDefaultLogger(baseLogger, "")
experiment.Init(baseLogger)
// TODO(a.garipov): Consider ways of replacing a prefix and stop passing
// the main logger everywhere.
mainLogger := logger.With(slogutil.KeyPrefix, "main")
mainLogger := baseLogger.With(slogutil.KeyPrefix, "main")
// Signal service startup now that we have the logs set up.
branch := version.Branch()
@@ -56,13 +61,13 @@ func Main(plugins *plugin.Registry) {
// TODO(a.garipov): Consider parsing SENTRY_DSN separately to set sentry up
// first and collect panics from the readEnvs call above as well.
errColl := errors.Must(envs.buildErrColl())
errColl := errors.Must(envs.buildErrColl(baseLogger))
defer reportPanics(ctx, errColl, mainLogger)
c := errors.Must(parseConfig(envs.ConfPath))
errors.Check(c.validate())
errors.Check(c.Validate())
errors.Check(envs.validateFromValidConfig(c))
@@ -73,7 +78,7 @@ func Main(plugins *plugin.Registry) {
b := newBuilder(&builderConfig{
envs: envs,
conf: c,
baseLogger: logger,
baseLogger: baseLogger,
plugins: plugins,
errColl: errColl,
})
@@ -92,6 +97,10 @@ func Main(plugins *plugin.Registry) {
errors.Check(b.initBindToDevice(ctx))
errors.Check(b.initDNSDB(ctx))
errors.Check(b.initQueryLog(ctx))
errors.Check(b.initMsgConstructor(ctx))
errors.Check(b.initTLSManager(ctx))

View File

@@ -6,6 +6,7 @@ import (
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
"gopkg.in/yaml.v2"
)
@@ -83,16 +84,16 @@ type configuration struct {
}
// type check
var _ validator = (*configuration)(nil)
var _ validate.Interface = (*configuration)(nil)
// validate implements the [validator] interface for *configuration.
func (c *configuration) validate() (err error) {
// Validate implements the [validate.Interface] interface for *configuration.
func (c *configuration) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
// Keep this in the same order as the fields in the config.
validators := container.KeyValues[string, validator]{{
validators := container.KeyValues[string, validate.Interface]{{
Key: "ratelimit",
Value: c.RateLimit,
}, {
@@ -154,15 +155,12 @@ func (c *configuration) validate() (err error) {
Value: c.AdditionalMetricsInfo,
}}
// TODO(a.garipov): Use errors.Join everywhere.
var errs []error
for _, kv := range validators {
err = kv.Value.validate()
if err != nil {
return fmt.Errorf("%s: %w", kv.Key, err)
}
errs = validate.Append(errs, kv.Key, kv.Value)
}
return nil
return errors.Join(errs...)
}
// isProfilesEnabled returns true if there is at least one server group with

View File

@@ -5,8 +5,9 @@ import (
"net"
"net/netip"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
)
// connCheckConfig is the connectivity check configuration.
@@ -19,18 +20,15 @@ type connCheckConfig struct {
}
// type check
var _ validator = (*connCheckConfig)(nil)
var _ validate.Interface = (*connCheckConfig)(nil)
// validate implements the [validator] interface for *connCheckConfig.
func (c *connCheckConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *connCheckConfig.
func (c *connCheckConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case c.ProbeIPv4 == netip.AddrPort{}:
return fmt.Errorf("probe_ipv4: %w", errors.ErrEmptyValue)
}
return nil
return validate.NotEmpty("probe_ipv4", c.ProbeIPv4)
}
// connectivityCheck performs connectivity checks for bind addresses with
@@ -38,7 +36,10 @@ func (c *connCheckConfig) validate() (err error) {
// server bind addresses looking up for IPv6 addresses. If an IPv6 address is
// found, then additionally to a general probe to IPv4 it will perform a check
// to IPv6 probe address.
func connectivityCheck(srvGrps []*agd.ServerGroup, connCheck *connCheckConfig) (err error) {
func connectivityCheck(
srvGrps []*dnssvc.ServerGroupConfig,
connCheck *connCheckConfig,
) (err error) {
probeIPv4 := net.TCPAddrFromAddrPort(connCheck.ProbeIPv4)
// General check to IPv4 probe address.
@@ -78,7 +79,7 @@ func connectivityCheck(srvGrps []*agd.ServerGroup, connCheck *connCheckConfig) (
// requireIPv6ConnCheck returns true if provided serverGroups require IPv6
// connectivity check.
func requireIPv6ConnCheck(serverGroups []*agd.ServerGroup) (ok bool) {
func requireIPv6ConnCheck(serverGroups []*dnssvc.ServerGroupConfig) (ok bool) {
for _, srvGrp := range serverGroups {
for _, s := range srvGrp.Servers {
if s.HasIPv6() {

View File

@@ -1,29 +0,0 @@
package cmd
import (
"context"
"time"
)
// defaultTimeout is the timeout used for some operations where another timeout
// hasn't been defined yet.
const defaultTimeout = 30 * time.Second
// contextConstructor is a type alias for functions that can create a context.
type contextConstructor = func() (ctx context.Context, cancel context.CancelFunc)
// ctxWithDefaultTimeout is a helper function that returns a context with
// timeout set to defaultTimeout.
func ctxWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), defaultTimeout)
}
// newCtxWithTimeoutCons returns a context constructor that creates a simple
// context with the given timeout.
func newCtxWithTimeoutCons(timeout time.Duration) (c contextConstructor) {
parent := context.Background()
return func() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(parent, timeout)
}
}

View File

@@ -9,9 +9,11 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/validate"
"github.com/miekg/dns"
)
@@ -19,7 +21,7 @@ import (
type ddrConfig struct {
// DeviceRecords are used to respond to DDR queries from recognized devices.
// The keys of the map are device ID wildcards.
DeviceRecords map[string]*ddrRecord `yaml:"device_records"`
DeviceRecords ddrDeviceRecords `yaml:"device_records"`
// PublicRecords are used to respond to DDR queries from unrecognized
// devices. The keys of the map are the public domain names.
@@ -30,10 +32,42 @@ type ddrConfig struct {
Enabled bool `yaml:"enabled"`
}
// ddrDeviceRecords is a mapping of wildcard domains to the DDR records for
// devices using that domain.
type ddrDeviceRecords map[string]*ddrRecord
// type check
var _ validate.Interface = ddrDeviceRecords(nil)
// Validate implements the [validate.Interface] interface for ddrDeviceRecords.
func (recs ddrDeviceRecords) Validate() (err error) {
var errs []error
for wildcard, r := range recs {
if !strings.HasPrefix(wildcard, "*.") {
errs = append(errs, fmt.Errorf("wildcard %q: not a wildcard", wildcard))
continue
}
domainSuf := wildcard[2:]
err = netutil.ValidateHostname(domainSuf)
if err != nil {
errs = append(errs, fmt.Errorf("wildcard %q: %w", wildcard, err))
}
err = r.Validate()
if err != nil {
errs = append(errs, fmt.Errorf("wildcard %q: %w", wildcard, err))
}
}
return errors.Join(errs...)
}
// toInternal returns the DDR configuration. messages must not be nil. c must
// be valid.
func (c *ddrConfig) toInternal(msgs *dnsmsg.Constructor) (conf *agd.DDR) {
conf = &agd.DDR{
func (c *ddrConfig) toInternal(msgs *dnsmsg.Constructor) (conf *dnssvc.DDRConfig) {
conf = &dnssvc.DDRConfig{
Enabled: c.Enabled,
}
@@ -99,34 +133,31 @@ func appendDDRSVCBTmpls(
}
// type check
var _ validator = (*ddrConfig)(nil)
var _ validate.Interface = (*ddrConfig)(nil)
// validate implements the [validator] interface for *ddrConfig.
func (c *ddrConfig) validate() (err error) {
// Validate implements the [validate.Interface] interface for *ddrConfig.
func (c *ddrConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
for wildcard, r := range c.DeviceRecords {
if !strings.HasPrefix(wildcard, "*.") {
return fmt.Errorf("device_records: record for wildcard %q: not a wildcard", wildcard)
}
var errs []error
domainSuf := wildcard[2:]
err = errors.Join(netutil.ValidateHostname(domainSuf), r.validate())
if err != nil {
return fmt.Errorf("device_records: wildcard %q: %w", wildcard, err)
}
}
errs = validate.Append(errs, "device_records", c.DeviceRecords)
for domain, r := range c.PublicRecords {
err = errors.Join(netutil.ValidateHostname(domain), r.validate())
err = netutil.ValidateHostname(domain)
if err != nil {
return fmt.Errorf("public_records: domain %q: %w", domain, err)
errs = append(errs, fmt.Errorf("public_records: domain %q: %w", domain, err))
}
err = r.Validate()
if err != nil {
errs = append(errs, fmt.Errorf("public_records: domain %q: %w", domain, err))
}
}
return nil
return errors.Join(errs...)
}
// ddrRecord is a DDR record template for responses to DDR queries from both
@@ -158,34 +189,42 @@ type ddrRecord struct {
}
// type check
var _ validator = (*ddrRecord)(nil)
var _ validate.Interface = (*ddrRecord)(nil)
// validate implements the [validator] interface for *ddrRecord.
func (r *ddrRecord) validate() (err error) {
// Validate implements the [validate.Interface] interface for *ddrRecord.
func (r *ddrRecord) Validate() (err error) {
if r == nil {
return errors.ErrNoValue
}
var errs []error
// TODO(a.garipov): Consider validating that r.DoHPath is a valid RFC 6570
// URI template.
if r.HTTPSPort != 0 && r.DoHPath == "" {
return errors.Error("doh_path: cannot be empty if https_port is set")
errs = append(errs, errors.Error("doh_path: cannot be empty if https_port is set"))
}
// TODO(a.garipov): Merge with [validateAddrs] and [validateNonNilIPs].
for i, addr := range r.IPv4Hints {
if !addr.Is4() {
return fmt.Errorf("ipv4_hints: at index %d: not an ipv4 addr", i)
errs = append(errs, fmt.Errorf("ipv4_hints: at index %d: not an ipv4 addr", i))
}
}
for i, addr := range r.IPv6Hints {
if !addr.Is6() {
return fmt.Errorf("ipv6_hints: at index %d: not an ipv6 addr", i)
errs = append(errs, fmt.Errorf("ipv6_hints: at index %d: not an ipv6 addr", i))
}
}
return r.validatePorts()
err = r.validatePorts()
if err != nil {
// Don't wrap the error, because it's informative enough as is.
errs = append(errs, err)
}
return errors.Join(errs...)
}
// validatePorts returns an error if the DDR record has invalid ports. r must

View File

@@ -1,11 +1,12 @@
package cmd
import (
"fmt"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
"github.com/c2h5oh/datasize"
"github.com/miekg/dns"
)
@@ -34,37 +35,25 @@ type dnsConfig struct {
}
// type check
var _ validator = (*dnsConfig)(nil)
var _ validate.Interface = (*dnsConfig)(nil)
// validate implements the [validator] interface for *dnsConfig.
func (c *dnsConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *dnsConfig.
func (c *dnsConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case c.ReadTimeout.Duration <= 0:
return newNotPositiveError("read_timeout", c.ReadTimeout)
case c.TCPIdleTimeout.Duration <= 0:
return newNotPositiveError("tcp_idle_timeout", c.TCPIdleTimeout)
case c.TCPIdleTimeout.Duration > dnsserver.MaxTCPIdleTimeout:
return fmt.Errorf(
"tcp_idle_timeout: %w: must be less than or equal to %s got %s",
errors.ErrOutOfRange,
dnsserver.MaxTCPIdleTimeout,
c.TCPIdleTimeout,
)
case c.WriteTimeout.Duration <= 0:
return newNotPositiveError("write_timeout", c.WriteTimeout)
case c.HandleTimeout.Duration <= 0:
return newNotPositiveError("handle_timeout", c.HandleTimeout)
case c.MaxUDPResponseSize.Bytes() == 0:
return newNotPositiveError("max_udp_response_size", c.MaxUDPResponseSize)
case c.MaxUDPResponseSize.Bytes() > dns.MaxMsgSize:
return fmt.Errorf(
"max_udp_response_size must be less than %s, got %s",
datasize.ByteSize(dns.MaxMsgSize),
c.MaxUDPResponseSize,
)
default:
return nil
}
return errors.Join(
validate.Positive("read_timeout", c.ReadTimeout),
validate.Positive("tcp_idle_timeout", c.TCPIdleTimeout),
validate.NoGreaterThan(
"tcp_idle_timeout",
time.Duration(c.TCPIdleTimeout),
dnsserver.MaxTCPIdleTimeout,
),
validate.Positive("write_timeout", c.WriteTimeout),
validate.Positive("handle_timeout", c.HandleTimeout),
validate.Positive("max_udp_response_size", c.MaxUDPResponseSize),
validate.NoGreaterThan("max_udp_response_size", c.MaxUDPResponseSize, dns.MaxMsgSize),
)
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
"github.com/ameshkov/dnscrypt/v2"
"gopkg.in/yaml.v2"
)
@@ -63,9 +64,9 @@ func (c *dnsCryptConfig) toInternal() (conf *agd.DNSCryptConfig, err error) {
}, nil
}
// validate returns an error if the DNSCrypt configuration is invalid for the
// given protocol.
func (c *dnsCryptConfig) validate(p serverProto) (err error) {
// validateForProtocol returns an error if the DNSCrypt configuration is invalid
// for the given protocol.
func (c *dnsCryptConfig) validateForProtocol(p serverProto) (err error) {
needsDC := p == srvProtoDNSCrypt
switch {
case c == nil:
@@ -91,18 +92,18 @@ func (c *dnsCryptConfig) validate(p serverProto) (err error) {
return nil
}
// validateDNSCrypt validates DNSCrypt resolver configuration.
// validateDNSCrypt validates DNSCrypt resolver configuration. rc must not be
// nil.
func validateDNSCrypt(rc *dnscrypt.ResolverConfig) (err error) {
switch {
case rc.ProviderName == "":
return errors.Error("no provider_name")
case rc.PublicKey == "":
return errors.Error("no public_key")
case rc.PrivateKey == "":
return errors.Error("no private_key")
case rc.EsVersion != dnscrypt.XChacha20Poly1305 && rc.EsVersion != dnscrypt.XSalsa20Poly1305:
return fmt.Errorf("bad es_version: %d", rc.EsVersion)
default:
return nil
errs := []error{
validate.NotEmpty("provider_name", rc.ProviderName),
validate.NotEmpty("public_key", rc.PublicKey),
validate.NotEmpty("private_key", rc.PrivateKey),
}
if rc.EsVersion != dnscrypt.XChacha20Poly1305 && rc.EsVersion != dnscrypt.XSalsa20Poly1305 {
errs = append(errs, fmt.Errorf("es_version: %w: %d", errors.ErrBadEnumValue, rc.EsVersion))
}
return errors.Join(errs...)
}

View File

@@ -1,12 +1,8 @@
package cmd
import (
"log/slog"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsdb"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/validate"
)
// dnsDBConfig is the configuration of the DNSDB module.
@@ -19,37 +15,15 @@ type dnsDBConfig struct {
}
// type check
var _ validator = (*dnsDBConfig)(nil)
var _ validate.Interface = (*dnsDBConfig)(nil)
// validate implements the [validator] interface for *dnsDBConfig.
func (c *dnsDBConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *dnsDBConfig.
func (c *dnsDBConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case !c.Enabled:
return nil
case c.MaxSize <= 0:
return newNotPositiveError("size", c.MaxSize)
default:
} else if !c.Enabled {
return nil
}
}
// toInternal builds and returns an anonymous statistics collector. c must be
// valid.
func (c *dnsDBConfig) toInternal(
baseLogger *slog.Logger,
errColl errcoll.Interface,
) (d dnsdb.Interface) {
if !c.Enabled {
return dnsdb.Empty{}
}
db := dnsdb.New(&dnsdb.DefaultConfig{
Logger: baseLogger.With(slogutil.KeyPrefix, "dnsdb"),
ErrColl: errColl,
MaxSize: c.MaxSize,
})
return db
return validate.Positive("max_size", c.MaxSize)
}

View File

@@ -15,11 +15,11 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/version"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
"github.com/c2h5oh/datasize"
"github.com/caarlos0/env/v7"
"github.com/getsentry/sentry-go"
@@ -53,11 +53,12 @@ type environment struct {
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"`
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"`
RedisKeyPrefix string `env:"REDIS_KEY_PREFIX" envDefault:"agdns"`
QueryLogPath string `env:"QUERYLOG_PATH" envDefault:"./querylog.jsonl"`
SSLKeyLogFile string `env:"SSL_KEY_LOG_FILE"`
SentryDSN string `env:"SENTRY_DSN" envDefault:"stderr"`
WebStaticDir string `env:"WEB_STATIC_DIR"`
@@ -100,11 +101,10 @@ func parseEnvironment() (envs *environment, err error) {
}
// type check
var _ validator = (*environment)(nil)
var _ validate.Interface = (*environment)(nil)
// validate implements the [validator] interface for *environment.
func (envs *environment) validate() (err error) {
// TODO(a.garipov): Use a similar approach with errors.Join everywhere.
// Validate implements the [validate.Interface] interface for *environment.
func (envs *environment) Validate() (err error) {
var errs []error
errs = envs.validateHTTPURLs(errs)
@@ -112,19 +112,24 @@ func (envs *environment) validate() (err error) {
if s := envs.FilterIndexURL.Scheme; !strings.EqualFold(s, urlutil.SchemeFile) &&
!urlutil.IsValidHTTPURLScheme(s) {
errs = append(errs, fmt.Errorf(
"env %s: not a valid http(s) url or file uri",
"%s: not a valid http(s) url or file uri",
"FILTER_INDEX_URL",
))
}
err = envs.validateWebStaticDir()
if err != nil {
errs = append(errs, fmt.Errorf("env WEB_STATIC_DIR: %w", err))
errs = append(errs, fmt.Errorf("WEB_STATIC_DIR: %w", err))
}
_, err = slogutil.NewFormat(envs.LogFormat)
if err != nil {
errs = append(errs, fmt.Errorf("LOG_FORMAT: %w", err))
}
_, err = slogutil.VerbosityToLevel(envs.Verbosity)
if err != nil {
errs = append(errs, fmt.Errorf("env VERBOSE: %w", err))
errs = append(errs, fmt.Errorf("VERBOSE: %w", err))
}
return errors.Join(errs...)
@@ -234,7 +239,7 @@ func (envs *environment) validateWebStaticDir() (err error) {
func (envs *environment) validateFromValidConfig(conf *configuration) (err error) {
var errs []error
switch typ := conf.Check.RemoteKV.Type; typ {
switch typ := conf.Check.KV.Type; typ {
case kvModeBackend:
errs = envs.validateBackendKV(errs)
case kvModeCache:
@@ -248,13 +253,13 @@ func (envs *environment) validateFromValidConfig(conf *configuration) (err error
if conf.isProfilesEnabled() {
errs = envs.validateProfilesURLs(errs)
if envs.ProfilesMaxRespSize > math.MaxInt {
errs = append(errs, fmt.Errorf(
"PROFILES_MAX_RESP_SIZE: %w: must be less than or equal to %s, got %s",
errors.ErrOutOfRange,
datasize.ByteSize(math.MaxInt),
envs.ProfilesMaxRespSize,
))
err = validate.NoGreaterThan(
"PROFILES_MAX_RESP_SIZE",
envs.ProfilesMaxRespSize,
math.MaxInt,
)
if err != nil {
errs = append(errs, err)
}
}
@@ -268,8 +273,9 @@ func (envs *environment) validateFromValidConfig(conf *configuration) (err error
func (envs *environment) validateCache(errs []error) (res []error) {
res = errs
if envs.DNSCheckCacheKVSize <= 0 {
err := newNotPositiveError("DNSCHECK_CACHE_KV_SIZE", envs.DNSCheckCacheKVSize)
err := validate.Positive("env DNSCHECK_CACHE_KV_SIZE", envs.DNSCheckCacheKVSize)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
res = append(res, err)
}
@@ -281,23 +287,23 @@ func (envs *environment) validateCache(errs []error) (res []error) {
func (envs *environment) validateRedis(errs []error) (res []error) {
res = errs
if envs.RedisAddr == "" {
err := fmt.Errorf("REDIS_ADDR: %w", errors.ErrEmptyValue)
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 envs.RedisIdleTimeout.Duration <= 0 {
err := newNotPositiveError("REDIS_IDLE_TIMEOUT", envs.RedisIdleTimeout)
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 envs.RedisMaxActive < 0 {
err := newNegativeError("REDIS_MAX_ACTIVE", envs.RedisMaxActive)
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 envs.RedisMaxIdle < 0 {
err := newNegativeError("REDIS_MAX_IDLE", envs.RedisMaxIdle)
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)
}
@@ -385,31 +391,11 @@ func (envs *environment) validateRateLimitURLs(
return errs
}
// configureLogs sets the configuration for the plain text logs. It also
// returns a [slog.Logger] for code that uses it.
func (envs *environment) configureLogs() (slogLogger *slog.Logger) {
var flags int
if envs.LogTimestamp {
flags = log.LstdFlags | log.Lmicroseconds
}
log.SetFlags(flags)
lvl := errors.Must(slogutil.VerbosityToLevel(envs.Verbosity))
if lvl < slog.LevelInfo {
log.SetLevel(log.DEBUG)
}
return slogutil.New(&slogutil.Config{
Output: os.Stdout,
Format: slogutil.FormatAdGuardLegacy,
Level: lvl,
AddTimestamp: bool(envs.LogTimestamp),
})
}
// buildErrColl builds and returns an error collector from environment.
func (envs *environment) buildErrColl() (errColl errcoll.Interface, err error) {
// baseLogger must not be nil.
func (envs *environment) buildErrColl(
baseLogger *slog.Logger,
) (errColl errcoll.Interface, err error) {
dsn := envs.SentryDSN
if dsn == "stderr" {
return errcoll.NewWriterErrorCollector(os.Stderr), nil
@@ -424,7 +410,9 @@ func (envs *environment) buildErrColl() (errColl errcoll.Interface, err error) {
return nil, err
}
return errcoll.NewSentryErrorCollector(cli), nil
l := baseLogger.With(slogutil.KeyPrefix, "sentry_errcoll")
return errcoll.NewSentryErrorCollector(cli, l), nil
}
// debugConf returns a debug HTTP service configuration from environment.

View File

@@ -2,25 +2,17 @@ package cmd
import (
"context"
"fmt"
"log/slog"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/timeutil"
"golang.org/x/exp/constraints"
)
// validator is the interface for configuration entities that can validate
// themselves.
type validator interface {
// validate returns an error if the entity isn't valid.
validate() (err error)
}
// reportPanics reports all panics in Main using the Sentry client, logs them,
// and repanics. It should be called in a defer.
//
// TODO(a.garipov): Consider switching to pure Sentry.
func reportPanics(ctx context.Context, errColl errcoll.Interface, l *slog.Logger) {
v := recover()
if v == nil {
@@ -39,31 +31,3 @@ func reportPanics(ctx context.Context, errColl errcoll.Interface, l *slog.Logger
panic(v)
}
// numberOrDuration is the constraint for integer types along with
// [timeutil.Duration].
type numberOrDuration interface {
constraints.Integer | timeutil.Duration
}
// newNotPositiveError returns an error about the value that must be positive
// but isn't. prop is the name of the property to mention in the error message.
// The returned error has underlying value of [errors.ErrNotPositive].
func newNotPositiveError[T numberOrDuration](prop string, v T) (err error) {
if s, ok := any(v).(fmt.Stringer); ok {
return fmt.Errorf("%s: %w: got %s", prop, errors.ErrNotPositive, s)
}
return fmt.Errorf("%s: %w: got %d", prop, errors.ErrNotPositive, v)
}
// newNegativeError returns an error about the value that must be non-negative
// but isn't. prop is the name of the property to mention in the error message.
// The returned error has underlying value of [errors.ErrNegative].
func newNegativeError[T numberOrDuration](prop string, v T) (err error) {
if s, ok := any(v).(fmt.Stringer); ok {
return fmt.Errorf("%s: %w: got %s", prop, errors.ErrNegative, s)
}
return fmt.Errorf("%s: %w: got %d", prop, errors.ErrNegative, v)
}

View File

@@ -1,10 +1,9 @@
package cmd
import (
"fmt"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
"github.com/c2h5oh/datasize"
)
@@ -63,33 +62,30 @@ type filtersConfig struct {
}
// type check
var _ validator = (*filtersConfig)(nil)
var _ validate.Interface = (*filtersConfig)(nil)
// validate implements the [validator] interface for *filtersConfig.
func (c *filtersConfig) validate() (err error) {
// Validate implements the [validate.Interface] interface for *filtersConfig.
func (c *filtersConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
errs := []error{
validatePositive("custom_filter_cache_size", c.CustomFilterCacheSize),
validatePositive("safe_search_cache_size", c.SafeSearchCacheSize),
validatePositive("response_ttl", c.ResponseTTL),
validatePositive("refresh_interval", c.RefreshIvl),
validatePositive("refresh_timeout", c.RefreshTimeout),
validatePositive("index_refresh_timeout", c.IndexRefreshTimeout),
validatePositive("rule_list_refresh_timeout", c.RuleListRefreshTimeout),
validatePositive("max_size", c.MaxSize),
validate.Positive("custom_filter_cache_size", c.CustomFilterCacheSize),
validate.Positive("safe_search_cache_size", c.SafeSearchCacheSize),
validate.Positive("response_ttl", c.ResponseTTL),
validate.Positive("refresh_interval", c.RefreshIvl),
validate.Positive("refresh_timeout", c.RefreshTimeout),
validate.Positive("index_refresh_timeout", c.IndexRefreshTimeout),
validate.Positive("rule_list_refresh_timeout", c.RuleListRefreshTimeout),
validate.Positive("max_size", c.MaxSize),
}
if !c.EDEEnabled && c.SDEEnabled {
errs = append(errs, errors.Error("ede must be enabled to enable sde"))
}
err = c.RuleListCache.validate()
if err != nil {
errs = append(errs, fmt.Errorf("rule_list_cache: %w", err))
}
errs = validate.Append(errs, "rule_list_cache", c.RuleListCache)
return errors.Join(errs...)
}
@@ -107,16 +103,13 @@ type fltRuleListCache struct {
}
// type check
var _ validator = (*fltRuleListCache)(nil)
var _ validate.Interface = (*fltRuleListCache)(nil)
// validate implements the [validator] interface for *fltRuleListCache.
func (c *fltRuleListCache) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *fltRuleListCache.
func (c *fltRuleListCache) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case c.Size <= 0:
return newNotPositiveError("size", c.Size)
default:
return nil
}
return validate.Positive("size", c.Size)
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/filter"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
)
// filteringGroup represents a set of filtering settings.
@@ -57,6 +58,38 @@ func (c *fltGrpRuleLists) toInternal(ids []filter.ID) (fltConf *filter.ConfigRul
}
}
// type check
var _ validate.Interface = (*fltGrpRuleLists)(nil)
// Validate implements the [validate.Interface] interface for *fltGrpRuleLists.
func (c *fltGrpRuleLists) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
var errs []error
fltIDs := container.NewMapSet[string]()
for i, fltID := range c.IDs {
if fltIDs.Has(fltID) {
err = fmt.Errorf("at index %d: id: %w: %q", i, errors.ErrDuplicated, fltID)
errs = append(errs, err)
continue
}
_, err = filter.NewID(fltID)
if err != nil {
errs = append(errs, fmt.Errorf("rule_lists: at index %d: id: %w", i, err))
continue
}
fltIDs.Add(fltID)
}
return errors.Join(errs...)
}
// fltGrpParental contains parental protection configuration for a filtering
// group.
type fltGrpParental struct {
@@ -116,42 +149,26 @@ func (c *fltGrpSafeBrowsing) toInternal() (fltConf *filter.ConfigSafeBrowsing) {
}
// type check
var _ validator = (*filteringGroup)(nil)
var _ validate.Interface = (*filteringGroup)(nil)
// validate implements the [validator] interface for *filteringGroup.
func (g *filteringGroup) validate() (err error) {
switch {
case g == nil:
// Validate implements the [validate.Interface] interface for *filteringGroup.
func (g *filteringGroup) Validate() (err error) {
if g == nil {
return errors.ErrNoValue
case g.Parental == nil:
return fmt.Errorf("parental: %w", errors.ErrNoValue)
case g.RuleLists == nil:
return fmt.Errorf("rule_lists: %w", errors.ErrNoValue)
case g.SafeBrowsing == nil:
return fmt.Errorf("safe_browsing: %w", errors.ErrNoValue)
case g.ID == "":
return fmt.Errorf("id: %w", errors.ErrEmptyValue)
}
fltIDs := container.NewMapSet[string]()
for i, fltID := range g.RuleLists.IDs {
if fltIDs.Has(fltID) {
return fmt.Errorf("rule_lists: at index %d: id: %w: %q", i, errors.ErrDuplicated, fltID)
}
_, err = filter.NewID(fltID)
if err != nil {
return fmt.Errorf("rule_lists: at index %d: %w", i, err)
}
fltIDs.Add(fltID)
errs := []error{
validate.NotEmpty("id", g.ID),
validate.NotNil("parental", g.Parental),
validate.NotNil("safe_browsing", g.SafeBrowsing),
}
return nil
errs = validate.Append(errs, "rule_lists", g.RuleLists)
return errors.Join(errs...)
}
// filteringGroups are the filtering settings. A valid instance of
// filteringGroups has no nil items.
// filteringGroups are the filtering settings. All items must not be nil.
type filteringGroups []*filteringGroup
// toInternal converts groups to the filtering groups for the DNS server.
@@ -191,27 +208,32 @@ func (groups filteringGroups) toInternal(
}
// type check
var _ validator = filteringGroups(nil)
var _ validate.Interface = filteringGroups(nil)
// validate implements the [validator] interface for filteringGroups.
func (groups filteringGroups) validate() (err error) {
// Validate implements the [validate.Interface] interface for filteringGroups.
func (groups filteringGroups) Validate() (err error) {
if len(groups) == 0 {
return fmt.Errorf("filtering_groups: %w", errors.ErrEmptyValue)
return errors.ErrEmptyValue
}
var errs []error
ids := container.NewMapSet[string]()
for i, g := range groups {
err = g.validate()
err = g.Validate()
if err != nil {
return fmt.Errorf("at index %d: %w", i, err)
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
continue
}
if ids.Has(string(g.ID)) {
return fmt.Errorf("at index %d: id: %w: %q", i, errors.ErrDuplicated, g.ID)
errs = append(errs, fmt.Errorf("at index %d: %w: %q", i, errors.ErrDuplicated, g.ID))
continue
}
ids.Add(g.ID)
}
return nil
return errors.Join(errs...)
}

View File

@@ -3,6 +3,7 @@ package cmd
import (
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
)
// geoIPConfig is the GeoIP database configuration.
@@ -23,22 +24,19 @@ type geoIPConfig struct {
}
// type check
var _ validator = (*geoIPConfig)(nil)
var _ validate.Interface = (*geoIPConfig)(nil)
// validate implements the [validator] interface for *geoIPConfig.
func (c *geoIPConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *geoIPConfig.
func (c *geoIPConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case c.HostCacheSize <= 0:
// Note that while geoip.File can work with an empty host cache, that
// feature is only used for tests.
return newNotPositiveError("host_cache_size", c.HostCacheSize)
case c.IPCacheSize <= 0:
return newNotPositiveError("ip_cache_size", c.IPCacheSize)
case c.RefreshIvl.Duration <= 0:
return newNotPositiveError("refresh_interval", c.RefreshIvl)
default:
return nil
}
return errors.Join(
// NOTE: While a [geoip.File] can work with an empty host cache, that
// feature is only used for tests.
validate.Positive("host_cache_size", c.HostCacheSize),
validate.Positive("ip_cache_size", c.IPCacheSize),
validate.Positive("refresh_interval", c.RefreshIvl),
)
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/validate"
)
// interfaceListenersConfig contains the optional configuration for the network
@@ -53,33 +54,37 @@ func (c *interfaceListenersConfig) toInternal(
}
// type check
var _ validator = (*interfaceListenersConfig)(nil)
var _ validate.Interface = (*interfaceListenersConfig)(nil)
// validate implements the [validator] interface for *interfaceListenersConfig.
func (c *interfaceListenersConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for
// *interfaceListenersConfig.
func (c *interfaceListenersConfig) Validate() (err error) {
if c == nil {
// This configuration is optional.
//
// TODO(a.garipov): Consider making required or not relying on nil
// values.
return nil
case c.ChannelBufferSize <= 0:
return newNotPositiveError("channel_buffer_size", c.ChannelBufferSize)
case len(c.List) == 0:
return fmt.Errorf("list: %w", errors.ErrEmptyValue)
default:
// Go on.
}
errs := []error{
validate.Positive("channel_buffer_size", c.ChannelBufferSize),
}
// TODO(a.garipov): Consider adding validate.NotEmptyMap.
if len(c.List) == 0 {
errs = append(errs, fmt.Errorf("list: %w", errors.ErrEmptyValue))
}
// TODO(a.garipov): Consider adding validate.Map.
for _, id := range slices.Sorted(maps.Keys(c.List)) {
err = c.List[id].validate()
err = c.List[id].Validate()
if err != nil {
return fmt.Errorf("interface %q: %w", id, err)
errs = append(errs, fmt.Errorf("interface %q: %w", id, err))
}
}
return err
return errors.Join(errs...)
}
// interfaceListener contains configuration for a single network interface
@@ -93,18 +98,17 @@ type interfaceListener struct {
}
// type check
var _ validator = (*interfaceListener)(nil)
var _ validate.Interface = (*interfaceListener)(nil)
// validate implements the [validator] interface for *interfaceListener.
func (l *interfaceListener) validate() (err error) {
switch {
case l == nil:
// Validate implements the [validate.Interface] interface for
// *interfaceListener.
func (l *interfaceListener) Validate() (err error) {
if l == nil {
return errors.ErrNoValue
case l.Port == 0:
return fmt.Errorf("port: %w", errors.ErrEmptyValue)
case l.Interface == "":
return fmt.Errorf("interface: %w", errors.ErrEmptyValue)
default:
return nil
}
return errors.Join(
validate.Positive("port", l.Port),
validate.NotEmpty("interface", l.Interface),
)
}

View File

@@ -1,12 +1,12 @@
package cmd
import (
"fmt"
"math"
"github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/netext"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
"github.com/c2h5oh/datasize"
)
@@ -22,29 +22,20 @@ type network struct {
}
// type check
var _ validator = (*interfaceListener)(nil)
var _ validate.Interface = (*network)(nil)
// validate implements the [validator] interface for *network.
func (n *network) validate() (err error) {
const maxBufSize datasize.ByteSize = math.MaxInt32
switch {
case n == nil:
// Validate implements the [validate.Interface] interface for *network.
func (n *network) Validate() (err error) {
if n == nil {
return errors.ErrNoValue
case n.SndBufSize > maxBufSize:
return fmt.Errorf(
"so_sndbuf: %s: must be less than or equal to %s",
errors.ErrOutOfRange,
maxBufSize,
)
case n.RcvBufSize > maxBufSize:
return fmt.Errorf(
"so_rcvbuf: %s: must be less than or equal to %s",
errors.ErrOutOfRange,
maxBufSize,
)
default:
return nil
}
const maxBufSize datasize.ByteSize = math.MaxInt32
return errors.Join(
validate.NoGreaterThan("so_sndbuf", n.SndBufSize, maxBufSize),
validate.NoGreaterThan("so_rcvbuf", n.RcvBufSize, maxBufSize),
)
}
// toInternal converts n to the bindtodevice control configuration and network

View File

@@ -1,9 +1,8 @@
package cmd
import (
"fmt"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
)
// queryLogConfig is the query log configuration.
@@ -13,18 +12,15 @@ type queryLogConfig struct {
}
// type check
var _ validator = (*queryLogConfig)(nil)
var _ validate.Interface = (*queryLogConfig)(nil)
// validate implements the [validator] interface for *queryLogConfig.
func (c *queryLogConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *queryLogConfig.
func (c *queryLogConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case c.File == nil:
return fmt.Errorf("file: %w", errors.ErrNoValue)
default:
return nil
}
return validate.NotNil("file", c.File)
}
// queryLogFileConfig is the JSONL file query log configuration.

View File

@@ -1,26 +1,21 @@
package cmd
import (
"cmp"
"context"
"fmt"
"log/slog"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/connlimiter"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
"github.com/c2h5oh/datasize"
)
// Constants for rate limit settings endpoints.
const (
rlAllowlistTypeBackend = "backend"
rlAllowlistTypeConsul = "consul"
)
// rateLimitConfig is the configuration of the instance's rate limiting.
type rateLimitConfig struct {
// AllowList is the allowlist of clients.
@@ -78,18 +73,18 @@ type rateLimitOptions struct {
}
// type check
var _ validator = (*rateLimitOptions)(nil)
var _ validate.Interface = (*rateLimitOptions)(nil)
// validate implements the [validator] interface for *rateLimitOptions.
func (o *rateLimitOptions) validate() (err error) {
// Validate implements the [validate.Interface] interface for *rateLimitOptions.
func (o *rateLimitOptions) Validate() (err error) {
if o == nil {
return errors.ErrNoValue
}
return cmp.Or(
validatePositive("count", o.Count),
validatePositive("interval", o.Interval),
validatePositive("subnet_key_len", o.SubnetKeyLen),
return errors.Join(
validate.Positive("count", o.Count),
validate.Positive("interval", o.Interval),
validate.Positive("subnet_key_len", o.SubnetKeyLen),
)
}
@@ -99,13 +94,13 @@ func (c *rateLimitConfig) toInternal(al ratelimit.Allowlist) (conf *ratelimit.Ba
return &ratelimit.BackoffConfig{
Allowlist: al,
ResponseSizeEstimate: c.ResponseSizeEstimate,
Duration: c.BackoffDuration.Duration,
Period: c.BackoffPeriod.Duration,
Duration: time.Duration(c.BackoffDuration),
Period: time.Duration(c.BackoffPeriod),
IPv4Count: c.IPv4.Count,
IPv4Interval: c.IPv4.Interval.Duration,
IPv4Interval: time.Duration(c.IPv4.Interval),
IPv4SubnetKeyLen: c.IPv4.SubnetKeyLen,
IPv6Count: c.IPv6.Count,
IPv6Interval: c.IPv6.Interval.Duration,
IPv6Interval: time.Duration(c.IPv6.Interval),
IPv6SubnetKeyLen: c.IPv6.SubnetKeyLen,
Count: c.BackoffCount,
RefuseANY: c.RefuseANY,
@@ -113,26 +108,29 @@ func (c *rateLimitConfig) toInternal(al ratelimit.Allowlist) (conf *ratelimit.Ba
}
// type check
var _ validator = (*rateLimitConfig)(nil)
var _ validate.Interface = (*rateLimitConfig)(nil)
// validate implements the [validator] interface for *rateLimitConfig.
func (c *rateLimitConfig) validate() (err error) {
// Validate implements the [validate.Interface] interface for *rateLimitConfig.
func (c *rateLimitConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
return cmp.Or(
validateProp("allowlist", c.Allowlist.validate),
validateProp("connection_limit", c.ConnectionLimit.validate),
validateProp("ipv4", c.IPv4.validate),
validateProp("ipv6", c.IPv6.validate),
validateProp("quic", c.QUIC.validate),
validateProp("tcp", c.TCP.validate),
validatePositive("backoff_count", c.BackoffCount),
validatePositive("backoff_duration", c.BackoffDuration),
validatePositive("backoff_period", c.BackoffPeriod),
validatePositive("response_size_estimate", c.ResponseSizeEstimate),
)
errs := []error{
validate.Positive("backoff_count", c.BackoffCount),
validate.Positive("backoff_duration", c.BackoffDuration),
validate.Positive("backoff_period", c.BackoffPeriod),
validate.Positive("response_size_estimate", c.ResponseSizeEstimate),
}
errs = validate.Append(errs, "allowlist", c.Allowlist)
errs = validate.Append(errs, "connection_limit", c.ConnectionLimit)
errs = validate.Append(errs, "ipv4", c.IPv4)
errs = validate.Append(errs, "ipv6", c.IPv6)
errs = validate.Append(errs, "quic", c.QUIC)
errs = validate.Append(errs, "tcp", c.TCP)
return errors.Join(errs...)
}
// allowListConfig is the consul allow list configuration.
@@ -148,23 +146,35 @@ type allowListConfig struct {
RefreshIvl timeutil.Duration `yaml:"refresh_interval"`
}
// type check
var _ validator = (*allowListConfig)(nil)
// Constants for rate limit settings endpoints.
const (
rlAllowlistTypeBackend = "backend"
rlAllowlistTypeConsul = "consul"
)
// validate implements the [validator] interface for *allowListConfig.
func (c *allowListConfig) validate() (err error) {
// type check
var _ validate.Interface = (*allowListConfig)(nil)
// Validate implements the [validate.Interface] interface for *allowListConfig.
func (c *allowListConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
switch c.Type {
case rlAllowlistTypeBackend, rlAllowlistTypeConsul:
// Go on.
default:
return fmt.Errorf("type: %w: %q", errors.ErrBadEnumValue, c.Type)
errs := []error{
validate.Positive("refresh_interval", c.RefreshIvl),
}
return validatePositive("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...)
}
// connLimitConfig is the configuration structure for the stream-connection
@@ -187,44 +197,40 @@ type connLimitConfig struct {
Enabled bool `yaml:"enabled"`
}
// toInternal converts c to the connection limiter to use. c must be valid.
func (c *connLimitConfig) toInternal(logger *slog.Logger) (l *connlimiter.Limiter) {
if !c.Enabled {
return nil
// toInternal converts c to a valid connection limiter config. c must be valid.
// mtrc must not be nil.
func (c *connLimitConfig) toInternal(
ctx context.Context,
logger *slog.Logger,
mtrc connlimiter.Metrics,
) (l *connlimiter.Config) {
mtrc.SetStopLimit(ctx, c.Stop)
mtrc.SetResumeLimit(ctx, c.Resume)
return &connlimiter.Config{
Metrics: mtrc,
Logger: logger.With(slogutil.KeyPrefix, "connlimiter"),
Stop: c.Stop,
Resume: c.Resume,
}
l, err := connlimiter.New(&connlimiter.Config{
Logger: logger.With(slogutil.KeyPrefix, "connlimiter"),
Stop: c.Stop,
Resume: c.Resume,
})
if err != nil {
panic(err)
}
metrics.ConnLimiterLimits.WithLabelValues("stop").Set(float64(c.Stop))
metrics.ConnLimiterLimits.WithLabelValues("resume").Set(float64(c.Resume))
return l
}
// type check
var _ validator = (*connLimitConfig)(nil)
var _ validate.Interface = (*connLimitConfig)(nil)
// validate implements the [validator] interface for *connLimitConfig.
func (c *connLimitConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *connLimitConfig.
func (c *connLimitConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case !c.Enabled:
return nil
case c.Stop == 0:
return newNotPositiveError("stop", c.Stop)
case c.Resume > c.Stop:
return errors.Error("resume: must be less than or equal to stop")
default:
} else if !c.Enabled {
return nil
}
return errors.Join(
validate.Positive("stop", c.Stop),
validate.Positive("resume", c.Resume),
validate.NoGreaterThan("resume", c.Resume, c.Stop),
)
}
// ratelimitTCPConfig is the configuration of TCP pipeline limiting.
@@ -238,15 +244,15 @@ type ratelimitTCPConfig struct {
}
// type check
var _ validator = (*ratelimitTCPConfig)(nil)
var _ validate.Interface = (*ratelimitTCPConfig)(nil)
// validate implements the [validator] interface for *ratelimitTCPConfig.
func (c *ratelimitTCPConfig) validate() (err error) {
// Validate implements the [validate.Interface] interface for *ratelimitTCPConfig.
func (c *ratelimitTCPConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
return validatePositive("max_pipeline_count", c.MaxPipelineCount)
return validate.Positive("max_pipeline_count", c.MaxPipelineCount)
}
// ratelimitQUICConfig is the configuration of QUIC streams limiting.
@@ -260,13 +266,13 @@ type ratelimitQUICConfig struct {
}
// type check
var _ validator = (*ratelimitQUICConfig)(nil)
var _ validate.Interface = (*ratelimitQUICConfig)(nil)
// validate implements the [validator] interface for *ratelimitQUICConfig.
func (c *ratelimitQUICConfig) validate() (err error) {
// Validate implements the [validate.Interface] interface for *ratelimitQUICConfig.
func (c *ratelimitQUICConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
}
return validatePositive("max_streams_per_peer", c.MaxStreamsPerPeer)
return validate.Positive("max_streams_per_peer", c.MaxStreamsPerPeer)
}

View File

@@ -1,10 +1,9 @@
package cmd
import (
"fmt"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
)
// safeBrowsingConfig is the configuration for one of the safe browsing filters.
@@ -30,24 +29,20 @@ type safeBrowsingConfig struct {
}
// type check
var _ validator = (*safeBrowsingConfig)(nil)
var _ validate.Interface = (*safeBrowsingConfig)(nil)
// validate implements the [validator] interface for *safeBrowsingConfig.
func (c *safeBrowsingConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for
// *safeBrowsingConfig.
func (c *safeBrowsingConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case c.BlockHost == "":
return fmt.Errorf("block_host: %w", errors.ErrEmptyValue)
case c.CacheSize <= 0:
return newNotPositiveError("cache_size", c.CacheSize)
case c.CacheTTL.Duration <= 0:
return newNotPositiveError("cache_ttl", c.CacheTTL)
case c.RefreshIvl.Duration <= 0:
return newNotPositiveError("refresh_interval", c.RefreshIvl)
case c.RefreshTimeout.Duration <= 0:
return newNotPositiveError("refresh_timeout", c.RefreshTimeout)
default:
return nil
}
return errors.Join(
validate.NotEmpty("block_host", c.BlockHost),
validate.Positive("cache_size", c.CacheSize),
validate.Positive("cache_ttl", c.CacheTTL),
validate.Positive("refresh_interval", c.RefreshIvl),
validate.Positive("refresh_timeout", c.RefreshTimeout),
)
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net/netip"
"slices"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
@@ -12,6 +13,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
)
// toInternal returns the configuration of DNS servers for a single server
@@ -34,14 +36,14 @@ func (srvs servers) toInternal(
name := agd.ServerName(srv.Name)
dnsSrv := &agd.Server{
Name: name,
ReadTimeout: dnsConf.ReadTimeout.Duration,
WriteTimeout: dnsConf.WriteTimeout.Duration,
ReadTimeout: time.Duration(dnsConf.ReadTimeout),
WriteTimeout: time.Duration(dnsConf.WriteTimeout),
LinkedIPEnabled: srv.LinkedIPEnabled,
Protocol: srv.Protocol.toInternal(),
}
tcpConf := &agd.TCPConfig{
IdleTimeout: dnsConf.TCPIdleTimeout.Duration,
IdleTimeout: time.Duration(dnsConf.TCPIdleTimeout),
MaxPipelineCount: ratelimitConf.TCP.MaxPipelineCount,
MaxPipelineEnabled: ratelimitConf.TCP.Enabled,
}
@@ -113,25 +115,31 @@ func newTLSConfig(
// nil items.
type servers []*server
// validate returns an error if the configuration is invalid.
func (srvs servers) validate() (needsTLS bool, err error) {
// validateWithTLS returns an error if the configuration is invalid.
func (srvs servers) validateWithTLS() (needsTLS bool, err error) {
if len(srvs) == 0 {
return false, errors.Error("no servers")
return false, errors.ErrEmptyValue
}
var errs []error
names := container.NewMapSet[string]()
for i, s := range srvs {
if s == nil {
return false, fmt.Errorf("at index %d: no server", i)
}
err = s.validate()
err = s.Validate()
if err != nil {
return false, fmt.Errorf("at index %d: %w", i, err)
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
continue
}
if names.Has(s.Name) {
return false, fmt.Errorf("at index %d: name: %w: %q", i, errors.ErrDuplicated, s.Name)
errs = append(errs, fmt.Errorf(
"at index %d: name: %w: %q",
i,
errors.ErrDuplicated,
s.Name,
))
continue
}
names.Add(s.Name)
@@ -139,7 +147,7 @@ func (srvs servers) validate() (needsTLS bool, err error) {
needsTLS = needsTLS || s.Protocol.needsTLS()
}
return needsTLS, nil
return needsTLS, errors.Join(errs...)
}
// serverProto is the type for the server protocols in the on-disk
@@ -181,10 +189,10 @@ func (p serverProto) toInternal() (sp agd.Protocol) {
}
// type check
var _ validator = serverProto("")
var _ validate.Interface = serverProto("")
// validate implements the [validator] interface for serverProto.
func (p serverProto) validate() (err error) {
// Validate implements the [validate.Interface] interface for serverProto.
func (p serverProto) Validate() (err error) {
switch p {
case srvProtoDNS,
srvProtoDNSCrypt,
@@ -265,34 +273,35 @@ func (s *server) bindData(
}
// type check
var _ validator = (*server)(nil)
var _ validate.Interface = (*server)(nil)
// validate implements the [validator] interface for *server.
func (s *server) validate() (err error) {
switch {
case s == nil:
// Validate implements the [validate.Interface] interface for *server.
func (s *server) Validate() (err error) {
if s == nil {
return errors.ErrNoValue
case s.Name == "":
return fmt.Errorf("name: %w", errors.ErrEmptyValue)
}
errs := []error{
validate.NotEmpty("name", s.Name),
}
err = s.validateBindData()
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
errs = append(errs, err)
}
err = s.Protocol.validate()
err = s.Protocol.Validate()
if err != nil {
return fmt.Errorf("protocol: %w", err)
errs = append(errs, fmt.Errorf("protocol: %w", err))
}
err = s.DNSCrypt.validate(s.Protocol)
err = s.DNSCrypt.validateForProtocol(s.Protocol)
if err != nil {
return fmt.Errorf("dnscrypt: %w", err)
errs = append(errs, fmt.Errorf("dnscrypt: %w", err))
}
return nil
return errors.Join(errs...)
}
// validateBindData returns an error if the server's binding data aren't valid.
@@ -303,12 +312,8 @@ func (s *server) validateBindData() (err error) {
return errors.Error("bind_addresses and bind_interfaces cannot both be set")
}
err = validateAddrs(s.BindAddresses)
if err != nil {
return fmt.Errorf("bind_addresses: %w", err)
}
return nil
// Don't wrap the error, because it's informative enough as is.
return validateBindAddrs(s.BindAddresses)
}
if !bindIfacesSet {
@@ -323,14 +328,21 @@ func (s *server) validateBindData() (err error) {
)
}
for i, bindIface := range s.BindInterfaces {
err = bindIface.validate()
if err != nil {
return fmt.Errorf("bind_interfaces: at index %d: %w", i, err)
return validate.Slice("bind_interfaces", s.BindInterfaces)
}
// validateBindAddrs returns an error if any of addrs isn't valid.
//
// TODO(a.garipov): Merge with [validateNonNilIPs].
func validateBindAddrs(addrs []netip.AddrPort) (err error) {
var errs []error
for i, a := range addrs {
if !a.IsValid() {
errs = append(errs, fmt.Errorf("bind_addresses: at index %d: invalid addr", i))
}
}
return nil
return errors.Join(errs...)
}
// serverBindInterface contains the data for a network interface binding.
@@ -340,33 +352,40 @@ type serverBindInterface struct {
}
// type check
var _ validator = (*serverBindInterface)(nil)
var _ validate.Interface = (*serverBindInterface)(nil)
// validate implements the [validator] interface for *serverBindInterface.
func (c *serverBindInterface) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *serverBindInterface.
func (c *serverBindInterface) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case c.ID == "":
return fmt.Errorf("id: %w", errors.ErrEmptyValue)
case len(c.Subnets) == 0:
return fmt.Errorf("subnets: %w", errors.ErrEmptyValue)
default:
// Go on.
}
errs := []error{
validate.NotEmpty("id", c.ID),
validate.NotEmptySlice("subnets", c.Subnets),
}
set := container.NewMapSet[netip.Prefix]()
for i, subnet := range c.Subnets {
if !subnet.IsValid() {
return fmt.Errorf("subnets: at index %d: bad subnet", i)
errs = append(errs, fmt.Errorf("subnets: at index %d: bad subnet", i))
continue
}
if set.Has(subnet) {
return fmt.Errorf("subnets: at index %d: %w: %s", i, errors.ErrDuplicated, subnet)
errs = append(errs, fmt.Errorf(
"subnets: at index %d: %w: %s",
i,
errors.ErrDuplicated,
subnet,
))
continue
}
set.Add(subnet)
}
return nil
return errors.Join(errs...)
}

View File

@@ -7,9 +7,11 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/bindtodevice"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/dnssvc"
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
)
// serverGroups are the DNS server groups. A valid instance of serverGroups has
@@ -26,8 +28,8 @@ func (srvGrps serverGroups) toInternal(
fltGrps map[agd.FilteringGroupID]*agd.FilteringGroup,
ratelimitConf *rateLimitConfig,
dnsConf *dnsConfig,
) (svcSrvGrps []*agd.ServerGroup, err error) {
svcSrvGrps = make([]*agd.ServerGroup, len(srvGrps))
) (svcSrvGrps []*dnssvc.ServerGroupConfig, err error) {
svcSrvGrps = make([]*dnssvc.ServerGroupConfig, len(srvGrps))
for i, g := range srvGrps {
fltGrpID := agd.FilteringGroupID(g.FilteringGroup)
_, ok := fltGrps[fltGrpID]
@@ -41,7 +43,7 @@ func (srvGrps serverGroups) toInternal(
return nil, fmt.Errorf("tls %q: %w", g.Name, err)
}
svcSrvGrps[i] = &agd.ServerGroup{
svcSrvGrps[i] = &dnssvc.ServerGroupConfig{
DDR: g.DDR.toInternal(messages),
DeviceDomains: deviceDomains,
Name: agd.ServerGroupName(g.Name),
@@ -65,29 +67,34 @@ func (srvGrps serverGroups) toInternal(
}
// type check
var _ validator = serverGroups(nil)
var _ validate.Interface = serverGroups(nil)
// validate implements the [validator] interface for serverGroups.
func (srvGrps serverGroups) validate() (err error) {
// Validate implements the [validate.Interface] interface for serverGroups.
func (srvGrps serverGroups) Validate() (err error) {
if len(srvGrps) == 0 {
return errors.ErrEmptyValue
}
var errs []error
names := container.NewMapSet[string]()
for i, g := range srvGrps {
err = g.validate()
err = g.Validate()
if err != nil {
return fmt.Errorf("at index %d: %w", i, err)
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
continue
}
if names.Has(g.Name) {
return fmt.Errorf("at index %d: name: %w: %q", i, errors.ErrDuplicated, g.Name)
errs = append(errs, fmt.Errorf("at index %d: %w: %q", i, errors.ErrDuplicated, g.Name))
continue
}
names.Add(g.Name)
}
return nil
return errors.Join(errs...)
}
// serverGroup defines a group of DNS servers all of which use the same
@@ -118,35 +125,32 @@ type serverGroup struct {
}
// type check
var _ validator = (*serverGroup)(nil)
var _ validate.Interface = (*serverGroup)(nil)
// validate implements the [validator] interface for *serverGroup.
func (g *serverGroup) validate() (err error) {
switch {
case g == nil:
// Validate implements the [validate.Interface] interface for *serverGroup.
func (g *serverGroup) Validate() (err error) {
if g == nil {
return errors.ErrNoValue
case g.Name == "":
return fmt.Errorf("name: %w", errors.ErrEmptyValue)
case g.FilteringGroup == "":
return fmt.Errorf("filtering_group: %w", errors.ErrEmptyValue)
}
err = g.DDR.validate()
errs := []error{
validate.NotEmpty("name", g.Name),
validate.NotEmpty("filtering_group", g.FilteringGroup),
}
errs = validate.Append(errs, "ddr", g.DDR)
needsTLS, err := g.Servers.validateWithTLS()
if err != nil {
return fmt.Errorf("ddr: %w", err)
errs = append(errs, fmt.Errorf("servers: %w", err))
}
needsTLS, err := g.Servers.validate()
err = g.TLS.validateIfNecessary(needsTLS)
if err != nil {
return fmt.Errorf("servers: %w", err)
errs = append(errs, fmt.Errorf("tls: %w", err))
}
err = g.TLS.validate(needsTLS)
if err != nil {
return fmt.Errorf("tls: %w", err)
}
return nil
return errors.Join(errs...)
}
// collectSessTicketPaths returns the list of unique session ticket file paths
@@ -154,7 +158,12 @@ func (g *serverGroup) validate() (err error) {
func (srvGrps serverGroups) collectSessTicketPaths() (paths []string) {
set := container.NewSortedSliceSet[string]()
for _, g := range srvGrps {
for _, k := range g.TLS.SessionKeys {
grpTLS := g.TLS
if grpTLS == nil {
continue
}
for _, k := range grpTLS.SessionKeys {
set.Add(k)
}
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/tlsconfig"
"github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/validate"
)
// tlsConfig are the TLS settings of a DNS server, if any.
@@ -53,9 +54,9 @@ func (c *tlsConfig) toInternal(
return deviceDomains, nil
}
// validate returns an error if the TLS configuration is invalid for the given
// protocol.
func (c *tlsConfig) validate(needsTLS bool) (err error) {
// validateIfNecessary returns an error if the TLS configuration is invalid
// depending on whether it is necessary or not.
func (c *tlsConfig) validateIfNecessary(needsTLS bool) (err error) {
switch {
case c == nil:
if needsTLS {
@@ -68,21 +69,20 @@ func (c *tlsConfig) validate(needsTLS bool) (err error) {
return errors.Error("server group does not require tls")
}
if len(c.Certificates) == 0 {
return fmt.Errorf("certificates: %w", errors.ErrEmptyValue)
var errs []error
if err = validate.NotEmptySlice("certificates", c.Certificates); err != nil {
// Don't wrap the error, because it's informative enough as is.
errs = append(errs, err)
}
err = c.Certificates.validate()
if err != nil {
return fmt.Errorf("certificates: %w", err)
}
errs = validate.Append(errs, "certificates", c.Certificates)
err = validateDeviceIDWildcards(c.DeviceIDWildcards)
if err != nil {
return fmt.Errorf("device_id_wildcards: %w", err)
errs = append(errs, fmt.Errorf("device_id_wildcards: %w", err))
}
return nil
return errors.Join(errs...)
}
// validateDeviceIDWildcards returns an error if the device ID domain wildcards
@@ -126,11 +126,7 @@ func (certs tlsConfigCerts) store(ctx context.Context, tlsMgr tlsconfig.Manager)
}
}
if len(errs) != 0 {
return errors.Join(errs...)
}
return nil
return errors.Join(errs...)
}
// toInternal is like [tlsConfigCerts.store] but it also returns the TLS
@@ -153,20 +149,31 @@ func (certs tlsConfigCerts) toInternal(
}
// type check
var _ validator = tlsConfigCerts(nil)
var _ validate.Interface = tlsConfigCerts(nil)
// validate implements the [validator] interface for tlsConfigCerts.
func (certs tlsConfigCerts) validate() (err error) {
// Validate implements the [validate.Interface] interface for tlsConfigCerts.
// certs may be nil.
func (certs tlsConfigCerts) Validate() (err error) {
if len(certs) == 0 {
return nil
}
var errs []error
for i, c := range certs {
switch {
case c == nil:
return fmt.Errorf("at index %d: %w", i, errors.ErrNoValue)
case c.Certificate == "":
return fmt.Errorf("at index %d: certificate: %w", i, errors.ErrEmptyValue)
case c.Key == "":
return fmt.Errorf("at index %d: key: %w", i, errors.ErrEmptyValue)
if c == nil {
errs = append(errs, fmt.Errorf("at index %d: %w", i, errors.ErrNoValue))
continue
}
if err = validate.NotEmpty("certificate", c.Certificate); err != nil {
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
}
if err = validate.NotEmpty("key", c.Key); err != nil {
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
}
}
return nil
return errors.Join(errs...)
}

View File

@@ -1,7 +1,6 @@
package cmd
import (
"cmp"
"fmt"
"log/slog"
"net/netip"
@@ -9,15 +8,15 @@ import (
"strings"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/forward"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus"
dnssvcprom "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/prometheus"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/contextutil"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/service"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
)
// upstreamConfig is the upstream module configuration.
@@ -35,57 +34,45 @@ type upstreamConfig struct {
// toInternal converts c to the data storage configuration for the DNS server.
// c must be valid.
func (c *upstreamConfig) toInternal(logger *slog.Logger) (fwdConf *forward.HandlerConfig) {
upstreams := c.Servers
fallbacks := c.Fallback.Servers
upsConfs := toUpstreamConfigs(upstreams)
fallbackConfs := toUpstreamConfigs(fallbacks)
metricsListener := prometheus.NewForwardMetricsListener(
metrics.Namespace(),
len(upstreams)+len(fallbacks),
)
func (c *upstreamConfig) toInternal(
logger *slog.Logger,
mtrcListener *dnssvcprom.ForwardMetricsListener,
) (fwdConf *forward.HandlerConfig) {
var hcInit time.Duration
if c.Healthcheck.Enabled {
hcInit = c.Healthcheck.Timeout.Duration
hcInit = time.Duration(c.Healthcheck.Timeout)
}
fwdConf = &forward.HandlerConfig{
return &forward.HandlerConfig{
Logger: logger.With(slogutil.KeyPrefix, "forward"),
MetricsListener: metricsListener,
MetricsListener: mtrcListener,
HealthcheckDomainTmpl: c.Healthcheck.DomainTmpl,
UpstreamsAddresses: upsConfs,
FallbackAddresses: fallbackConfs,
HealthcheckBackoffDuration: c.Healthcheck.BackoffDuration.Duration,
UpstreamsAddresses: toUpstreamConfigs(c.Servers),
FallbackAddresses: toUpstreamConfigs(c.Fallback.Servers),
HealthcheckBackoffDuration: time.Duration(c.Healthcheck.BackoffDuration),
HealthcheckInitDuration: hcInit,
}
return fwdConf
}
// type check
var _ validator = (*upstreamConfig)(nil)
var _ validate.Interface = (*upstreamConfig)(nil)
// validate implements the [validator] interface for *upstreamConfig.
func (c *upstreamConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *upstreamConfig.
func (c *upstreamConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case len(c.Servers) == 0:
return fmt.Errorf("servers: %w", errors.ErrEmptyValue)
}
for i, s := range c.Servers {
if err = s.validate(); err != nil {
return fmt.Errorf("servers: at index %d: %w", i, err)
}
errs := []error{
validate.NotEmptySlice("servers", c.Servers),
}
return cmp.Or(
validateProp("fallback", c.Fallback.validate),
validateProp("healthcheck", c.Healthcheck.validate),
)
errs = validate.AppendSlice(errs, "servers", c.Servers)
errs = validate.Append(errs, "fallback", c.Fallback)
errs = validate.Append(errs, "healthcheck", c.Healthcheck)
return errors.Join(errs...)
}
// splitUpstreamURL separates server url to net protocol and port address.
@@ -142,26 +129,23 @@ type upstreamHealthcheckConfig struct {
}
// type check
var _ validator = (*upstreamHealthcheckConfig)(nil)
var _ validate.Interface = (*upstreamHealthcheckConfig)(nil)
// validate implements the [validator] interface for *upstreamHealthcheckConfig.
func (c *upstreamHealthcheckConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for
// *upstreamHealthcheckConfig.
func (c *upstreamHealthcheckConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case !c.Enabled:
} else if !c.Enabled {
return nil
case c.DomainTmpl == "":
return fmt.Errorf("domain_template: %w", errors.ErrEmptyValue)
case c.Interval.Duration <= 0:
return newNotPositiveError("interval", c.Interval)
case c.Timeout.Duration <= 0:
return newNotPositiveError("timeout", c.Timeout)
case c.BackoffDuration.Duration <= 0:
return newNotPositiveError("backoff_duration", c.BackoffDuration)
}
return nil
return errors.Join(
validate.NotEmpty("domain_template", c.DomainTmpl),
validate.Positive("backoff_duration", c.BackoffDuration),
validate.Positive("interval", c.Interval),
validate.Positive("timeout", c.Timeout),
)
}
// newUpstreamHealthcheck returns refresher worker service that performs
@@ -178,13 +162,13 @@ func newUpstreamHealthcheck(
const prefix = "upstream_healthcheck_refresh"
refrLogger := logger.With(slogutil.KeyPrefix, prefix)
return agdservice.NewRefreshWorker(&agdservice.RefreshWorkerConfig{
Context: newCtxWithTimeoutCons(conf.Healthcheck.Timeout.Duration),
Refresher: agdservice.NewRefresherWithErrColl(handler, refrLogger, errColl, prefix),
Logger: refrLogger,
Interval: conf.Healthcheck.Interval.Duration,
RefreshOnShutdown: false,
RandomizeStart: false,
return service.NewRefreshWorker(&service.RefreshWorkerConfig{
ContextConstructor: contextutil.NewTimeoutConstructor(time.Duration(conf.Healthcheck.Timeout)),
ErrorHandler: errcoll.NewRefreshErrorHandler(refrLogger, errColl),
Refresher: handler,
Schedule: timeutil.NewConstSchedule(time.Duration(conf.Healthcheck.Interval)),
RefreshOnShutdown: false,
})
}
@@ -197,24 +181,22 @@ type upstreamFallbackConfig struct {
}
// type check
var _ validator = (*upstreamFallbackConfig)(nil)
var _ validate.Interface = (*upstreamFallbackConfig)(nil)
// validate implements the [validator] interface for *upstreamFallbackConfig.
func (c *upstreamFallbackConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for
// *upstreamFallbackConfig.
func (c *upstreamFallbackConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case len(c.Servers) == 0:
return fmt.Errorf("servers: %w", errors.ErrEmptyValue)
}
for i, s := range c.Servers {
if err = s.validate(); err != nil {
return fmt.Errorf("servers: at index %d: %w", i, err)
}
errs := []error{
validate.NotEmptySlice("servers", c.Servers),
}
return nil
errs = validate.AppendSlice(errs, "servers", c.Servers)
return errors.Join(errs...)
}
// upstreamServerConfig is the configuration for the upstream server.
@@ -228,23 +210,25 @@ type upstreamServerConfig struct {
}
// type check
var _ validator = (*upstreamServerConfig)(nil)
var _ validate.Interface = (*upstreamServerConfig)(nil)
// validate implements the [validator] interface for *upstreamServerConfig.
func (c *upstreamServerConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for
// *upstreamServerConfig.
func (c *upstreamServerConfig) Validate() (err error) {
if c == nil {
return errors.ErrNoValue
case c.Timeout.Duration <= 0:
return newNotPositiveError("timeout", c.Timeout)
}
errs := []error{
validate.Positive("timeout", c.Timeout),
}
_, _, err = splitUpstreamURL(c.Address)
if err != nil {
return fmt.Errorf("invalid addr: %s", c.Address)
errs = append(errs, fmt.Errorf("address: %w", err))
}
return nil
return errors.Join(errs...)
}
// toUpstreamConfigs converts confs to the list of upstream configurations.
@@ -257,7 +241,7 @@ func toUpstreamConfigs(confs []*upstreamServerConfig) (upsConfs []*forward.Upstr
upsConfs = append(upsConfs, &forward.UpstreamPlainConfig{
Network: net,
Address: addrPort,
Timeout: c.Timeout.Duration,
Timeout: time.Duration(c.Timeout),
})
}

View File

@@ -1,50 +0,0 @@
package cmd
import (
"fmt"
"net/netip"
"github.com/AdguardTeam/golibs/timeutil"
)
// validatePositive returns an error if v is not a positive number. prop is the
// name of the property being checked, used for error messages.
func validatePositive[T numberOrDuration](prop string, v T) (err error) {
if d, ok := any(v).(timeutil.Duration); ok && d.Duration <= 0 {
return newNotPositiveError(prop, v)
}
return nil
}
// validateProp returns an error wrapped with prop name if the given validator
// func returns an error.
func validateProp(prop string, validator func() error) (err error) {
err = validator()
if err != nil {
return fmt.Errorf("%s: %w", prop, err)
}
return nil
}
// netipAddr is the type constraint for the types from [netip], which we can
// validate using [validateAddrs].
type netipAddr interface {
netip.Addr | netip.AddrPort
IsValid() (ok bool)
}
// validateAddrs returns an error if any of the addrs isn't valid.
//
// TODO(a.garipov): Merge with [validateNonNilIPs].
func validateAddrs[T netipAddr](addrs []T) (err error) {
for i, a := range addrs {
if !a.IsValid() {
return fmt.Errorf("at index %d: invalid addr", i)
}
}
return nil
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/base64"
"fmt"
"log/slog"
"maps"
"net/http"
"net/netip"
@@ -11,6 +12,7 @@ import (
"os"
"path"
"slices"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnscheck"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
@@ -18,9 +20,11 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/websvc"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/timeutil"
"github.com/AdguardTeam/golibs/validate"
)
// webConfig contains configuration for the AdGuard DNS web service.
@@ -62,12 +66,13 @@ type webConfig struct {
}
// toInternal converts c to the AdGuardDNS web service configuration. c must be
// valid.
// valid. All arguments must not be nil.
func (c *webConfig) toInternal(
ctx context.Context,
envs *environment,
dnsCk dnscheck.Interface,
errColl errcoll.Interface,
baseLogger *slog.Logger,
tlsMgr tlsconfig.Manager,
) (conf *websvc.Config, err error) {
if c == nil {
@@ -75,8 +80,9 @@ func (c *webConfig) toInternal(
}
conf = &websvc.Config{
Logger: baseLogger.With(slogutil.KeyPrefix, "websvc"),
ErrColl: errColl,
Timeout: c.Timeout.Duration,
Timeout: time.Duration(c.Timeout),
}
if dnsCkHdlr, ok := dnsCk.(http.Handler); ok {
@@ -174,50 +180,26 @@ func (c *webConfig) setStaticContent(envs *environment, conf *websvc.Config) (er
}
// type check
var _ validator = (*webConfig)(nil)
var _ validate.Interface = (*webConfig)(nil)
// validate implements the [validator] interface for *webConfig.
func (c *webConfig) validate() (err error) {
switch {
case c == nil:
// Validate implements the [validate.Interface] interface for *webConfig.
func (c *webConfig) Validate() (err error) {
if c == nil {
return nil
case c.Timeout.Duration <= 0:
return newNotPositiveError("timeout", c.Timeout)
default:
// Go on.
}
err = c.LinkedIP.validate()
if err != nil {
return fmt.Errorf("linked_ip: %w", err)
errs := []error{
validate.Positive("timeout", c.Timeout),
}
err = c.AdultBlocking.validate()
if err != nil {
return fmt.Errorf("adult_blocking: %w", err)
}
errs = validate.Append(errs, "linked_ip", c.LinkedIP)
errs = validate.Append(errs, "adult_blocking", c.AdultBlocking)
errs = validate.Append(errs, "general_blocking", c.GeneralBlocking)
errs = validate.Append(errs, "safe_browsing", c.SafeBrowsing)
errs = validate.Append(errs, "static_content", c.StaticContent)
errs = validate.Append(errs, "non_doh_bind", c.NonDoHBind)
err = c.GeneralBlocking.validate()
if err != nil {
return fmt.Errorf("general_blocking: %w", err)
}
err = c.SafeBrowsing.validate()
if err != nil {
return fmt.Errorf("safe_browsing: %w", err)
}
err = c.StaticContent.validate()
if err != nil {
return fmt.Errorf("static_content: %w", err)
}
err = c.NonDoHBind.validate()
if err != nil {
return fmt.Errorf("non_doh_bind: %w", err)
}
return nil
return errors.Join(errs...)
}
// linkedIPServer is the linked IP web server configuration.
@@ -253,25 +235,21 @@ func (s *linkedIPServer) toInternal(
}
// type check
var _ validator = (*linkedIPServer)(nil)
var _ validate.Interface = (*linkedIPServer)(nil)
// validate implements the [validator] interface for *linkedIPServer.
func (s *linkedIPServer) validate() (err error) {
switch {
case s == nil:
// Validate implements the [validate.Interface] interface for *linkedIPServer.
func (s *linkedIPServer) Validate() (err error) {
if s == nil {
return nil
case len(s.Bind) == 0:
return fmt.Errorf("bind: %w", errors.ErrEmptyValue)
default:
// Go on.
}
err = s.Bind.validate()
if err != nil {
return fmt.Errorf("bind: %w", err)
errs := []error{
validate.NotEmptySlice("bind", s.Bind),
}
return nil
errs = validate.Append(errs, "bind", s.Bind)
return errors.Join(errs...)
}
// blockPageServer is the safe browsing or adult blocking block page web servers
@@ -307,27 +285,22 @@ func (s *blockPageServer) toInternal(
}
// type check
var _ validator = (*blockPageServer)(nil)
var _ validate.Interface = (*blockPageServer)(nil)
// validate implements the [validator] interface for *blockPageServer.
func (s *blockPageServer) validate() (err error) {
switch {
case s == nil:
// Validate implements the [validate.Interface] interface for *blockPageServer.
func (s *blockPageServer) Validate() (err error) {
if s == nil {
return nil
case s.BlockPage == "":
return fmt.Errorf("block_page: %w", errors.ErrEmptyValue)
case len(s.Bind) == 0:
return fmt.Errorf("bind: %w", errors.ErrEmptyValue)
default:
// Go on.
}
err = s.Bind.validate()
if err != nil {
return fmt.Errorf("bind: %w", err)
errs := []error{
validate.NotEmpty("block_page", s.BlockPage),
validate.NotEmptySlice("bind", s.Bind),
}
return nil
errs = validate.Append(errs, "bind", s.Bind)
return errors.Join(errs...)
}
// bindData are the data for binding HTTP servers to addresses.
@@ -352,22 +325,23 @@ func (bd bindData) toInternal(
}
// type check
var _ validator = bindData(nil)
var _ validate.Interface = bindData(nil)
// validate implements the [validator] interface for bindData.
func (bd bindData) validate() (err error) {
// Validate implements the [validate.Interface] interface for bindData.
func (bd bindData) Validate() (err error) {
if len(bd) == 0 {
return nil
}
var errs []error
for i, d := range bd {
err = d.validate()
err = d.Validate()
if err != nil {
return fmt.Errorf("at index %d: %w", i, err)
errs = append(errs, fmt.Errorf("at index %d: %w", i, err))
}
}
return nil
return errors.Join(errs...)
}
// bindItem is data for binding one HTTP server to an address.
@@ -397,25 +371,21 @@ func (i *bindItem) toInternal(
}
// type check
var _ validator = (*bindItem)(nil)
var _ validate.Interface = (*bindItem)(nil)
// validate implements the [validator] interface for *bindItem.
func (i *bindItem) validate() (err error) {
switch {
case i == nil:
// Validate implements the [validate.Interface] interface for *bindItem.
func (i *bindItem) Validate() (err error) {
if i == nil {
return errors.ErrNoValue
case i.Address == netip.AddrPort{}:
return fmt.Errorf("address: %w", errors.ErrEmptyValue)
default:
// Go on.
}
err = i.Certificates.validate()
if err != nil {
return fmt.Errorf("certificates: %w", err)
errs := []error{
validate.NotEmpty("address", i.Address),
}
return nil
errs = validate.Append(errs, "certificates", i.Certificates)
return errors.Join(errs...)
}
// staticContent is the static content mapping. Paths must be absolute and
@@ -441,26 +411,29 @@ func (sc staticContent) toInternal() (fs websvc.StaticContent, err error) {
}
// type check
var _ validator = staticContent(nil)
var _ validate.Interface = staticContent(nil)
// validate implements the [validator] interface for staticContent.
func (sc staticContent) validate() (err error) {
// Validate implements the [validate.Interface] interface for staticContent.
func (sc staticContent) Validate() (err error) {
if len(sc) == 0 {
return nil
}
var errs []error
for _, p := range slices.Sorted(maps.Keys(sc)) {
if !path.IsAbs(p) {
return fmt.Errorf("path %q: not absolute", p)
errs = append(errs, fmt.Errorf("path %q: not absolute", p))
continue
}
err = sc[p].validate()
err = sc[p].Validate()
if err != nil {
return fmt.Errorf("path %q: %w", p, err)
}
}
return nil
return errors.Join(errs...)
}
// staticFile is a single file in a static content mapping.
@@ -499,10 +472,10 @@ func (f *staticFile) toInternal() (file *websvc.StaticFile, err error) {
}
// type check
var _ validator = (*staticFile)(nil)
var _ validate.Interface = (*staticFile)(nil)
// validate implements the [validator] interface for *staticFile.
func (f *staticFile) validate() (err error) {
// Validate implements the [validate.Interface] interface for *staticFile.
func (f *staticFile) Validate() (err error) {
if f == nil {
return errors.ErrNoValue
}

View File

@@ -7,11 +7,8 @@ import (
"sync/atomic"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/optslog"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/timeutil"
)
// limitConn is a wrapper for a stream connection that decreases the counter
@@ -21,11 +18,12 @@ import (
type limitConn struct {
net.Conn
logger *slog.Logger
serverInfo *dnsserver.ServerInfo
decrement func()
start time.Time
isClosed atomic.Bool
connInfo *ConnMetricsData
logger *slog.Logger
metrics Metrics
decrement func(ctx context.Context)
start time.Time
isClosed atomic.Bool
}
// Close closes the underlying connection and decrements the counter.
@@ -42,22 +40,11 @@ func (c *limitConn) Close() (err error) {
ctx := context.Background()
connLife := time.Since(c.start)
optslog.Debug2(
ctx,
c.logger,
"closed conn",
"raddr", c.RemoteAddr(),
"conn_life", timeutil.Duration{
Duration: connLife,
},
)
metrics.StreamConnLifeDuration.WithLabelValues(
c.serverInfo.Name,
c.serverInfo.Proto.String(),
c.serverInfo.Addr,
).Observe(connLife.Seconds())
optslog.Debug2(ctx, c.logger, "closed conn", "raddr", c.RemoteAddr(), "conn_life", connLife)
c.decrement()
c.metrics.ObserveLifeDuration(ctx, c.connInfo, connLife)
c.decrement(ctx)
return err
}

View File

@@ -1,13 +1,11 @@
package connlimiter
import (
"fmt"
"log/slog"
"net"
"sync"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
)
// Config is the configuration structure for the stream-connection limiter.
@@ -15,6 +13,10 @@ type Config struct {
// Logger is used to log the operation of the limiter. It must not be nil.
Logger *slog.Logger
// Metrics is used for the collection of the stream connections statistics.
// It must not be nil.
Metrics Metrics
// Stop is the point at which the limiter stops accepting new connections.
// Once the number of active connections reaches this limit, new connections
// wait for the number to decrease to or below Resume.
@@ -31,7 +33,8 @@ type Config struct {
// Limiter is the stream-connection limiter.
type Limiter struct {
logger *slog.Logger
logger *slog.Logger
metrics Metrics
// counterCond is the shared condition variable that protects counter.
counterCond *sync.Cond
@@ -40,14 +43,11 @@ type Limiter struct {
counter *counter
}
// New returns a new *Limiter.
func New(c *Config) (l *Limiter, err error) {
if c == nil || c.Stop == 0 || c.Resume > c.Stop {
return nil, fmt.Errorf("bad limiter config: %+v", c)
}
// New returns a new *Limiter. c must be valid.
func New(c *Config) (l *Limiter) {
return &Limiter{
logger: c.Logger,
metrics: c.Metrics,
counterCond: sync.NewCond(&sync.Mutex{}),
counter: &counter{
current: 0,
@@ -55,27 +55,28 @@ func New(c *Config) (l *Limiter, err error) {
resume: c.Resume,
isAccepting: true,
},
}, nil
}
}
// Limit wraps lsnr to control the number of active connections. srvInfo is
// used for logging and metrics.
func (l *Limiter) Limit(lsnr net.Listener, srvInfo *dnsserver.ServerInfo) (limited net.Listener) {
name, addr := srvInfo.Name, srvInfo.Addr
proto := srvInfo.Proto.String()
name := srvInfo.Name
return &limitListener{
Listener: lsnr,
logger: l.logger.With("name", name),
logger: l.logger.With("name", name),
metrics: l.metrics,
counterCond: l.counterCond,
counter: l.counter,
serverInfo: srvInfo,
activeGauge: metrics.ConnLimiterActiveStreamConns.WithLabelValues(name, proto, addr),
waitingHist: metrics.StreamConnWaitDuration.WithLabelValues(name, proto, addr),
connInfo: &ConnMetricsData{
Addr: srvInfo.Addr,
Name: name,
Proto: srvInfo.Proto.String(),
},
isClosed: false,
}

View File

@@ -27,12 +27,12 @@ var testServerInfo = &dnsserver.ServerInfo{
}
func TestLimiter(t *testing.T) {
l, err := connlimiter.New(&connlimiter.Config{
Logger: slogutil.NewDiscardLogger(),
Stop: 1,
Resume: 1,
l := connlimiter.New(&connlimiter.Config{
Metrics: connlimiter.EmptyMetrics{},
Logger: slogutil.NewDiscardLogger(),
Stop: 1,
Resume: 1,
})
require.NoError(t, err)
conn := &fakenet.Conn{
OnClose: func() (err error) { return nil },
@@ -109,13 +109,3 @@ func TestLimiter(t *testing.T) {
// Check that double close causes an error.
assert.ErrorIs(t, limited.Close(), net.ErrClosed)
}
func TestLimiter_badConf(t *testing.T) {
l, err := connlimiter.New(&connlimiter.Config{
Logger: slogutil.NewDiscardLogger(),
Stop: 1,
Resume: 2,
})
assert.Nil(t, l)
assert.Error(t, err)
}

View File

@@ -49,12 +49,12 @@ func TestListenConfig(t *testing.T) {
},
}
l, err := connlimiter.New(&connlimiter.Config{
Logger: slogutil.NewDiscardLogger(),
Stop: 1,
Resume: 1,
l := connlimiter.New(&connlimiter.Config{
Metrics: connlimiter.EmptyMetrics{},
Logger: slogutil.NewDiscardLogger(),
Stop: 1,
Resume: 1,
})
require.NoError(t, err)
limited := connlimiter.NewListenConfig(c, l)

View File

@@ -9,9 +9,7 @@ import (
"sync"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/errors"
"github.com/prometheus/client_golang/prometheus"
)
// limitListener is a wrapper that uses a counter to limit the number of active
@@ -23,9 +21,13 @@ type limitListener struct {
logger *slog.Logger
// serverInfo is used for logging and metrics in both the listener itself
// and in its conns. It's never nil.
serverInfo *dnsserver.ServerInfo
// metrics is used for the collection of the stream connections statistics.
// It must not be nil.
metrics Metrics
// connInfo is used for metrics in both the listener itself and in its
// conns. It's never nil.
connInfo *ConnMetricsData
// counterCond is the condition variable that protects counter and isClosed
// through its locker, as well as signals when connections can be accepted
@@ -35,13 +37,6 @@ type limitListener struct {
// counter is the shared counter for all listeners.
counter *counter
// activeGauge is the metrics gauge of currently active stream-connections.
activeGauge prometheus.Gauge
// waitingHist is the metrics histogram of how much a connection spends
// waiting for an accept.
waitingHist prometheus.Observer
// isClosed shows whether this listener has been closed.
isClosed bool
}
@@ -60,12 +55,12 @@ func (l *limitListener) Accept() (conn net.Conn, err error) {
return nil, net.ErrClosed
}
l.waitingHist.Observe(time.Since(waitStart).Seconds())
l.activeGauge.Inc()
l.metrics.ObserveWaitingDuration(ctx, l.connInfo, time.Since(waitStart))
l.metrics.IncrementActive(ctx, l.connInfo)
conn, err = l.Listener.Accept()
if err != nil {
l.decrement()
l.decrement(ctx)
return nil, err
}
@@ -73,10 +68,11 @@ func (l *limitListener) Accept() (conn net.Conn, err error) {
return &limitConn{
Conn: conn,
logger: l.logger,
decrement: l.decrement,
start: time.Now(),
serverInfo: l.serverInfo,
connInfo: l.connInfo,
metrics: l.metrics,
logger: l.logger,
decrement: l.decrement,
start: time.Now(),
}, nil
}
@@ -110,14 +106,13 @@ func (l *limitListener) increment(ctx context.Context) (isClosed bool) {
// decrement decreases the number of active connections in the counter and
// broadcasts the change.
func (l *limitListener) decrement() {
func (l *limitListener) decrement(ctx context.Context) {
defer l.metrics.DecrementActive(ctx, l.connInfo)
l.counterCond.L.Lock()
defer l.counterCond.L.Unlock()
l.activeGauge.Dec()
l.counter.decrement()
l.counterCond.Signal()
}

View File

@@ -0,0 +1,75 @@
package connlimiter
import (
"context"
"time"
)
// Metrics is an interface used for collection of the stream-connections
// statistics.
type Metrics interface {
// IncrementActive increments the number of active stream-connections. m
// must not be nil.
IncrementActive(ctx context.Context, m *ConnMetricsData)
// DecrementActive decrements the number of active stream-connections. m
// must not be nil.
DecrementActive(ctx context.Context, m *ConnMetricsData)
// ObserveLifeDuration updates the duration of life times for
// stream-connections. m must not be nil.
ObserveLifeDuration(ctx context.Context, m *ConnMetricsData, dur time.Duration)
// ObserveWaitingDuration updates the duration of waiting times for
// accepting stream-connections. m must not be nil.
ObserveWaitingDuration(ctx context.Context, m *ConnMetricsData, dur time.Duration)
// SetStopLimit sets the stopping limit number of active stream-connections.
SetStopLimit(ctx context.Context, n uint64)
// SetResumeLimit sets the resuming limit number of active
// stream-connections.
SetResumeLimit(ctx context.Context, n uint64)
}
// ConnMetricsData is an alias for a structure that contains the information
// about a stream-connection. All fields must not be empty.
//
// NOTE: This is an alias to reduce the amount of dependencies required of
// implementations. This is also the reason why only built-in or stdlib types
// are used.
type ConnMetricsData = struct {
// Addr is the address that the server is configured to listen on.
Addr string
// Name is the name of the server.
Name string
// Proto is the protocol of the server.
Proto string
}
// EmptyMetrics is the implementation of the [Metrics] interface that does
// nothing.
type EmptyMetrics struct{}
// type check
var _ Metrics = EmptyMetrics{}
// IncrementActive implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) IncrementActive(_ context.Context, _ *ConnMetricsData) {}
// DecrementActive implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) DecrementActive(_ context.Context, _ *ConnMetricsData) {}
// ObserveLifeDuration implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) ObserveLifeDuration(_ context.Context, _ *ConnMetricsData, _ time.Duration) {}
// ObserveWaitingDuration implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) ObserveWaitingDuration(_ context.Context, _ *ConnMetricsData, _ time.Duration) {}
// SetStopLimit implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) SetStopLimit(_ context.Context, _ uint64) {}
// SetResumeLimit implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) SetResumeLimit(_ context.Context, _ uint64) {}

View File

@@ -12,11 +12,11 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/ratelimit"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/service"
)
// AllowlistUpdater is a wrapper that updates the allowlist on refresh. It
@@ -68,10 +68,9 @@ func NewAllowlistUpdater(c *AllowlistUpdaterConfig) (upd *AllowlistUpdater) {
}
// type check
var _ agdservice.Refresher = (*AllowlistUpdater)(nil)
var _ service.Refresher = (*AllowlistUpdater)(nil)
// Refresh implements the [agdservice.Refresher] interface for
// *AllowlistUpdater.
// Refresh implements the [service.Refresher] interface for *AllowlistUpdater.
func (upd *AllowlistUpdater) Refresh(ctx context.Context) (err error) {
upd.logger.InfoContext(ctx, "refresh started")
defer upd.logger.InfoContext(ctx, "refresh finished")

View File

@@ -11,12 +11,12 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdcache"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/AdGuardDNS/internal/agdtest"
"github.com/AdguardTeam/AdGuardDNS/internal/debugsvc"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil/httputil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/fakeservice"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -39,21 +39,21 @@ func TestService_Start(t *testing.T) {
var refreshed []string
refreshers := debugsvc.Refreshers{
"test": &agdtest.Refresher{
"test": &fakeservice.Refresher{
OnRefresh: func(_ context.Context) (err error) {
refreshed = append(refreshed, "test")
return nil
},
},
"parent/first": &agdtest.Refresher{
"parent/first": &fakeservice.Refresher{
OnRefresh: func(_ context.Context) (err error) {
refreshed = append(refreshed, "parent/first")
return nil
},
},
"parent/second": &agdtest.Refresher{
"parent/second": &fakeservice.Refresher{
OnRefresh: func(_ context.Context) (err error) {
refreshed = append(refreshed, "parent/second")

View File

@@ -12,10 +12,10 @@ import (
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/AdGuardDNS/internal/agdservice"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/service"
)
// RefresherID is a type alias for strings that represent IDs of refreshers.
@@ -25,7 +25,7 @@ type RefresherID = string
// Refreshers is a type alias for maps of refresher IDs to Refreshers
// themselves.
type Refreshers map[RefresherID]agdservice.Refresher
type Refreshers map[RefresherID]service.Refresher
// refreshHandler performs debug refreshes.
type refreshHandler struct {

View File

@@ -8,6 +8,7 @@ import (
"strings"
"github.com/AdguardTeam/AdGuardDNS/internal/agd"
"github.com/AdguardTeam/AdGuardDNS/internal/agdvalidate"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
@@ -79,7 +80,7 @@ func validateRandomID(id string) (err error) {
min = 4
)
if err = agd.ValidateInclusion(len(id), max, min, agd.UnitByte); err != nil {
if err = agdvalidate.Inclusion(len(id), min, max, agdvalidate.UnitByte); err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}

View File

@@ -1,29 +0,0 @@
package dnscheck
import (
"context"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/remotekv/consulkv"
"github.com/AdguardTeam/golibs/errors"
)
// incErrMetrics increments error gauge metrics for the given src and err.
// "source" can be "dns" or "http".
func incErrMetrics(src string, err error) {
if err == nil {
return
}
var errType string
switch {
case errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled):
errType = "timeout"
case errors.Is(err, consulkv.ErrRateLimited):
errType = "ratelimit"
default:
errType = "other"
}
metrics.DNSCheckErrorTotal.WithLabelValues(src, errType).Inc()
}

View File

@@ -0,0 +1,65 @@
package dnscheck
import (
"context"
"github.com/AdguardTeam/AdGuardDNS/internal/remotekv/consulkv"
"github.com/AdguardTeam/golibs/errors"
)
// Error types for [Metrics.HandleError].
const (
errMtrcTypeTimeout = "timeout"
errMtrcTypeRatelimit = "ratelimit"
errMtrcTypeOther = "other"
)
// Request types for [Metrics].
const (
reqMtrcTypeDNS = "dns"
reqMtrcTypeHTTP = "http"
)
// Metrics is an interface that is used for the collection of the DNSCheck
// service statistics.
type Metrics interface {
// HandleError handles the total number of errors by type. reqType must be
// [reqMtrcTypeDNS] or [reqMtrcTypeHTTP]. errType must be either
// [errMtrcTypeTimeout], [errMtrcTypeRatelimit], [errMtrcTypeOther] or an
// empty string.
HandleError(ctx context.Context, reqType, errType string)
// HandleRequest handles the total number of requests by type. reqType must
// be [reqMtrcTypeDNS] or [reqMtrcTypeHTTP].
HandleRequest(ctx context.Context, reqType string, isValid bool)
}
// EmptyMetrics is the implementation of the [Metrics] interface that does
// nothing.
type EmptyMetrics struct{}
// type check
var _ Metrics = EmptyMetrics{}
// HandleError implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) HandleError(_ context.Context, _, _ string) {}
// HandleRequest implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) HandleRequest(_ context.Context, _ string, _ bool) {}
// errMetricsType returns the error type for [Metrics.HandleError]. It is an
// empty string if there is no error.
func errMetricsType(err error) (errType string) {
if err == nil {
return ""
}
switch {
case errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled):
return errMtrcTypeTimeout
case errors.Is(err, consulkv.ErrRateLimited):
return errMtrcTypeRatelimit
default:
return errMtrcTypeOther
}
}

View File

@@ -15,7 +15,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdhttp"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/AdGuardDNS/internal/remotekv"
"github.com/AdguardTeam/AdGuardDNS/internal/remotekv/consulkv"
"github.com/AdguardTeam/golibs/errors"
@@ -24,12 +23,12 @@ import (
"github.com/AdguardTeam/golibs/netutil"
"github.com/miekg/dns"
cache "github.com/patrickmn/go-cache"
"github.com/prometheus/client_golang/prometheus"
)
// RemoteKV is the RemoteKV KV based DNS checker.
type RemoteKV struct {
logger *slog.Logger
logger *slog.Logger
metrics Metrics
// mu protects cache. Don't use an RWMutex here, since it is expected that
// there are about as many reads as there are writes.
@@ -59,6 +58,9 @@ type RemoteKVConfig struct {
// IPv4 and IPv6 IPs.
Messages *dnsmsg.Constructor
// Metrics is used for the collection of the DNSCheck service statistics.
Metrics Metrics
// RemoteKV for DNS server checking.
RemoteKV remotekv.Interface
@@ -94,6 +96,7 @@ const (
func NewRemoteKV(c *RemoteKVConfig) (dc *RemoteKV) {
return &RemoteKV{
logger: c.Logger,
metrics: c.Metrics,
mu: &sync.Mutex{},
cache: cache.New(defaultCacheExp, defaultCacheGC),
kv: c.RemoteKV,
@@ -118,16 +121,13 @@ func (dc *RemoteKV) Check(
) (resp *dns.Msg, err error) {
var matched bool
defer func() {
incErrMetrics("dns", err)
dc.metrics.HandleError(ctx, reqMtrcTypeDNS, errMetricsType(err))
if !matched {
return
}
metrics.DNSCheckRequestTotal.With(prometheus.Labels{
"type": "dns",
"valid": metrics.BoolString(err == nil),
}).Inc()
dc.metrics.HandleRequest(ctx, reqMtrcTypeDNS, err == nil)
}()
var randomID string
@@ -180,19 +180,19 @@ const (
// newInfo returns an information record with all available data about the
// server and the request. ri must not be nil.
func (dc *RemoteKV) newInfo(ri *agd.RequestInfo) (inf *info) {
g := ri.ServerGroup
srvInfo := ri.ServerInfo
srvType := serverTypePublic
if g.ProfilesEnabled {
if srvInfo.ProfilesEnabled {
srvType = serverTypePrivate
}
inf = &info{
ServerGroupName: g.Name,
ServerName: ri.Server,
ServerGroupName: srvInfo.GroupName,
ServerName: srvInfo.Name,
ServerType: srvType,
Protocol: ri.Proto.String(),
Protocol: srvInfo.Protocol.String(),
NodeLocation: dc.nodeLocation,
NodeName: dc.nodeName,
@@ -230,10 +230,10 @@ var _ http.Handler = (*RemoteKV)(nil)
// ServeHTTP implements the http.Handler interface for *RemoteKV.
//
// TODO(a.garipov): Consider using the websvc logger once it switches to
// log/slog.
// TODO(a.garipov): Find ways of merging the attributes of [RemoteKV.logger]
// and the logger that websvc adds.
func (dc *RemoteKV) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// TODO(a.garipov): Put this into constant here and in package dnssvc.
// TODO(a.garipov): Put this into constant here and in package websvc.
if r.URL.Path == "/dnscheck/test" {
dc.serveCheckTest(r.Context(), w, r)
@@ -308,12 +308,8 @@ func (dc *RemoteKV) serveCheckTest(ctx context.Context, w http.ResponseWriter, r
// info returns an information record by the random request ID.
func (dc *RemoteKV) info(ctx context.Context, randomID string) (inf []byte, ok bool, err error) {
defer func() {
metrics.DNSCheckRequestTotal.With(prometheus.Labels{
"type": "http",
"valid": metrics.BoolString(err == nil),
}).Inc()
incErrMetrics("http", err)
dc.metrics.HandleError(ctx, reqMtrcTypeHTTP, errMetricsType(err))
dc.metrics.HandleRequest(ctx, reqMtrcTypeHTTP, err == nil)
}()
defer func() { err = errors.Annotate(err, "getting from remote kv: %w") }()

View File

@@ -49,6 +49,7 @@ func TestConsul_ServeHTTP(t *testing.T) {
conf := &dnscheck.RemoteKVConfig{
Logger: slogutil.NewDiscardLogger(),
Messages: &dnsmsg.Constructor{},
Metrics: dnscheck.EmptyMetrics{},
RemoteKV: remotekv.Empty{},
ErrColl: agdtest.NewErrorCollector(),
Domains: []string{checkDomain},
@@ -72,15 +73,16 @@ func TestConsul_ServeHTTP(t *testing.T) {
Device: &agd.Device{ID: agd.DeviceID(theOnlyVal["device_id"].(string))},
Profile: &agd.Profile{ID: agd.ProfileID(theOnlyVal["profile_id"].(string))},
},
ServerGroup: &agd.ServerGroup{
Name: agd.ServerGroupName(theOnlyVal["server_group_name"].(string)),
ServerInfo: &agd.RequestServerInfo{
GroupName: agd.ServerGroupName(theOnlyVal["server_group_name"].(string)),
Name: agd.ServerName(theOnlyVal["server_name"].(string)),
DeviceDomains: nil,
Protocol: agd.ProtoDNS,
ProfilesEnabled: theOnlyVal["server_type"] == "private",
},
Server: agd.ServerName(theOnlyVal["server_name"].(string)),
Host: randomid + "-" + checkDomain,
RemoteIP: testRemoteIP,
QType: dns.TypeA,
Proto: agd.ProtoDNS,
},
)
require.NoError(t, err)
@@ -175,6 +177,7 @@ func TestConsul_Check(t *testing.T) {
conf := &dnscheck.RemoteKVConfig{
Logger: slogutil.NewDiscardLogger(),
Messages: msgs,
Metrics: dnscheck.EmptyMetrics{},
RemoteKV: remotekv.Empty{},
Domains: []string{checkDomain},
IPv4: []netip.Addr{netip.MustParseAddr("1.2.3.4")},
@@ -194,12 +197,13 @@ func TestConsul_Check(t *testing.T) {
}},
}
ri := &agd.RequestInfo{
Host: tc.host,
ServerGroup: &agd.ServerGroup{},
RemoteIP: testRemoteIP,
QType: tc.qtype,
Messages: msgs,
Proto: agd.ProtoDNS,
Host: tc.host,
ServerInfo: &agd.RequestServerInfo{
Protocol: agd.ProtoDNS,
},
RemoteIP: testRemoteIP,
QType: tc.qtype,
Messages: msgs,
}
resp, cErr := dnsCk.Check(ctx, req, ri)

View File

@@ -4,7 +4,6 @@ import (
"sync"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/AdguardTeam/golibs/container"
"github.com/miekg/dns"
)
@@ -23,15 +22,22 @@ type buffer struct {
maxSize int
}
// add increments the records for all answers.
func (b *buffer) add(target string, answers []dns.RR, qt dnsmsg.RRType, rc dnsmsg.RCode) {
// add adds a new record to the buffer or updates the number of hits on the
// stored record. count is the total number of records stored, ok is true if
// the new record was added.
func (b *buffer) add(
target string,
answers []dns.RR,
qt dnsmsg.RRType,
rc dnsmsg.RCode,
) (count int, ok bool) {
b.mu.Lock()
defer b.mu.Unlock()
// Do nothing if the buffer is already full.
l := len(b.entries)
if l >= b.maxSize {
return
count = len(b.entries)
if count >= b.maxSize {
return count, false
}
key := recordKey{
@@ -47,7 +53,7 @@ func (b *buffer) add(target string, answers []dns.RR, qt dnsmsg.RRType, rc dnsms
// If a more detailed response is needed, maps.Copy can be used to
// achieve that.
return
return count, false
}
b.entries[key] = &recordValue{
@@ -55,7 +61,7 @@ func (b *buffer) add(target string, answers []dns.RR, qt dnsmsg.RRType, rc dnsms
hits: 1,
}
metrics.DNSDBBufferSize.Set(float64(l + 1))
return count + 1, true
}
// all returns buffered records.

View File

@@ -15,7 +15,6 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/agdnet"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg"
"github.com/AdguardTeam/AdGuardDNS/internal/errcoll"
"github.com/AdguardTeam/AdGuardDNS/internal/metrics"
"github.com/miekg/dns"
)
@@ -39,18 +38,25 @@ type Default struct {
logger *slog.Logger
buffer *atomic.Pointer[buffer]
errColl errcoll.Interface
metrics Metrics
maxSize int
}
// DefaultConfig is the default DNS database configuration structure.
type DefaultConfig struct {
// Logger is used to log the operation of the DNS database.
// Logger is used to log the operation of the DNS database. It must not be
// nil.
Logger *slog.Logger
// ErrColl is used to collect HTTP errors.
// ErrColl is used to collect HTTP errors. It must not be nil.
ErrColl errcoll.Interface
// MaxSize is the maximum amount of records in the memory buffer.
// Metrics is used for the collection of the DNS database statistics. It
// must not be nil.
Metrics Metrics
// MaxSize is the maximum amount of records in the memory buffer. It must
// be positive.
MaxSize int
}
@@ -60,6 +66,7 @@ func New(c *DefaultConfig) (db *Default) {
logger: c.Logger,
buffer: &atomic.Pointer[buffer]{},
errColl: c.ErrColl,
metrics: c.Metrics,
maxSize: c.MaxSize,
}
@@ -88,11 +95,14 @@ func (db *Default) Record(ctx context.Context, m *dns.Msg, ri *agd.RequestInfo)
}
// #nosec G115 -- RCODE is currently defined to be 16 bit or less.
db.buffer.Load().add(ri.Host, m.Answer, q.Qtype, dnsmsg.RCode(m.Rcode))
count, ok := db.buffer.Load().add(ri.Host, m.Answer, q.Qtype, dnsmsg.RCode(m.Rcode))
if ok {
db.metrics.SetRecordCount(ctx, count)
}
}
// reset returns buffered records and resets the database.
func (db *Default) reset() (records []*record) {
func (db *Default) reset(ctx context.Context) (records []*record) {
start := time.Now()
prevBuf := db.buffer.Swap(&buffer{
@@ -103,9 +113,8 @@ func (db *Default) reset() (records []*record) {
records = prevBuf.all()
metrics.DNSDBBufferSize.Set(0)
metrics.DNSDBRotateTime.SetToCurrentTime()
metrics.DNSDBSaveDuration.Observe(time.Since(start).Seconds())
db.metrics.SetRecordCount(ctx, 0)
db.metrics.ObserveRotation(ctx, time.Since(start))
return records
}

View File

@@ -17,12 +17,12 @@ import (
// type check
var _ http.Handler = (*Default)(nil)
// ServeHTTP implements the http.Handler interface for *Default.
// ServeHTTP implements the [http.Handler] interface for *Default.
func (db *Default) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var err error
ctx := r.Context()
records := db.reset()
records := db.reset(ctx)
h := w.Header()
h.Add(httphdr.ContentType, agdhttp.HdrValTextCSV)

View File

@@ -123,6 +123,7 @@ func TestDefault_ServeHTTP(t *testing.T) {
db := dnsdb.New(&dnsdb.DefaultConfig{
Logger: slogutil.NewDiscardLogger(),
ErrColl: agdtest.NewErrorCollector(),
Metrics: dnsdb.EmptyMetrics{},
MaxSize: 100,
})
rw := httptest.NewRecorder()

31
internal/dnsdb/metrics.go Normal file
View File

@@ -0,0 +1,31 @@
package dnsdb
import (
"context"
"time"
)
// Metrics is an interface that is used for the collection of the DNS database
// statistics.
type Metrics interface {
// SetRecordCount sets the number of records that have not yet been
// uploaded.
SetRecordCount(ctx context.Context, count int)
// ObserveRotation updates the time of the database rotation and stores the
// duration of the rotation.
ObserveRotation(ctx context.Context, dur time.Duration)
}
// EmptyMetrics is the implementation of the [Metrics] interface that does
// nothing.
type EmptyMetrics struct{}
// type check
var _ Metrics = EmptyMetrics{}
// SetRecordCount implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) SetRecordCount(_ context.Context, _ int) {}
// ObserveRotation implements the [Metrics] interface for EmptyMetrics.
func (EmptyMetrics) ObserveRotation(_ context.Context, dur time.Duration) {}

View File

@@ -17,11 +17,11 @@ type ConstructorConfig struct {
// StructuredErrors is the configuration for the experimental Structured DNS
// Errors feature. It must not be nil. If enabled,
// [ConstructorConfig.Enabled] should also be true.
// [ConstructorConfig.EDEEnabled] should also be true.
StructuredErrors *StructuredDNSErrorsConfig
// BlockingMode is the blocking mode to use in
// [Constructor.NewBlockedRespMsg]. It must not be nil.
// BlockingMode is the blocking mode to use in [Constructor.NewBlockedResp].
// It must not be nil.
BlockingMode BlockingMode
// FilteredResponseTTL is the time-to-live value used for responses created
@@ -118,7 +118,7 @@ func (c *Constructor) AppendDebugExtra(req, resp *dns.Msg, str string) (err erro
// positive numbers, but we need a ceiling operation here.
strNum := (strLen + MaxTXTStringLen - 1) / MaxTXTStringLen
// TODO(a.garipov): Use slices.Chunk in Go 1.23.
// TODO(a.garipov): Consider adding strings.Chunks to golibs.
newStr := make([]string, strNum)
for i := range strNum {
start := i * MaxTXTStringLen

View File

@@ -183,7 +183,46 @@ func TestConstructor_AppendDebugExtra(t *testing.T) {
wantExtra[0].Header().Name = fqdn
}
assert.Equal(t, resp.Extra, tc.wantExtra)
assert.Equal(t, tc.wantExtra, resp.Extra)
})
}
}
// errSink is a sink for benchmark results.
var errSink error
func BenchmarkConstructor_AppendDebugExtra(b *testing.B) {
msgs := agdtest.NewConstructor(b)
longText := strings.Repeat("abc", 2*dnsmsg.MaxTXTStringLen)
req := &dns.Msg{
MsgHdr: dns.MsgHdr{
Id: dns.Id(),
},
Question: []dns.Question{{
Name: testFQDN,
Qtype: dns.TypeTXT,
Qclass: dns.ClassCHAOS,
}},
}
resp := &dns.Msg{}
resp = resp.SetReply(req)
b.ReportAllocs()
b.ResetTimer()
for range b.N {
errSink = msgs.AppendDebugExtra(req, resp, longText)
}
assert.NoError(b, errSink)
// Most recent results:
//
// goos: darwin
// goarch: arm64
// pkg: github.com/AdguardTeam/AdGuardDNS/internal/dnsmsg
// cpu: Apple M1 Pro
// BenchmarkConstructor_AppendDebugExtra-8 8809124 137.3 ns/op 253 B/op 2 allocs/op
}

View File

@@ -14,10 +14,6 @@ import (
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
// Common domain names for tests.
const (
testDomain = "test.example"

View File

@@ -8,7 +8,6 @@ import (
"strconv"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/urlfilter/rules"
"github.com/miekg/dns"
)
@@ -64,8 +63,6 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
"ech": func(valStr string) (val dns.SVCBKeyValue) {
ech, err := base64.StdEncoding.DecodeString(valStr)
if err != nil {
log.Debug("can't parse svcb/https ech: %s; ignoring", err)
return nil
}
@@ -77,8 +74,6 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
"ipv4hint": func(valStr string) (val dns.SVCBKeyValue) {
ip := net.ParseIP(valStr)
if ip4 := ip.To4(); ip == nil || ip4 == nil {
log.Debug("can't parse svcb/https ipv4 hint %q; ignoring", valStr)
return nil
}
@@ -90,8 +85,6 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
"ipv6hint": func(valStr string) (val dns.SVCBKeyValue) {
ip := net.ParseIP(valStr)
if ip == nil {
log.Debug("can't parse svcb/https ipv6 hint %q; ignoring", valStr)
return nil
}
@@ -103,8 +96,6 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
"mandatory": func(valStr string) (val dns.SVCBKeyValue) {
code, ok := strToSVCBKey[valStr]
if !ok {
log.Debug("unknown svcb/https mandatory key %q, ignoring", valStr)
return nil
}
@@ -120,8 +111,6 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
"port": func(valStr string) (val dns.SVCBKeyValue) {
port64, err := strconv.ParseUint(valStr, 10, 16)
if err != nil {
log.Debug("can't parse svcb/https port: %s; ignoring", err)
return nil
}
@@ -142,7 +131,9 @@ var svcbKeyHandlers = map[string]svcbKeyHandler{
// ipv4hint=127.0.0.1,127.0.0.2 // Unsupported.
// ipv4hint="127.0.0.1,127.0.0.2" // Unsupported.
//
// TODO(a.garipov): Support all of these.
// TODO(a.garipov): Support all of these.
//
// TODO(a.garipov): Consider re-adding debug logging for SVCB handlers.
func (c *Constructor) NewAnswerSVCB(req *dns.Msg, svcb *rules.DNSSVCB) (ans *dns.SVCB) {
ans = &dns.SVCB{
Hdr: c.newHdr(req, dns.TypeSVCB),
@@ -157,8 +148,6 @@ func (c *Constructor) NewAnswerSVCB(req *dns.Msg, svcb *rules.DNSSVCB) (ans *dns
for k, valStr := range svcb.Params {
handler, ok := svcbKeyHandlers[k]
if !ok {
log.Debug("unknown svcb/https key %q, ignoring", k)
continue
}

View File

@@ -4,16 +4,18 @@
package cache
import (
"cmp"
"context"
"encoding/binary"
"fmt"
"log/slog"
"math"
"strings"
"time"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/bluele/gcache"
"github.com/miekg/dns"
)
@@ -22,21 +24,19 @@ import (
//
// TODO(a.garipov): Extract cache logic to golibs.
type Middleware struct {
// metrics is a listener for the middleware events.
metrics MetricsListener
// cache is the underlying LRU cache.
cache gcache.Cache
// cacheMinTTL is the minimum supported TTL for cache items.
logger *slog.Logger
metrics MetricsListener
cache gcache.Cache
cacheMinTTL time.Duration
// overrideTTL shows if the TTL overrides logic should be used.
overrideTTL bool
}
// MiddlewareConfig is the configuration structure for NewMiddleware.
type MiddlewareConfig struct {
// Logger is used to log the operation of the middleware. If Logger is nil,
// [slog.Default] is used.
Logger *slog.Logger
// MetricsListener is the optional listener for the middleware events. Set
// it if you want to keep track of what the middleware does and record
// performance metrics. If not set, EmptyMetricsListener is used.
@@ -55,15 +55,9 @@ type MiddlewareConfig struct {
// NewMiddleware initializes a new LRU caching middleware. c must not be nil.
func NewMiddleware(c *MiddlewareConfig) (m *Middleware) {
var metrics MetricsListener
if c.MetricsListener != nil {
metrics = c.MetricsListener
} else {
metrics = EmptyMetricsListener{}
}
return &Middleware{
metrics: metrics,
logger: cmp.Or(c.Logger, slog.Default()),
metrics: cmp.Or[MetricsListener](c.MetricsListener, EmptyMetricsListener{}),
cache: gcache.New(c.Count).LRU().Build(),
cacheMinTTL: c.MinTTL,
overrideTTL: c.OverrideTTL,
@@ -78,7 +72,7 @@ func (m *Middleware) Wrap(handler dnsserver.Handler) (wrapped dnsserver.Handler)
f := func(ctx context.Context, rw dnsserver.ResponseWriter, req *dns.Msg) (err error) {
defer func() { err = errors.Annotate(err, "cache: %w") }()
resp, ok := m.get(req)
resp, ok := m.get(ctx, req)
if ok {
m.metrics.OnCacheHit(ctx, req)
@@ -115,13 +109,13 @@ func (m *Middleware) Wrap(handler dnsserver.Handler) (wrapped dnsserver.Handler)
}
// get retrieves a DNS message for the specified request from the cache.
func (m *Middleware) get(req *dns.Msg) (resp *dns.Msg, found bool) {
func (m *Middleware) get(ctx context.Context, req *dns.Msg) (resp *dns.Msg, found bool) {
key := toCacheKey(req)
ciVal, err := m.cache.Get(key)
if err != nil {
if !errors.Is(err, gcache.KeyNotFoundError) {
// Shouldn't happen, since we don't set a serialization function.
log.Error("cache: error while retrieving a message from the cache: %v", err)
m.logger.ErrorContext(ctx, "retrieving from cache", slogutil.KeyError, err)
}
return nil, false
@@ -129,7 +123,12 @@ func (m *Middleware) get(req *dns.Msg) (resp *dns.Msg, found bool) {
item, ok := ciVal.(cacheItem)
if !ok {
log.Error("cache: bad type %T of cache item for name %q", ciVal, req.Question[0].Name)
m.logger.ErrorContext(
ctx,
"bad type in cache",
"type", fmt.Sprintf("%T", ciVal),
"target", req.Question[0].Name,
)
return nil, false
}

View File

@@ -10,11 +10,15 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/cache"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver/dnsservertest"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// testLogger is the common logger for tests.
var testLogger = slogutil.NewDiscardLogger()
func TestMiddleware_Wrap(t *testing.T) {
const (
servFailMaxCacheTTL = 30
@@ -186,6 +190,7 @@ func TestMiddleware_Wrap(t *testing.T) {
withCache := dnsserver.WithMiddlewares(
handler,
cache.NewMiddleware(&cache.MiddlewareConfig{
Logger: testLogger,
Count: 100,
MinTTL: minTTL,
OverrideTTL: tc.minTTL != nil,

View File

@@ -13,9 +13,11 @@ import (
type MetricsListener interface {
// OnCacheItemAdded is called when an item has been added to the cache.
OnCacheItemAdded(ctx context.Context, resp *dns.Msg, cacheLen int)
// OnCacheHit is called when a response for the specified request has been
// found in the cache.
OnCacheHit(ctx context.Context, req *dns.Msg)
// OnCacheMiss is called when a response for the specified request has not
// been found in the cache.
OnCacheMiss(ctx context.Context, req *dns.Msg)

View File

@@ -7,50 +7,6 @@ import (
"time"
)
// ContextConstructor is an interface for constructing interfaces with
// deadlines, e.g. for request contexts.
type ContextConstructor interface {
New() (ctx context.Context, cancel context.CancelFunc)
}
// DefaultContextConstructor is the default implementation of the
// [ContextConstructor] interface.
type DefaultContextConstructor struct{}
// type check
var _ ContextConstructor = DefaultContextConstructor{}
// New implements the [ContextConstructor] interface for
// DefaultContextConstructor. It returns [context.Background] and an empty
// [context.CancelFunc].
func (DefaultContextConstructor) New() (ctx context.Context, cancel context.CancelFunc) {
return context.Background(), func() {}
}
// TimeoutContextConstructor is an implementation of the [ContextConstructor]
// interface that returns a context with the given timeout.
type TimeoutContextConstructor struct {
timeout time.Duration
}
// NewTimeoutContextConstructor returns a new properly initialized
// *TimeoutContextConstructor.
func NewTimeoutContextConstructor(timeout time.Duration) (c *TimeoutContextConstructor) {
return &TimeoutContextConstructor{
timeout: timeout,
}
}
// type check
var _ ContextConstructor = (*TimeoutContextConstructor)(nil)
// New implements the [ContextConstructor] interface for
// *TimeoutContextConstructor. It returns a context with its timeout and the
// corresponding cancelation function.
func (c *TimeoutContextConstructor) New() (ctx context.Context, cancel context.CancelFunc) {
return context.WithTimeout(context.Background(), c.timeout)
}
// ctxKey is the type for context keys.
type ctxKey int

View File

@@ -9,92 +9,47 @@ package dnsserver
import (
"context"
"net"
"github.com/miekg/dns"
)
// Handler is an interface that defines how the DNS server would process DNS
// queries. Inspired by net/http.Server and it's Handler.
type Handler interface {
// ServeDNS should process the request and write a DNS response to the
// specified ResponseWriter.
//
// It accepts context.Context argument which has some additional info
// attached to it. This context always contains [ServerInfo] which can be
// retrieved using [ServerInfoFromContext] or [MustServerInfoFromContext].
// It also must contain [RequestInfo] that can be retrieved with
// [RequestInfoFromContext] or [MustRequestInfoFromContext].
ServeDNS(context.Context, ResponseWriter, *dns.Msg) (err error)
}
// The HandlerFunc type is an adapter to allow the use of ordinary functions
// as DNS handlers. If f is a function with the appropriate signature,
// HandlerFunc(f) is a Handler that calls f.
type HandlerFunc func(context.Context, ResponseWriter, *dns.Msg) (err error)
// ServeDNS implements the Handler interface for HandlerFunc.
func (f HandlerFunc) ServeDNS(ctx context.Context, rw ResponseWriter, req *dns.Msg) (err error) {
return f(ctx, rw, req)
}
// notImplementedHandlerFunc is used if no Handler is configured for a server.
var notImplementedHandlerFunc HandlerFunc = func(
ctx context.Context,
w ResponseWriter,
r *dns.Msg,
) (err error) {
res := new(dns.Msg)
res.SetRcode(r, dns.RcodeNotImplemented)
return w.WriteMsg(ctx, r, res)
}
// Server represents a DNS server.
//
// TODO(ameshkov): move validation to ctors (for all structs that inherit this).
//
// TODO(ameshkov): consider Proto()/Network()/Addr() -> single Info() func.
// TODO(a.garipov): Minimize the number of methods; consider embedding
// service.Service from golibs.
type Server interface {
// Name returns the server name.
//
// TODO(a.garipov): Consider removing.
Name() (name string)
// Proto returns the protocol of the server.
//
// TODO(a.garipov): Consider removing.
Proto() (proto Protocol)
// Network is a network (tcp, udp or empty) this server listens to. If it
// is empty, the server listens to all networks that are supposed to be
// used by its protocol.
//
// TODO(a.garipov): Consider removing.
Network() (network Network)
// Addr returns the address the server was configured to listen to.
Addr() (addr string)
// Start starts the server, exits immediately if it failed to start
// listening. Start returns once all servers are considered up.
Start(ctx context.Context) (err error)
// Shutdown stops the server and waits for all active connections to close.
Shutdown(ctx context.Context) (err error)
// LocalTCPAddr returns the TCP address the server listens to at the moment
// or nil if it does not listen to TCP. Depending on the server protocol
// it may correspond to DNS-over-TCP, DNS-over-TLS, HTTP2, DNSCrypt (TCP).
LocalTCPAddr() (addr net.Addr)
// LocalUDPAddr returns the UDP address the server listens to at the moment or
// nil if it does not listen to UDP. Depending on the server protocol
// it may correspond to DNS-over-UDP, HTTP3, QUIC, DNSCrypt (UDP).
// LocalUDPAddr returns the UDP address the server listens to at the moment
// or nil if it does not listen to UDP. Depending on the server protocol it
// may correspond to DNS-over-UDP, HTTP3, QUIC, DNSCrypt (UDP).
LocalUDPAddr() (addr net.Addr)
}
// A ResponseWriter interface is used by a DNS handler to construct a DNS
// response.
type ResponseWriter interface {
// LocalAddr returns the net.Addr of the server.
LocalAddr() net.Addr
// RemoteAddr returns the net.Addr of the client that sent the current
// request.
RemoteAddr() net.Addr
// WriteMsg writes a reply back to the client.
//
// Handlers must not modify req and resp after the call to WriteMsg, since
// their ResponseWriter implementation may be a recorder.
//
// TODO(a.garipov): Store bytes written to the socket.
WriteMsg(ctx context.Context, req, resp *dns.Msg) error
}

View File

@@ -1,15 +1,6 @@
package dnsserver_test
import (
"testing"
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/testutil"
)
func TestMain(m *testing.M) {
testutil.DiscardLogOutput(m)
}
import "github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
// testTimeout is a common timeout for tests.
const testTimeout = dnsserver.DefaultReadTimeout

View File

@@ -12,6 +12,7 @@ import (
"github.com/AdguardTeam/AdGuardDNS/internal/dnsserver"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil"
"github.com/ameshkov/dnscrypt/v2"
"github.com/miekg/dns"
@@ -27,11 +28,12 @@ import (
func RunDNSServer(t testing.TB, h dnsserver.Handler) (s *dnsserver.ServerDNS, addr string) {
t.Helper()
conf := dnsserver.ConfigDNS{
ConfigBase: dnsserver.ConfigBase{
Name: "test",
Addr: "127.0.0.1:0",
Handler: h,
conf := &dnsserver.ConfigDNS{
Base: &dnsserver.ConfigBase{
BaseLogger: slogutil.NewDiscardLogger(),
Name: "test",
Addr: "127.0.0.1:0",
Handler: h,
},
MaxUDPRespSize: dns.MaxMsgSize,
}
@@ -59,12 +61,13 @@ func RunDNSServer(t testing.TB, h dnsserver.Handler) (s *dnsserver.ServerDNS, ad
func RunTLSServer(t testing.TB, h dnsserver.Handler, tlsConfig *tls.Config) (addr *net.TCPAddr) {
t.Helper()
conf := dnsserver.ConfigTLS{
ConfigDNS: dnsserver.ConfigDNS{
ConfigBase: dnsserver.ConfigBase{
Name: "test",
Addr: "127.0.0.1:0",
Handler: h,
conf := &dnsserver.ConfigTLS{
DNS: &dnsserver.ConfigDNS{
Base: &dnsserver.ConfigBase{
BaseLogger: slogutil.NewDiscardLogger(),
Name: "test",
Addr: "127.0.0.1:0",
Handler: h,
},
},
TLSConfig: tlsConfig,
@@ -118,14 +121,15 @@ func RunDNSCryptServer(t testing.TB, h dnsserver.Handler) (s *TestDNSCryptServer
s.ResolverPk = testutil.RequireTypeAssert[ed25519.PublicKey](t, pk)
conf := dnsserver.ConfigDNSCrypt{
ConfigBase: dnsserver.ConfigBase{
Name: "test",
Addr: "127.0.0.1:0",
Handler: h,
conf := &dnsserver.ConfigDNSCrypt{
Base: &dnsserver.ConfigBase{
BaseLogger: slogutil.NewDiscardLogger(),
Name: "test",
Addr: "127.0.0.1:0",
Handler: h,
},
DNSCryptProviderName: s.ProviderName,
DNSCryptResolverCert: cert,
ProviderName: s.ProviderName,
ResolverCert: cert,
}
// Create a new ServerDNSCrypt and run it.
@@ -166,12 +170,13 @@ func RunLocalHTTPSServer(
tlsConfigH3.NextProtos = dnsserver.NextProtoDoH3
}
conf := dnsserver.ConfigHTTPS{
ConfigBase: dnsserver.ConfigBase{
Name: "test",
Addr: "127.0.0.1:0",
Handler: h,
Network: network,
conf := &dnsserver.ConfigHTTPS{
Base: &dnsserver.ConfigBase{
BaseLogger: slogutil.NewDiscardLogger(),
Name: "test",
Addr: "127.0.0.1:0",
Handler: h,
Network: network,
},
TLSConfDefault: tlsConfig,
TLSConfH3: tlsConfigH3,
@@ -197,12 +202,13 @@ func RunLocalQUICServer(
h dnsserver.Handler,
tlsConfig *tls.Config,
) (s *dnsserver.ServerQUIC, addr *net.UDPAddr, err error) {
conf := dnsserver.ConfigQUIC{
conf := &dnsserver.ConfigQUIC{
TLSConfig: tlsConfig,
ConfigBase: dnsserver.ConfigBase{
Name: "test",
Addr: "127.0.0.1:0",
Handler: h,
Base: &dnsserver.ConfigBase{
BaseLogger: slogutil.NewDiscardLogger(),
Name: "test",
Addr: "127.0.0.1:0",
Handler: h,
},
}

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