mirror of
https://github.com/AdguardTeam/AdGuardDNS.git
synced 2025-12-23 23:38:37 -05:00
Sync v2.12.0
This commit is contained in:
42
CHANGELOG.md
42
CHANGELOG.md
@@ -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 **doesn’t** 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 profile’s 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 profile’s 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 profile’s 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 it’s 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 it’s 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 isn’t 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 it’s 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 it’s 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` doesn’t 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 it’s 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 it’s 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 certificate’s DNS Names section of SAN.
|
||||
|
||||
## AGDNS-167 / Build 39
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
51
go.mod
@@ -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
128
go.sum
@@ -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=
|
||||
|
||||
43
go.work.sum
43
go.work.sum
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
76
internal/agdrand/agdrand.go
Normal file
76
internal/agdrand/agdrand.go
Normal 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
|
||||
}
|
||||
118
internal/agdrand/agdrand_test.go
Normal file
118
internal/agdrand/agdrand_test.go
Normal 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
|
||||
}
|
||||
@@ -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{}
|
||||
@@ -1,6 +0,0 @@
|
||||
package agdservice_test
|
||||
|
||||
import "time"
|
||||
|
||||
// testTimeout is the timeout for common test operations.
|
||||
const testTimeout = 1 * time.Second
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
37
internal/agdvalidate/agdvalidate.go
Normal file
37
internal/agdvalidate/agdvalidate.go
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
}}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
75
internal/connlimiter/metrics.go
Normal file
75
internal/connlimiter/metrics.go
Normal 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) {}
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
65
internal/dnscheck/metrics.go
Normal file
65
internal/dnscheck/metrics.go
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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") }()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
31
internal/dnsdb/metrics.go
Normal 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) {}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
43
internal/dnsserver/cache/cache.go
vendored
43
internal/dnsserver/cache/cache.go
vendored
@@ -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
|
||||
}
|
||||
|
||||
5
internal/dnsserver/cache/cache_test.go
vendored
5
internal/dnsserver/cache/cache_test.go
vendored
@@ -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,
|
||||
|
||||
2
internal/dnsserver/cache/metrics.go
vendored
2
internal/dnsserver/cache/metrics.go
vendored
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user